O JavaScript garante a ordem da propriedade do objeto?

647

Se eu criar um objeto como este:

var obj = {};
obj.prop1 = "Foo";
obj.prop2 = "Bar";

O objeto resultante sempre será assim?

{ prop1 : "Foo", prop2 : "Bar" }

Ou seja, as propriedades estarão na mesma ordem em que as adicionei?

mellowsoon
fonte
1
@TJCrowder Você poderia falar um pouco mais sobre por que a resposta aceita não é mais precisa? A pergunta que você vinculou parece se resumir à idéia de que a ordem da propriedade ainda não é garantida por especificação.
precisa saber é o seguinte
2
@ zero298: A resposta aceita para essa pergunta descreve claramente a ordem de propriedades especificada no ES2015 +. Operações legadas ( for-in, Object.keys) não tem que apoiá-lo (oficialmente), mas não é a ordem agora. (Não oficialmente: Firefox, Chrome e Edge seguem a ordem especificada, mesmo no for-in e Object.keys, onde eles não são oficialmente obrigados a: jsfiddle.net/arhbn3k2/1 )
TJ Crowder

Respostas:

474

A ordem de iteração para objetos segue um determinado conjunto de regras desde o ES2015, mas nem sempre segue a ordem de inserção . Simplificando, a ordem de iteração é uma combinação da ordem de inserção para chaves de strings e ordem crescente para chaves semelhantes a números:

// key order: 1, foo, bar
const obj = { "foo": "foo", "1": "1", "bar": "bar" }

Usar uma matriz ou um Mapobjeto pode ser a melhor maneira de conseguir isso. Mapcompartilha algumas semelhanças Objecte garante que as chaves sejam iteradas em ordem de inserção , sem exceção:

As chaves no mapa são ordenadas, enquanto as chaves adicionadas ao objeto não são. Assim, ao iterar sobre ele, um objeto Map retorna chaves em ordem de inserção. (Observe que, no ECMAScript 2015, os objetos de especificação preservam a ordem de criação das chaves de seqüência de caracteres e Symbol; portanto, a travessia de um objeto com, por exemplo, apenas as chaves de seqüência de caracteres produziria chaves em ordem de inserção)

Como uma nota, a ordem das propriedades nos objetos não era garantida antes do ES2015. Definição de um Objeto da ECMAScript Terceira Edição (pdf) :

4.3.3 Objeto

Um objeto é um membro do tipo Objeto. É uma coleção não ordenada de propriedades, cada uma das quais contém um valor primitivo, objeto ou função. Uma função armazenada na propriedade de um objeto é chamada de método.

bpierre
fonte
O comportamento das chaves inteiras não é consistente em todos os navegadores. Alguns navegadores mais antigos repetem as chaves inteiras em ordem de inserção (com as chaves de cadeia) e outras em ordem crescente.
Dave Dopson
1
@DaveDopson - Certo - navegadores obsoletos não seguem a especificação atual, porque não são atualizados.
TJ Crowder
199

SIM (para chaves não inteiras).

A maioria dos navegadores itera as propriedades do objeto como:

  1. Chaves inteiras em ordem crescente (e cadeias de caracteres como "1" que analisam como ints)
  2. Chaves de sequência, em ordem de inserção (o ES2015 garante que este e todos os navegadores estejam em conformidade)
  3. Nomes dos símbolos, em ordem de inserção (o ES2015 garante que este e todos os navegadores estejam em conformidade)

Alguns navegadores mais antigos combinam as categorias 1 e 2, repetindo todas as chaves na ordem de inserção. Se suas chaves puderem ser analisadas como números inteiros, é melhor não confiar em nenhuma ordem de iteração específica.

A ordem de inserção das especificações de idioma atual (desde ES2015) é preservada, exceto no caso de chaves que analisam como números inteiros (por exemplo, "7" ou "99"), em que o comportamento varia entre os navegadores. Por exemplo, o Chrome / V8 não respeita a ordem de inserção quando as chaves são analisadas como numéricas.

Especificações de idioma antigo (antes do ES2015) : a ordem da iteração era tecnicamente indefinida, mas todos os principais navegadores estavam em conformidade com o comportamento do ES2015.

