¿El índice de matriz de JavaScript es una cadena o un entero?
arrays indices (5)
Tenía una pregunta genérica sobre las matrices de JavaScript. ¿Los índices de matriz en JavaScript se manejan internamente como cadenas? Leí en alguna parte que debido a que las matrices son objetos en JavaScript, el índice es en realidad una cadena. Estoy un poco confundido acerca de esto, y estaría encantado de cualquier explicación.
En JavaScript hay dos tipos de matrices: matrices estándar y matrices asociativas (o un objeto con propiedades)
- [] - matriz estándar - solo índices enteros basados en 0
- {} - matriz asociativa - objetos JavaScript donde las claves pueden ser cualquier cadena
Entonces ...
var arr = [ 0, 1, 2, 3 ];
... se define como una matriz estándar donde los índices solo pueden ser enteros. Cuando haces arr ["algo"] ya que algo (que es lo que usas como índice) no es un número entero, básicamente estás definiendo una propiedad para el objeto arr (todo es objeto en JavaScript). Pero no está agregando un elemento a la matriz estándar.
Eso es correcto, entonces:
> var a = [''a'',''b'',''c'']
undefined
> a
[ ''a'', ''b'', ''c'' ]
> a[0]
''a''
> a[''0'']
''a''
> a[''4''] = ''e''
''e''
> a[3] = ''d''
''d''
> a
[ ''a'', ''b'', ''c'', ''d'', ''e'' ]
Formalmente, todos los nombres de propiedad son cadenas. Eso significa que los nombres de propiedades numéricas en forma de matriz realmente no son diferentes de cualquier otro nombre de propiedad.
Si marca el paso 6 en la parte relevante de la especificación , verá que las expresiones de acceso a la propiedad siempre se convierten en cadenas antes de buscar la propiedad. Ese proceso se sigue (formalmente) independientemente de si el objeto es una instancia de matriz u otro tipo de objeto. (Nuevamente, solo tiene que parecer que eso es lo que está sucediendo).
Ahora, internamente , el tiempo de ejecución de JavaScript es libre de implementar la funcionalidad de matriz de la forma que desee.
editar
: tuve la idea de jugar con
Number.toString
para demostrar que se produce una conversión de número a cadena, pero resulta que la especificación describe explícitamente que la conversión de tipo específico se lleva a cabo a través de un proceso interno, y no por un proceso implícito cast seguido de una llamada a
.toString()
(que probablemente sea algo bueno por razones de rendimiento).
Sí, técnicamente los índices de matriz son cadenas, pero como Flanagan lo puso elegantemente en su ''Guía definitiva'':
"Es útil distinguir claramente un índice de matriz de un nombre de propiedad de objeto. Todos los índices son nombres de propiedad, pero solo los nombres de propiedad que son enteros entre 0 y 2
32
-1 son índices".
Por lo general, no debería importarle lo que el navegador (o más en general ''script-host'') hace internamente siempre y cuando el resultado se ajuste a un resultado predecible y (generalmente / con suerte) especificado. De hecho, en el caso de javascript (o ECMAScript 262) solo se describe en términos de qué pasos conceptuales se necesitan. Eso (intencionalmente) deja espacio para que el host de script (y los navegadores) presenten formas inteligentes, más pequeñas y rápidas para implementar ese comportamiento específico.
De hecho, los navegadores modernos utilizan una serie de algoritmos diferentes para diferentes tipos de matrices internamente: importa lo que contienen, qué tan grandes son, si están en orden, si están fijos y son optimizables en tiempo de compilación (jit) o si son escasos o densos (sí, a menudo vale la pena hacer una
new Array(length_val)
lugar de ninja
[]
).
En su concepto de pensamiento (cuando aprende javascript) puede ser útil saber que las matrices son solo tipos especiales de objetos. Pero no siempre son lo mismo que uno podría esperar, por ejemplo:
var a=[];
a[''4294967295'']="I''m not the only one..";
a[''4294967296'']="Yes you are..";
alert(a); // === I''m not the only one..
aunque es fácil y bastante transparente para el programador desinformado tener una matriz (con índices) y adjuntar propiedades al objeto matriz.
La mejor respuesta (creo) es de la especificación (15.4) en sí:
Objetos de matriz
Los objetos de matriz dan un tratamiento especial a una determinada clase de nombres de propiedad. Un nombre de propiedad P (en forma de un valor de cadena) es un índice de matriz si y solo si ToString (ToUint32 (P)) es igual a P y ToUint32 (P) no es igual a 2 32 −1 . Una propiedad cuyo nombre de propiedad es un índice de matriz también se denomina elemento. Cada objeto de matriz tiene una propiedad de longitud cuyo valor es siempre un entero no negativo menor que 2 32 . El valor de la propiedad de longitud es numéricamente mayor que el nombre de cada propiedad cuyo nombre es un índice de matriz; cada vez que se crea o cambia una propiedad de un objeto Array, se ajustan otras propiedades según sea necesario para mantener esta invariante. Específicamente, cada vez que se agrega una propiedad cuyo nombre es un índice de matriz, la propiedad de longitud se cambia, si es necesario, para que sea uno más que el valor numérico de ese índice de matriz; y cada vez que se cambia la propiedad de longitud, cada propiedad cuyo nombre es un índice de matriz cuyo valor no es menor que la nueva longitud se elimina automáticamente. Esta restricción se aplica solo a las propiedades propias de un objeto Array y no se ve afectada por las propiedades de longitud o índice de matriz que pueden heredarse de sus prototipos.
Se dice que un objeto, O, es escaso si el siguiente algoritmo devuelve verdadero:
- Sea len el resultado de llamar al método interno [[Get]] de O con el argumento "length".
Para cada entero i en el rango 0≤i
a. Deje que elem sea el resultado de llamar al método interno [[GetOwnProperty]] de O con el argumento ToString (i).
si. Si elem no está definido, devuelve verdadero.Falso retorno.
Efectivamente, la especificación ECMAScript 262 solo garantiza al programador javascript referencias de matriz inequívocas independientemente de obtener / configurar
arr[''42'']
o
arr[42]
hasta 32 bits sin signo.
La principal diferencia es, por ejemplo, (actualización automática de)
array.length
,
array.push
y otros array-sugar como
array.concat
etc.
Si bien sí, javascript también permite un bucle sobre las propiedades que uno ha establecido en un objeto, no podemos leer cuánto hemos establecido (sin un bucle).
Y sí, que yo sepa, los navegadores modernos (especialmente Chrome en lo que llaman (pero no especifican exactamente)) ''enteros pequeños'' son muy rápidos con matrices verdaderas (preinicializadas) de pequeño tamaño.
Ver también, por ejemplo, this pregunta relacionada.
Editar: según la prueba de @Felix Kling (de su comentario anterior):
Después de
arr[4294967294] = 42;
,
arr.length
muestra correctamente
4294967295
.
Sin embargo, llamando a
arr.push(21)
;
arroja un
RangeError: Invalid array length
.
arr[arr.length] = 21
funciona, pero no cambia la longitud.
La explicación de este comportamiento (predecible y previsto) debe quedar clara después de esta respuesta.
Edit2:
Ahora, alguien dio el comentario:
for (var i in a) console.log (typeof i) muestra ''string'' para todos los índices.
Dado que
for in
es el iterador de propiedades (desordenado
debo
agregar) en javascript, es bastante obvio que devuelve una cadena (estaría bastante mal si no lo hiciera).
De MDN :
for..in no debe usarse para iterar sobre una matriz donde el orden del índice es importante.
Los índices de matriz son solo propiedades enumerables con nombres enteros y, por lo demás, son idénticas a las propiedades generales de Object. No hay garantía de que for ... in devuelva los índices en un orden particular y devolverá todas las propiedades enumerables, incluidas las que tienen nombres no enteros y las que se heredan.
Debido a que el orden de iteración depende de la implementación, iterar sobre una matriz puede no visitar elementos en un orden consistente. Por lo tanto, es mejor usar un bucle for con un índice numérico (o Array.forEach o el bucle for ... of) al iterar sobre matrices donde el orden de acceso es importante.
¿Así que, qué hemos aprendido? Si el orden es importante para nosotros (a menudo es con matrices), entonces NECESITAMOS esta peculiar matriz en javascript, y tener una ''longitud'' es bastante útil para hacer un bucle en orden numérico.
Ahora piense en la alternativa: dé a sus objetos una identificación / orden, pero luego tendrá que recorrer sus objetos para cada próxima identificación / orden (propiedad) una vez más.
Edición 3:
Alguien respondió en la línea de:
var a = [''a'',''b'',''c''];
a[''4''] = ''e'';
a[3] = ''d'';
alert(a); // returns a,b,c,d,e
Ahora, usando la explicación en mi respuesta: lo que sucedió es que
''4''
es coercible al número entero
4
y que está en el rango
[0, 4294967295]
convirtiéndolo en un
index
matriz válido también llamado
element
.
Como var
a
es una matriz (
[]
), el
elemento de
matriz 4 se agrega como
elemento de
matriz, no como propiedad (lo que hubiera sucedido si var
a
fuera un objeto (
{}
).
Un ejemplo para describir aún más la diferencia entre matriz y objeto:
var a = [''a'',''b'',''c''];
a[''prop'']=''d'';
alert(a);
vea cómo devuelve
a,b,c
sin ''d'' para ser visto.
Edición 4:
Comentó:
"En ese caso, un índice entero debe manejarse como una cadena, ya que es una propiedad de la matriz, que es un tipo especial de objeto JS".
Eso es
incorrecto
en términos de terminología porque: (cadenas que representan) índices enteros (entre [0, 4294967295]) crean
indexes
matriz o
elements
;
No
properties
.
Es mejor decir: tanto un entero real
como
una
string
representa un entero (ambos entre [0, 4294967295]) es un
índice de
matriz válido (y debe considerarse conceptualmente como entero) y crea / cambia
elementos de la
matriz (las ''cosas'' / valores (solo) que se devuelven cuando haces
arr.join()
o
arr.concat()
por ejemplo).
Todo lo demás crea / cambia una
propiedad
(y debe considerarse conceptualmente como una cadena).
Lo que el navegador realmente hace, por lo general, no debería interesarle, ya que cuanto más simple y claro especifique su código, más posibilidades tendrá el navegador de reconocer: ''oh, optimicemos esto a una matriz real debajo del capó''.
Veamos:
[1]["0"] === 1 // true
Ah, pero eso no es concluyente, ya que el tiempo de ejecución podría estar obligando a
"0"
a
+"0"
y
+"0" === 0
.
[1][false] === undefined // true
Ahora,
+false === 0
, entonces no, el tiempo de ejecución no está coaccionando el valor a un número.
var arr = [];
arr.false = "foobar";
arr[false] === "foobar" // true
Entonces, en realidad, el tiempo de ejecución está coaccionando el valor a una cadena. Entonces sí, es una búsqueda de tabla hash (externamente).