Volver al Centro de conocimiento
Huella digital

Orden de claves: enteros ordenan, cadenas se quedan

Objetos con claves mixtas: las enteras ordenan ascendente, las de cadena mantienen la inserción, cuáles cuentan como enteras, y cómo JSON, Map, for-in y spread siguen el orden.

BotBrowser Team

Documentación

Quieres la documentación estructurada de Huella digital?

Este artículo forma parte de la biblioteca editorial. Para pasos de configuración, material de referencia y actualizaciones continuas, entra en la sección de docs.

BotBrowser soporta repetir un fixture autorizado del mismo origen bajo un perfil declarado y conservar observaciones acotadas sobre el orden de claves que una página iteró y la cadena que serializó, pero no puede cambiar cómo un entorno ordena las claves de un objeto ordinario, no puede hacer que una clave entera mantenga el orden de inserción y no convierte un orden de claves en una señal de identidad. Esta revisión construye objetos con claves mixtas y los lee por Object.keys, JSON.stringify, for...in, Map y spread, y luego registra por qué las claves enteras ordenan ascendente mientras las de cadena mantienen el orden de inserción, qué claves cuentan como enteras, por qué for...in difiere de Object.keys, y dónde Map y JSON se separan de las reglas del objeto ordinario.

Resumen

  • Las claves enteras siempre van primero y ordenan ascendente, así que insertar b, 2, a, 10, 1 se lee como ["1","2","10","b","a"] sin importar el orden en que se escribieron.
  • Solo cuentan las claves que son un entero canónico de 0 a 4294967294; 01, -1 y 1.5 son claves de cadena y mantienen el orden de inserción.
  • Las claves de cadena mantienen el orden de inserción, los símbolos van después de toda clave de cadena y Object.keys los omite.
  • JSON.stringify sigue el mismo orden, así que un objeto mixto serializa con las claves enteras primero aunque se añadieran al final.
  • for...in incluye claves enumerables heredadas; Object.keys devuelve solo las claves propias.
  • Borrar y re-añadir una clave de cadena la mueve al final, mientras una clave entera se re-ordena a su posición numérica.
  • Map mantiene el orden de inserción incluso para claves enteras, así que las mismas claves en un Map y en un objeto se leen en órdenes distintos.

Contenido

Un objeto con claves insertadas B, 2, A, 10, 1 se lee como 1, 2, 10, B, A porque las claves enteras ordenan ascendente mientras las de cadena mantienen el orden de inserción; una columna MAP mantiene el orden de inserción B, 2, 1, y una columna JSON escribe las claves enteras primero aunque se añadieran al final

Por qué las claves enteras ordenan antes que las de cadena

Un objeto ordinario no itera las claves en el orden en que se escribieron. El orden que define el lenguaje es: primero las claves enteras en orden numérico ascendente, luego las claves de cadena restantes en orden de inserción, luego los símbolos en orden de inserción. La sonda escribe cinco claves con mezcla de enteras y de cadena y las lee:

Object=function Map=function node=v26.9.0
insert b,2,a,10,1 -> keys ["1","2","10","b","a"]

Las claves se insertaron b, 2, a, 10, 1, pero Object.keys devuelve 1, 2, 10, b, a. Las tres claves enteras 2, 10 y 1 van primero, ordenadas como números de modo que 1 precede a 2 precede a 10 en vez de ordenar como texto donde 10 precedería a 2. Las dos claves de cadena b y a siguen en el orden en que se añadieron. Nada se reordenó a mano; el entorno aplicó el orden de la especificación en cada lectura.

Esa ordenación numérica es por lo que 10 antes que 2 es la intuición equivocada: como texto "10" ordena antes que "2", pero como índice ordena después. Un registro que compara dos listas de claves asume que son comparables solo si ambas se construyeron igual, porque un objeto construido por inserción y un objeto construido por JSON.parse del mismo texto pueden iterar en el mismo orden, mientras un objeto cuyas claves enteras se añadieron después igualmente las ordena primero.