Observe que o comportamento do ES2015 foi um bom exemplo de especificação de idioma sendo conduzida pelo comportamento existente, e não o contrário. Para entender melhor essa mentalidade de compatibilidade com versões anteriores, consulte http://code.google.com/p/v8/issues/detail?id=164 , um bug do Chrome que cobre em detalhes as decisões de design por trás do comportamento da ordem de iteração do Chrome . Por um dos comentários (bastante opinativos) sobre esse relatório de bug:

Os padrões sempre seguem implementações, é daí que o XHR veio, e o Google faz a mesma coisa implementando o Gears e adotando a funcionalidade HTML5 equivalente. A solução certa é que a ECMA incorpore formalmente o comportamento padrão de fato na próxima revisão das especificações.

Dave Dopson
fonte
2
@BenjaminGruenbaum - esse era exatamente o meu ponto. A partir de 2014, todos os principais fornecedores tiveram uma implementação comum e, portanto, os padrões seguirão (em 2015).
Dave Dopson
2
A propósito: React createFragmentAPI já conta com isso ... #
mik01aj 4/16
7
@BenjaminGruenbaum Seu comentário é falso. No ES2015, o pedido é garantido apenas para os métodos selecionados . Veja a resposta do ftor abaixo.
Piotr Dobrogost 7/10
82

A ordem das propriedades em objetos normais é um assunto complexo em Javascript.

Enquanto no ES5 explicitamente nenhum pedido foi especificado, o ES2015 tem um pedido em determinados casos. Dado é o seguinte objeto:

o = Object.create(null, {
  m: {value: function() {}, enumerable: true},
  "2": {value: "2", enumerable: true},
  "b": {value: "b", enumerable: true},
  0: {value: 0, enumerable: true},
  [Symbol()]: {value: "sym", enumerable: true},
  "1": {value: "1", enumerable: true},
  "a": {value: "a", enumerable: true},
});

Isso resulta na seguinte ordem (em certos casos):

Object {
  0: 0,
  1: "1",
  2: "2",
  b: "b",
  a: "a",
  m: function() {},
  Symbol(): "sym"
}
  1. chaves do tipo inteiro em ordem crescente
  2. chaves normais em ordem de inserção
  3. Símbolos em ordem de inserção

Portanto, existem três segmentos que podem alterar a ordem de inserção (como aconteceu no exemplo). E chaves do tipo inteiro não se atêm à ordem de inserção.

A questão é: para quais métodos esse pedido é garantido nas especificações do ES2015?

Os métodos a seguir garantem a ordem mostrada:

  • Object.assign
  • Object.defineProperties
  • Object.getOwnPropertyNames
  • Object.getOwnPropertySymbols
  • Reflect.ownKeys

Os seguintes métodos / loops garantem nenhuma ordem:

  • Object.keys
  • for..in
  • JSON.parse
  • JSON.stringify

Conclusão: Mesmo no ES2015, você não deve confiar na ordem das propriedades dos objetos normais em Javascript. É propenso a erros. Use em Mapvez disso.

evolutionxbox
fonte
Testei aproximadamente a conclusão no nó v8.11 e está correta.
Merlin.ye
1
@BenjaminGruenbaum tem algum pensamento?
evolutionxbox
Object.entries garante a ordem, seguindo a regra do número inteiro
Mojimi
1
+1 em "Mesmo no ES2015, você não deve confiar na ordem das propriedades dos objetos normais em Javascript. É propenso a erros. Use o Map.". Você não pode confiar na ordem da propriedade quando a conformidade é complicada. Como você testa isso para comparar comparativamente com versões anteriores, se você precisa oferecer suporte a navegadores mais antigos?
zero298
1
Existe alguma documentação oficial sobre isso ou uma referência?
Bata em
66

No momento da redação deste artigo, a maioria dos navegadores retornava propriedades na mesma ordem em que foram inseridas, mas não era explicitamente garantido o comportamento, portanto, não deveria ser invocado.

A especificação ECMAScript costumava dizer:

A mecânica e a ordem de enumerar as propriedades ... não estão especificadas.

No entanto, no ES2015 e posterior, chaves não inteiras serão retornadas em ordem de inserção.

