Ordem dos elementos em um loop "for (… in…)"

205

O loop "for ... in" no Javascript passa pelas hashtables / elementos na ordem em que são declarados? Existe um navegador que não faça isso em ordem?
O objeto que desejo usar será declarado uma vez e nunca será modificado.

Suponha que eu tenha:

var myObject = { A: "Hello", B: "World" };

E eu ainda os uso em:

for (var item in myObject) alert(item + " : " + myObject[item]);

Posso esperar 'A: "Olá"' sempre antes de 'B: "Mundo"' na maioria dos navegadores decentes?

chakrit
fonte
61
Porque eles só testariam um subconjunto de possíveis navegadores e variantes. Sem mencionar navegadores futuros. É totalmente errado supor que um teste sem falhas fornece qualquer tipo de prova concreta.
James Hughes
6
Duvido que minha capacidade limitada de javascript seja melhor que a multidão de SO. Além do mais, quem sabe que navegador estranho espreita por aí? E você pode ver na resposta que o GChrome tem um bug que não será aparente no meu exemplo simples.
chakrit
possível duplicação de O JavaScript garante a ordem da propriedade do objeto?
21715 Bergi

Respostas:

214

Citando John Resig :

Atualmente, todos os principais navegadores fazem um loop sobre as propriedades de um objeto na ordem em que foram definidos. O Chrome também faz isso, exceto em alguns casos. [...] Esse comportamento é explicitamente deixado indefinido pela especificação ECMAScript. No ECMA-262, seção 12.6.4:

A mecânica de enumerar as propriedades ... depende da implementação.

No entanto, a especificação é bem diferente da implementação. Todas as implementações modernas do ECMAScript iteram pelas propriedades do objeto na ordem em que foram definidas. Por esse motivo, a equipe do Chrome considerou isso um bug e o corrigirá.

Todos os navegadores respeitam a ordem de definição, com exceção do Chrome e Opera, o que ocorre com todos os nomes de propriedades não numéricos. Nesses dois navegadores, as propriedades são colocadas em ordem antes da primeira propriedade não numérica (isso tem a ver com a maneira como eles implementam matrizes). O pedido também é o mesmo Object.keys.

Este exemplo deve deixar claro o que acontece:

var obj = {
  "first":"first",
  "2":"2",
  "34":"34",
  "1":"1",
  "second":"second"
};
for (var i in obj) { console.log(i); };
// Order listed:
// "1"
// "2"
// "34"
// "first"
// "second"

Os detalhes técnicos disso são menos importantes do que o fato de que isso pode mudar a qualquer momento. Não confie nas coisas que ficam assim.

Resumindo: use uma matriz se a ordem for importante para você.

Borgar
fonte
3
Na verdade não. O Chrome não implementa a mesma ordem que outros navegadores: code.google.com/p/v8/issues/detail?id=164
Tim Down
20
"Use uma matriz se a ordem for importante para você": E quando você estiver usando JSON?
HM2K
11
@ HM2K, mesma coisa, spec diz "Um objeto é uma coleção não ordenada de zero ou mais pares de nome / valor". JSON não é JavaScript: um servidor não precisa (e provavelmente não vai) respeitar a ordem em que você o entrega.
Borgar
7
O Firefox, desde sua versão 21, parece não respeitar mais o pedido de inserção.
Doc Davluz
3
Esta resposta é falsa no ES2015.
Benjamin Gruenbaum
54

Colidindo com isso um ano depois ...

É 2012 e os principais navegadores ainda diferem:

function lineate(obj){
    var arr = [], i;
    for (i in obj) arr.push([i,obj[i]].join(':'));
    console.log(arr);
}
var obj = { a:1, b:2, c:3, "123":'xyz' };
/* log1 */  lineate(obj);
obj.a = 4;
/* log2 */  lineate(obj);
delete obj.a;
obj.a = 4;
/* log3 */  lineate(obj);

essência ou teste no navegador atual

Safari 5, Firefox 14

["a:1", "b:2", "c:3", "123:xyz"]
["a:4", "b:2", "c:3", "123:xyz"]
["b:2", "c:3", "123:xyz", "a:4"]

Chrome 21, Opera 12, nó 0.6, Firefox 27

["123:xyz", "a:1", "b:2", "c:3"]
["123:xyz", "a:4", "b:2", "c:3"]
["123:xyz", "b:2", "c:3", "a:4"]

IE9