La consecuencia práctica es que un orden de claves registrado de un objeto no es el orden de inserción. Quien añade b luego 2 y lee 2 luego b ve una reordenación, y un diff de dos objetos construidos con las mismas claves en órdenes de inserción distintos está vacío porque las claves enteras dominan el frente de ambos. Un registro de las claves de un objeto con la palabra "orden de inserción" es por eso erróneo siempre que el objeto tenga claves enteras.

La reordenación también decide la primera clave que devuelve un iterador, y un lector que toma Object.keys(o)[0] como "el primer campo que la página puso" lee el campo equivocado siempre que exista alguna clave entera. El frente de la lista es la clave entera más pequeña, no la insertada primero, así que el mismo código devuelve un campo distinto para un objeto con clave numérica y para uno sin ella. Es un defecto real en un código que infiere la prioridad de campos por posición, porque la inferencia solo es válida para objetos cuyas claves son todas cadenas no enteras.

Qué claves cuentan como enteras

No toda cadena que parece numérica es una clave entera, y la frontera decide qué claves ordenan y cuáles mantienen el orden de inserción. La sonda escribe cinco claves que parecen numéricas pero no son índices canónicos:

non-index keys order -> ["4294967294","01","-1","1.5","4294967295"]

4294967294 es 2^32 - 2, el mayor índice de arreglo canónico, y ordena al frente como clave entera. Las claves 01, -1 y 1.5 no son enteros canónicos (01 tiene cero inicial, -1 es negativo, 1.5 no es entero), así que son claves de cadena ordinarias y mantienen el orden de inserción tras la clave entera. La clave 4294967295 es 2^32 - 1, una pasada del mayor índice, así que no cuenta como clave entera y queda como clave de cadena al final. La frontera es exacta: de 0 a 4294967294 son índices, y 2^32 - 1 no.

Esa frontera es por lo que una clave que parece numérica puede cambiar en silencio el orden de un objeto entero. Un código que usa un entero grande como id y lo añade a un objeto lo coloca al frente en orden numérico, mientras el mismo id escrito con un cero inicial o un signo se convierte en clave de cadena que se queda donde se insertó. Dos objetos que un lector llamaría "los mismos datos" entonces iteran en órdenes distintos, y cualquier código que lee la primera clave o depende de la posición ve un valor distinto.

La regla que mantiene seguro un código es usar un Map cuando importa el orden de inserción y tratar el orden de claves de un objeto como no fiable para todo salvo la regla entera-luego-cadena. Un registro que afirma preservar el orden de campos de un servidor a través de un objeto ordinario es erróneo para cualquier campo cuyo nombre sea un entero canónico, porque el objeto moverá ese campo al frente sin importar dónde lo escribió el servidor.

Cómo for-in difiere de Object.keys

Dos bucles que parecen idénticos leen un conjunto de claves distinto, y la diferencia es la herencia. La sonda construye un objeto hijo cuyo prototipo tiene una clave y luego compara los dos iteradores:

for...in -> ["own","inherited"]
Object.keys -> ["own"]

for...in recorre las claves propias del hijo y luego las claves enumerables que hereda de su cadena de prototipos, así que lista own e inherited. Object.keys devuelve solo las claves de cadena enumerables propias del objeto, así que lista solo own. Un objeto creado con un prototipo que lleva valores por defecto por eso listará esos valores en un bucle for...in y no en Object.keys, y un serializador construido sobre for...in copiará al resultado campos heredados mientras uno construido sobre Object.keys no.

Ese hueco es la fuente de una fuga sutil: un bucle for...in sobre un objeto que hereda de un prototipo poblado lee campos que quien llama nunca puso, y un registro que guarda la salida del bucle atribuye esos campos al objeto. Object.keys, Object.values y Object.entries toman la vista de claves propias, así que un registro construido sobre ellos describe solo lo que el objeto posee. Los dos iteradores también difieren en símbolos: for...in nunca visita claves de símbolo y Object.keys tampoco las devuelve, así que ninguno ve el símbolo de la sonda anterior, y solo Reflect.ownKeys o Object.getOwnPropertySymbols alcanza los símbolos.