Alnitak
fonte
16
O Chrome implementa uma ordem diferente para outros navegadores. Consulte code.google.com/p/v8/issues/detail?id=164
Tim Down
9
O Opera 10.50 e superior, assim como o IE9, correspondem à ordem do Chrome. O Firefox e o Safari são agora uma minoria (e os dois também usam pedidos diferentes para objetos / matrizes).
precisa saber é o seguinte
1
@Veverke não existe explicitamente nenhuma garantia no pedido, portanto, sempre se deve assumir que o pedido é efetivamente aleatório.
Alnitak
1
@Veverke Não, o pedido não é nada assim previsível. É dependente da implementação e está sujeito a alterações a qualquer momento e pode ser alterado (por exemplo) sempre que o navegador se atualizar.
Alnitak
3
Esta resposta é falsa no ES2015.
Benjamin Gruenbaum
42

Toda essa resposta está no contexto de conformidade com as especificações, não no que qualquer mecanismo faz em um momento específico ou historicamente.

Geralmente, não

A questão real é muito vaga.

as propriedades estarão na mesma ordem em que as adicionei

Em que contexto?

A resposta é: depende de vários fatores. Em geral, não .

Às vezes sim

Aqui é onde você pode contar com a ordem das chaves de propriedade para simples Objects:

  • Motor compatível com ES2015
  • Propriedades próprias
  • Object.getOwnPropertyNames(), Reflect.ownKeys(),Object.getOwnPropertySymbols(O)

Em todos os casos, esses métodos incluem chaves de propriedade não enumeráveis ​​e chaves de pedido, conforme especificado por [[OwnPropertyKeys]](veja abaixo). Eles diferem no tipo de valores-chave que incluem ( Stringe / ou Symbol). Nesse contexto, Stringinclui valores inteiros.

Object.getOwnPropertyNames(O)

Retorna Oas Stringpropriedades com chave própria ( nomes de propriedades ).

Reflect.ownKeys(O)

Retorna Oas propriedades próprias Stringe Symbolchave.

Object.getOwnPropertySymbols(O)

Retorna Oas Symbolpropriedades com chave própria .

[[OwnPropertyKeys]]

A ordem é essencialmente: tipo inteiro Stringsna ordem crescente, tipo não inteiro Stringsna ordem de criação, símbolos em ordem de criação. Dependendo de qual função chama isso, alguns desses tipos podem não estar incluídos.

O idioma específico é que as chaves são retornadas na seguinte ordem:

  1. ... cada chave Pde propriedade própria O[do objeto que está sendo iterado] que é um índice inteiro, em ordem crescente de índice numérico

  2. ... cada chave Pde propriedade própria Oé uma String, mas não é um índice inteiro, em ordem de criação de propriedade

  3. ... cada chave Pde propriedade Oé um símbolo, na ordem de criação da propriedade

Map

Se você estiver interessado em mapas ordenados, considere usar o Maptipo introduzido no ES2015 em vez de simples Objects.

JMM
fonte
13

Nos navegadores modernos, você pode usar a Mapestrutura de dados em vez de um objeto.

Desenvolvedor mozilla> Mapa

Um objeto Map pode iterar seus elementos na ordem de inserção ...

Topicus
fonte
7

No ES2015, sim, mas não no que você imagina

A ordem das chaves em um objeto não foi garantida até o ES2015. Foi definido pela implementação.

No entanto, no ES2015 em foi especificado. Como muitas coisas no JavaScript, isso foi feito para fins de compatibilidade e geralmente refletia um padrão não oficial existente entre a maioria dos mecanismos JS (com você sabe quem é uma exceção).

A ordem é definida na especificação, na operação abstrata OrdinaryOwnPropertyKeys , que sustenta todos os métodos de iteração nas próprias chaves de um objeto. Parafraseado, a ordem é a seguinte:

  1. Todos índice inteiro teclas (coisas como "1123", "55", etc.) em ordem numérica ascendente.

  2. Todas as chaves de sequência que não são índices inteiros, em ordem de criação (a mais antiga).

  3. Todas as teclas de símbolo, em ordem de criação (a mais antiga).

É tolice dizer que o pedido não é confiável - é confiável, provavelmente não é o que você deseja, e os navegadores modernos implementam esse pedido corretamente.