[123:xyz,a:1,b:2,c:3] 
[123:xyz,a:4,b:2,c:3] 
[123:xyz,a:4,b:2,c:3] 
dvdrtrgn
fonte
16
qual é o problema aqui? Exatamente como a especificação diz, é uma lista não ordenada de pares chave / val.
nickf 12/02
5
nickf: as implementações estão certas na medida em que fazem o que a especificação diz. Eu acho que todo mundo concorda que a especificação do javascript é .. bem, eu não quero usar a palavra "errado", que tal "extremamente estúpido e irritante"? : P
encaixotado em
10
@ boxed: Pense nos objetos como um mapa de hash (tabela / qualquer que seja). Na maioria das linguagens (Java, Python, etc), esses tipos de estruturas de dados não são classificados. Portanto, não é surpreendente que este seja o mesmo caso em JavaScript e certamente não torna a especificação errada ou estúpida.
Felix Kling #
9
Sinceramente, não vejo o objetivo desta resposta (desculpe). A ordem na qual as propriedades são iteradas é um detalhe da implementação, e os navegadores usam mecanismos JavaScript diferentes, portanto, espera-se que a ordem seja diferente. Isso não vai mudar.
precisa
2
Em uma implementação de última geração, você receberá propriedades inteiras em ordem numérica devido ao respaldo em uma matriz real, e as propriedades nomeadas serão recebidas em ordem de inserção devido à formulação de classes / formas ocultas. O que pode variar é se eles listam as propriedades inteiras primeiro ou as propriedades nomeadas primeiro. A adição de deleteé interessante porque, pelo menos na V8, faz com que o objeto seja copiado instantaneamente por uma tabela de hash. No entanto, a tabela de hash na V8 armazena em ordem de inserção. A maioria resultado interessante aqui é IE, gostaria de saber que tipo de feiúra que fazer para fazer aquilo ...
Esailija
28

Na especificação de idioma ECMAScript , seção 12.6.4 (no for .. inloop):

A mecânica de enumerar as propriedades depende da implementação. A ordem da enumeração é definida pelo objeto.

E seção 4.3.3 (definição de "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.

Acho que isso significa que você não pode confiar nas propriedades que estão sendo enumeradas em uma ordem consistente nas implementações de JavaScript. (Seria um estilo ruim, de qualquer maneira, contar com detalhes específicos da implementação de uma linguagem.)

Se você deseja que seu pedido seja definido, será necessário implementar algo que o defina, como uma matriz de chaves que você classifica antes de acessar o objeto com ele.

Tomalak
fonte
10

Os elementos de um objeto que enumera / são as propriedades que não possuem o sinalizador DontEnum definido. O padrão ECMAScript, também conhecido como Javascript, diz explicitamente que "Um Objeto é uma coleção não ordenada de propriedades" (consulte http://www.mozilla.org/js/language/E262-3.pdf, seção 8.6).

Não será compatível com os padrões (ou seja, seguro) assumir que todas as implementações de Javascript serão enumeradas em ordem de declaração.

Adam Wright
fonte
2
é por isso que ele está fazendo a pergunta, em vez de apenas assumindo: p
Jamie Pate
5

A ordem de iteração também é confundida com relação à exclusão de propriedades, mas neste caso apenas com o IE.

var obj = {};
obj.a = 'a';
obj.b = 'b';
obj.c = 'c';

// IE allows the value to be deleted...
delete obj.b;

// ...but remembers the old position if it is added back later
obj.b = 'bb';
for (var p in obj) {
    alert(obj[p]); // in IE, will be a, bb, then c;
                   // not a, c, then bb as for FF/Chrome/Opera/Safari
}

O desejo de alterar as especificações para corrigir a ordem da iteração parece ser um desejo bastante popular entre os desenvolvedores se a discussão em http://code.google.com/p/v8/issues/detail?id=164 for alguma indicação.

Brett Zamir
fonte
3

no IE6, o pedido não é garantido.


fonte
2

O pedido não pode ser confiável. O Opera e o Chrome retornam a lista de propriedades sem ordem.

<script type="text/javascript">
var username = {"14719":"A","648":"B","15185":"C"};

for (var i in username) {
  window.alert(i + ' => ' + username[i]);
}
</script>

O código acima mostra B, A, C no Opera e C, A, B no Chrome.

Kouber Saparev
fonte
1

Isso não responde à pergunta em si, mas oferece uma solução para o problema básico.

Supondo que você não possa confiar na ordem de preservação, por que não usar uma matriz de objetos com chave e valor como propriedades?

var myArray = [
    {
        'key'   : 'key1'
        'value' : 0
    },
    {
        'key'   : 'key2',
        'value' : 1
    } // ...
];

Agora, cabe a você garantir que as chaves sejam únicas (supondo que isso também seja importante para você. Além disso, as alterações de endereçamento direto e para (... em ...) agora retornam índices como 'chaves'.

> console.log(myArray[0].key);
key1

> for (let index in myArray) {console.log(myArray[index].value);}
0
1
Consulte a caneta para (... em ...) endereçar em ordem por JDQ ( @JDQ ) no CodePen .

JDQ
fonte