Cómo JSON, Map y spread siguen o rompen el orden

Los serializadores y contenedores que usa una página heredan o sobrescriben el orden del objeto ordinario, y la sonda muestra tres comportamientos. JSON sigue el orden del objeto, spread copia en el mismo orden, y un Map mantiene el orden de inserción incluso para claves enteras:

JSON.stringify order -> {"2":2,"b":1,"a":3}
JSON.stringify mixed -> {"2":"y","10":"x","z":"w"}
Map keys -> ["b","2","1"]
Object with same keys -> ["1","2","b"]
spread copy order -> ["2","b","a"]

JSON.stringify de un objeto con claves b, 2, a escribe 2 primero porque las claves enteras ordenan al frente, así que el texto serializado no refleja el orden en que se pusieron los campos. El objeto mixto serializa 2 luego 10 luego z, otra vez claves enteras primero en orden numérico. Una página que compara dos cuerpos serializados por igualdad ve las claves enteras siempre en la misma posición del frente, lo que oculta una diferencia de orden de inserción que el objeto de otro modo habría mostrado.

Las líneas de Map son el contraste que importa. Un Map con claves b, 2, 1 leído por [...map.keys()] devuelve b, 2, 1, el orden de inserción, porque un Map itera entradas en el orden en que se fijaron y no ordena claves enteras. Convertir ese Map a un objeto con Object.fromEntries reordena las mismas claves a 1, 2, b, así que un viaje por un objeto cambia el orden que un Map preservó. Un registro que guarda un Map como objeto y lo lee de vuelta ha reemplazado en silencio el orden de inserción por la regla entera-luego-cadena.

Esa divergencia es por lo que la elección de contenedor es una decisión de contrato de datos, no de estilo. Una página que recibe una lista de ajustes, los guarda en un Map para mantener el orden de llegada y luego los serializa como objeto ordinario para almacenarlos ha descartado el orden de llegada en el momento en que convirtió. Un lector que luego carga el objeto guardado y lee la primera clave ve la clave entera más pequeña, no el primer ajuste que llegó, y nada en la forma guardada registra cuál era. Mantener el Map como Map, o guardar el orden de claves como arreglo explícito junto a los valores, es lo que preserva el hecho que el objeto descartó.

La línea de spread confirma que la copia mantiene el orden del objeto: expandir {b:1, 2:2, a:3} en un objeto nuevo da claves 2, b, a, la clave entera primero. Spread copia las claves de cadena enumerables propias en orden de enumeración, así que preserva la reordenación y no la inserción original. Quien construye una copia limpia expandiendo un objeto y espera que el orden de campos coincida con la fuente acierta solo para objetos sin claves enteras.

Dónde borrar y re-añadir mueve una clave

Borrar una clave y añadirla de nuevo cambia su posición, y el cambio depende de si la clave es entera. La sonda borra y re-añade una clave de cadena y una clave entera:

delete+readd string -> ["b","c","a"]
delete+readd integer -> ["3","5"]

Para el objeto de claves de cadena {a, b, c}, borrar a y re-añadirla mueve a al final, así que el orden pasa a b, c, a. Esa es la firma de una clave ordenada por inserción: quitarla olvida su ranura, y re-añadirla la anexa. Para el objeto de claves enteras con claves 5 y 3, borrar 3 y re-añadirla deja el orden 3, 5, porque una clave entera se re-ordena a su ranura numérica sin importar cuándo se añadió. El comportamiento de borrar-y-mover-al-final se aplica solo a claves de cadena, y las claves enteras son inmunes a él.

Esa diferencia es por lo que un enfoque de "borrar la clave para llevarla al final" funciona con claves de cadena y falla con claves enteras. Una caché que borra y re-añade una entrada caliente para mantenerla al final de un objeto funciona solo mientras la clave es una cadena; en el momento en que la clave es un entero canónico la entrada vuelve de golpe a su posición numérica y el enfoque deja de funcionar en silencio. La misma asimetría aparece en un arreglo: los índices de un arreglo son claves enteras, así que un arreglo siempre itera en orden de índice, lo que la sonda confirma para un arreglo simple con una propiedad de cadena extra.