Algumas exceções incluem métodos de enumerar chaves herdadas, como o for .. inloop. O for .. inloop não garante a ordem de acordo com a especificação.

GregRos
fonte
7

No ES2015, a ordem das propriedades é garantida para certos métodos que iteram nas propriedades. mas não outros . Infelizmente, os métodos que não garantem um pedido são geralmente os mais usados:

  • Object.keys, Object.values,Object.entries
  • for..in rotações
  • JSON.stringify

Porém, a partir do ES2020, a ordem das propriedades desses métodos anteriormente não confiáveis será garantida pela especificação da iteração da mesma maneira determinística que as outras, devido à proposta final : mecânica for-in .

Assim como nos métodos que têm uma ordem de iteração garantida (like Reflect.ownKeyse Object.getOwnPropertyNames), os métodos anteriormente não especificados também irão iterar na seguinte ordem:

  • Teclas de matriz numérica, em ordem numérica crescente
  • Todas as outras teclas que não sejam de símbolo, em ordem de inserção
  • Teclas de símbolo, em ordem de inserção

Isso é o que praticamente toda implementação já faz (e faz há muitos anos), mas a nova proposta a tornou oficial.

Embora a especificação atual deixe para ... na ordem da iteração " quase totalmente não especificada , os mecanismos reais tendem a ser mais consistentes:"

A falta de especificidade na ECMA-262 não reflete a realidade. Em discussões anteriores, os implementadores observaram que existem algumas restrições no comportamento do for-in que qualquer pessoa que queira executar código na Web precisa seguir.

Como toda implementação já itera previsivelmente as propriedades, ela pode ser inserida na especificação sem quebrar a compatibilidade com versões anteriores.


Existem alguns casos estranhos com os quais as implementações atualmente não concordam e, nesses casos, a ordem resultante continuará não especificada. Para que a ordem da propriedade seja garantida :

Nem o objeto que está sendo iterado nem nada em sua cadeia de protótipos são proxy, matriz digitada, objeto de espaço para nome do módulo ou objeto exótico do host.

Nem o objeto nem qualquer coisa em sua cadeia de protótipos tem seu protótipo alterado durante a iteração.

Nem o objeto nem nada em sua cadeia de protótipos têm uma propriedade excluída durante a iteração.

Nada na cadeia de protótipos do objeto possui uma propriedade adicionada durante a iteração.

Nenhuma propriedade do objeto ou qualquer coisa em sua cadeia de protótipos sofre alteração na enumerabilidade durante a iteração.

Nenhuma propriedade não enumerável sombreia uma propriedade enumerável.

CertainPerformance
fonte
5

Como outros já declararam, você não tem garantia quanto à ordem ao iterar nas propriedades de um objeto. Se você precisar de uma lista ordenada de vários campos, sugeri a criação de uma matriz de objetos.

var myarr = [{somfield1: 'x', somefield2: 'y'},
{somfield1: 'a', somefield2: 'b'},
{somfield1: 'i', somefield2: 'j'}];

Dessa forma, você pode usar um loop for regular e ter a ordem de inserção. Você pode usar o método de classificação Array para classificá-lo em uma nova matriz, se necessário.

Drew Durham
fonte
3

Acabei de descobrir isso da maneira mais difícil.

Usando React with Redux, o contêiner de estado cujas chaves eu quero percorrer para gerar filhos é atualizado sempre que a loja é alterada (conforme os conceitos de imutabilidade do Redux).

Assim, para pegar Object.keys(valueFromStore)eu usei Object.keys(valueFromStore).sort(), de modo que pelo menos agora tenho uma ordem alfabética para as teclas.

kriskate
fonte
-7

Do padrão JSON :

Um objeto é uma coleção não ordenada de zero ou mais pares de nome / valor, em que um nome é uma sequência e um valor é uma sequência, número, booleano, nulo, objeto ou matriz.

(ênfase minha).

Então, não, você não pode garantir o pedido.

Kevin Lacquement
fonte
7
isso é especificado pelo padrão ECMAScript - não pela especificação JSON.
Alnitak
7
@ Alnitak, @Iacqui: JSON apenas tira isso da especificação ECMAScript. Também é especificado para JSON, mas isso realmente não está relacionado à questão.
Pa Elo Ebermann 02/07