Sea:
var a = [];
Dice la especificación de Javascript que los Arrays son simplemente unos objetos un poco curiosos y más o menos simpáticos. Tienen una propiedad especial llamada length
y si le intentamos añadir una propiedad con un nombre que sea un número válido (uint32) entonces lo consideran un "índice" y tratan ese valor como un elemento de un array/vector/llámaloX que mantiene internamente.
La propiedad length
es especial porque se actualiza más allá de nuestro control cuando ocurren algunas cosas. En concreto lo que dice la especificación es son las siguientes 2 cosas: Cada vez que se añada al array un elemento, es decir, cito (énfasis mío): "una propiedad cuyo nombre es un índice válido de array, si es necesario, length
se actualizará a ser 1 más que ese índice". Además, cuando se modifique length
, para mantener la restricción, "cualquier propiedad cuyo nombre es un índice válido que no es menor que el nuevo length
, se borra automáticamente".
Planteémos los 2 casos:
a.length = 10;
Al hacer esto, no se crea nada ni se borra nada. Sólo se actualiza el valor de length
. La restricción se sigue cumpliendo y es válida, luego el array es correcto y todo está bien. (Puedes ver el código de V8, SpiderMonkey, etc para asegurarse si quieres)
¿Cuál es el problema? El problema es que el resto de métodos de Array
(básicamente join
, ya que toString
se apoya normalmente en join
) se apoyan en length
para devolver una representación del array. Es decir, la consola de Firefox o de Chrome no te engaña (VER NOTA AL FINAL), pero te está mostrando una cosa distinta a la que quieres ver. Lo que quieres ver es si realmente se ha creado una propiedad de nombre '0'
, '1'
, '2'
, etc. Para eso, nada más fácil que hacer:
3 in a
Y la consola dirá:
false
Si queremos de verdad comprobar que ese es el caso, podemos hacerlo fácilmente:
var a = [];
a.length = 10;
a[1] = undefined;
a[4] = undefined;
Ahora pedimos a
y toString
parece engañarnos (simplemente muestra otra cosa). Si preguntamos:
3 in a
dirá false
, pero si preguntamos:
4 in a
dirá true
, porque sí, tiene valor undefined
pero hemos creado realmente la propiedad con nombre '4'
.
NOTA: Si hacemos a = new Array(10);
cambia porque estamos creando un objeto nuevo. Pero aún así, el constructor Array
tampoco crea las propiedades con nombre que es índice válido blabla... los elementos:
a = new Array(10);
3 in a // -> false
Si en lugar de lo anterior hacemos cualquiera de estas cosas:
a = [undefined, undefined, undefined, undefined,undefined, undefined, undefined, undefined,undefined, undefined];
// o:
a.push(undefined); // diez veces;
En el primer caso estamos creando un Array
nuevo, y a ese le estamos metiendo explícitamente 10 elementos. En el otro caso, en cada push
: a. buscamos la length
actual, b. añadimos un elemento cuyo nombre es el que toca según length
y cuyo valor es undefined
, c. actualizamos length
para que siga cumpliendo la restricción. (El caso de hacer un push
múltiple es igual, internamente hace casi lo mismo diez veces.
Y el problema? Pues que lo que nos saca toString
en este caso es igual que lo que saca en el anterior. Pero como digo es un problema de mirar una cosa cuando lo que queremos mirar es otra.
NOTA AL FINAL:
De hecho, la nueva consola de Firefox es en esto (y en otras muchas cosas) mejor que la de Chrome. Cuando hacemos explícitamente a[4] = undefined
podemos distinguirlo de cuando simplemente no se ha definido. Me dice la consola:
> var a = []; a.length = 10;
[23:33:00.116] 10
> a
[23:33:02.125] [, , , , , , , , , ,]
--
> a[4] = undefined;
[23:33:13.314] undefined
> a
[23:33:15.719] [, , , , (void 0), , , , , ,]