Registra una lectura de claves

Una lectura de claves sostiene una afirmación estrecha: este entorno, este objeto, iterado con este método, produjo esta lista de claves. Seis campos la mantienen verificable, y ninguno asume que la lista es orden de inserción:

  1. El método de iteración, Object.keys, for...in, Reflect.ownKeys, JSON.stringify o un iterador de Map, porque cada uno devuelve un conjunto u orden distinto.
  2. Las claves tal como se devolvieron, con las claves enteras ya en orden numérico, porque la lista cruda es el único registro fiel y un "orden de inserción" re-derivado es erróneo una vez que hay una clave entera.
  3. Si el objeto tiene un prototipo que aporta claves enumerables, porque for...in las lee y Object.keys no.
  4. Si se pidieron claves de símbolo, porque Object.keys y for...in las omiten y solo Reflect.ownKeys las incluye.
  5. Para un Map, el orden de inserción tal como se fijó, porque una conversión a objeto y de vuelta reordena las claves enteras.
  6. Si alguna clave se borró y re-añadió, porque una clave de cadena se mueve al final mientras una entera se re-ordena, y un registro que lo omite no puede explicar una clave movida.

Lo que tal lectura no puede mostrar es nada sobre cómo un servidor produjo las mismas claves. Un servidor que escribe JSON en un orden de campos puede ser re-serializado por un entorno de cliente que mueve las claves enteras al frente, así que el orden que eligió el servidor no es el orden que lee la página. Para el lado de copia de los mismos objetos, structuredClone frente a JSON muestra qué tipos de contenedor sobreviven a una copia profunda y qué colapsa un viaje por JSON, y para el texto que un regex escanea sobre la forma serializada, RegExp lastIndex y flags muestra por qué un patrón global reutilizado cambia su respuesta.

Dos hábitos mantienen la lectura honesta con el tiempo. Primero, registra la lista de claves cruda del método exacto usado, porque la regla entera-luego-cadena hace que un supuesto orden de inserción sea erróneo para cualquier objeto con una clave numérica. Segundo, usa un Map cuando el orden de inserción es parte de los datos, porque un objeto ordinario reordena las claves enteras en cada lectura y en cada viaje por JSON y spread.

Fuentes públicas

Conclusión

Trata el orden de claves de un objeto como la regla entera-luego-cadena, no como orden de inserción: las claves enteras ordenan ascendente y van primero, solo cuentan los enteros canónicos de 0 a 4294967294, y 01, -1, 1.5 y 4294967295 quedan como claves de cadena. Sabe que JSON.stringify y spread siguen ese orden, que Map mantiene el orden de inserción incluso para claves enteras así que un viaje por un objeto las reordena, y que for...in lee claves heredadas donde Object.keys no. Borrar y re-añadir mueve una clave de cadena al final pero deja una clave entera en su ranura numérica. Registra el método exacto y la lista de claves cruda, y usa un Map cuando el orden de inserción es parte de los datos, para que un orden de campos afirmado nunca contradiga el orden que un entorno realmente lee.

Lectura siguiente

Para qué tipos de contenedor sobreviven a una copia profunda antes de que se lea ninguna clave, lee structuredClone frente a JSON; para por qué un patrón global reutilizado sobre el texto serializado cambia su respuesta, lee RegExp lastIndex y flags; para el orden que un sort da a un array de esas claves, lee Orden de array y comparadores.

#Object.keys#Orden De Enumeración#JSON.stringify#Map#Orden De Propiedades

Este artículo forma parte de la guía: ¿Qué es la huella digital del navegador?

Lleva BotBrowser de la investigación a producción

Usa estas guías para entender el modelo y después avanzar hacia validación multiplataforma, contextos aislados y despliegue de navegador preparado para escalar.