Est-ce que JavaScript garantit la propriété des objets?

647

Si je crée un objet comme celui-ci:

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

L'objet résultant ressemblera-t-il toujours à ceci?

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

Autrement dit, les propriétés seront-elles dans le même ordre que je les ai ajoutées?

mellowsoon
la source
1
@TJCrowder Pourriez-vous expliquer un peu plus en détail pourquoi la réponse acceptée n'est plus exacte? La question que vous avez liée semble se résumer à l'idée que l'ordre des propriétés n'est toujours pas garanti par spécification.
zero298
2
@ zero298: La réponse acceptée à cette question décrit clairement l' ordre de propriété spécifié à partir de ES2015 +. Les opérations héritées ( for-in, Object.keys) n'ont pas à le supporter (officiellement), mais il y a de l' ordre maintenant. (Officiellement: Firefox, Chrome et Edge suivent tous l'ordre spécifié même dans for-in et Object.keys, où ils ne sont pas officiellement tenus de: jsfiddle.net/arhbn3k2/1 )
TJ Crowder

Réponses:

474

L'ordre d'itération des objets suit un certain ensemble de règles depuis ES2015, mais il ne suit pas (toujours) l'ordre d'insertion . Autrement dit, l'ordre d'itération est une combinaison de l'ordre d'insertion pour les clés de chaînes et de l'ordre croissant pour les clés de type numérique:

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

L'utilisation d'un tableau ou d'un Mapobjet peut être un meilleur moyen d'y parvenir. Mappartage certaines similitudes avec Objectet garantit les clés à itérer par ordre d'insertion , sans exception:

Les clés de la carte sont ordonnées tandis que les clés ajoutées à l'objet ne le sont pas. Ainsi, lors de son itération, un objet Map renvoie les clés par ordre d'insertion. (Notez que dans ECMAScript 2015, les objets spec conservent l'ordre de création pour les clés de chaîne et de symbole, donc la traversée d'un objet avec uniquement des clés de chaîne produirait des clés dans l'ordre d'insertion)

Remarque: l'ordre des propriétés dans les objets n'était pas du tout garanti avant ES2015. Définition d'un objet à partir d' ECMAScript troisième édition (pdf) :

4.3.3 Objet

Un objet est membre du type Object. Il s'agit d'une collection non ordonnée de propriétés dont chacune contient une valeur, un objet ou une fonction primitive. Une fonction stockée dans une propriété d'un objet est appelée méthode.

bpierre
la source
Le comportement des clés entières n'est pas cohérent dans tous les navigateurs. Certains navigateurs plus anciens parcourent les clés entières dans l'ordre d'insertion (avec les clés de chaîne) et d'autres dans l'ordre croissant.
Dave Dopson
1
@DaveDopson - Droite - les navigateurs obsolètes ne suivent pas la spécification actuelle, car ils ne sont pas mis à jour.
TJ Crowder
199

OUI (pour les clés non entières).

La plupart des navigateurs parcourent les propriétés des objets comme suit:

  1. Clés entières dans l'ordre croissant (et chaînes comme "1" qui analysent comme des entiers)
  2. Clés de chaîne, dans l'ordre d'insertion (ES2015 garantit cela et tous les navigateurs sont conformes)
  3. Noms des symboles, dans l'ordre d'insertion (ES2015 garantit cela et tous les navigateurs sont conformes)

Certains navigateurs plus anciens combinent les catégories # 1 et # 2, itérant toutes les clés dans l'ordre d'insertion. Si vos clés peuvent être analysées sous forme d'entiers, il est préférable de ne pas s'appuyer sur un ordre d'itération spécifique.

L' ordre d'insertion de la spécification de langue actuelle (depuis ES2015) est conservé, sauf dans le cas des clés qui analysent en tant qu'entiers (par exemple, «7» ou «99»), où le comportement varie d'un navigateur à l'autre. Par exemple, Chrome / V8 ne respecte pas l'ordre d'insertion lorsque les touches sont analysées en numérique.

Ancienne spécification de langue (avant ES2015) : L'ordre d'itération était techniquement indéfini, mais tous les principaux navigateurs respectaient le comportement ES2015.

Notez que le comportement ES2015 était un bon exemple de la spécification de langue entraînée par le comportement existant, et non l'inverse. Pour mieux comprendre cet état d'esprit de compatibilité descendante, consultez http://code.google.com/p/v8/issues/detail?id=164 , un bogue Chrome qui couvre en détail les décisions de conception derrière le comportement de l'ordre d'itération de Chrome. . Selon l'un des commentaires (plutôt avisés) sur ce rapport de bogue:

Les normes suivent toujours les implémentations, c'est de là que vient XHR, et Google fait la même chose en implémentant Gears puis en embrassant des fonctionnalités HTML5 équivalentes. La bonne solution consiste à faire en sorte que l'ECMA incorpore formellement le comportement standard de facto dans la prochaine révision de la spécification.

Dave Dopson
la source
2
@BenjaminGruenbaum - c'était exactement ce que je voulais dire. À partir de 2014, tous les principaux fournisseurs avaient une mise en œuvre commune et, par conséquent, les normes suivront éventuellement (c'est-à-dire en 2015).
Dave Dopson
2
Soit dit en passant: l' API ReactcreateFragment repose déjà sur cela ... 🤔
mik01aj
7
@BenjaminGruenbaum Votre commentaire est faux. Dans ES2015, la commande n'est garantie que pour les méthodes sélectionnées . Voir la réponse de ftor ci-dessous.
Piotr Dobrogost
84

L'ordre des propriétés dans les objets normaux est un sujet complexe en Javascript.

Alors que dans ES5 explicitement aucune commande n'a été spécifiée, ES2015 a une commande dans certains cas. Étant donné l'objet suivant:

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},
});

Il en résulte l'ordre suivant (dans certains cas):

Object {
  0: 0,
  1: "1",
  2: "2",
  b: "b",
  a: "a",
  m: function() {},
  Symbol(): "sym"
}
  1. clés de type entier dans l'ordre croissant
  2. clés normales dans l'ordre d'insertion
  3. Symboles dans l'ordre d'insertion

Ainsi, il y a trois segments, qui peuvent modifier l'ordre d'insertion (comme cela s'est produit dans l'exemple). Et les clés de type entier ne respectent pas du tout l'ordre d'insertion.

La question est de savoir pour quelles méthodes cette commande est garantie dans la spécification ES2015.

Les méthodes suivantes garantissent l'ordre indiqué:

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

Les méthodes / boucles suivantes garantissent aucun ordre du tout:

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

Conclusion: Même dans ES2015, vous ne devez pas vous fier à l'ordre des propriétés des objets normaux en Javascript. Il est sujet aux erreurs. Utilisez Mapplutôt.

evolutionxbox
la source
J'ai approximativement testé la conclusion dans le nœud v8.11 et elle est correcte.
merlin.ye
1
@BenjaminGruenbaum des pensées?
evolutionxbox
Object.entries garantit l'ordre, suivant la règle entière
Mojimi
1
+1 pour "Même dans ES2015, vous ne devriez pas vous fier à l'ordre des propriétés des objets normaux en Javascript. Il est sujet à des erreurs. Utilisez plutôt Map.". Vous ne pouvez pas vous fier à l'ordre des biens lorsque la conformité est alambiquée. Comment pouvez-vous même tester cela pour la comparabilité en amont si vous devez prendre en charge des navigateurs plus anciens?
zero298
1
Existe-t-il une documentation officielle à ce sujet ou une référence?
Battre le
66

Au moment de la rédaction de ce document, la plupart des navigateurs renvoyaient des propriétés dans le même ordre que celui où elles avaient été insérées, mais ce comportement n'était explicitement pas garanti et n'aurait donc pas dû être invoqué.

La spécification ECMAScript disait:

La mécanique et l'ordre d'énumération des propriétés ... n'est pas spécifié.

Cependant, dans ES2015 et les versions ultérieures, les clés non entières seront renvoyées dans l'ordre d'insertion.

Alnitak
la source
16
Chrome implémente un ordre différent des autres navigateurs. Voir code.google.com/p/v8/issues/detail?id=164
Tim Down du
9
Opera 10.50 et supérieur, ainsi que IE9, correspondent à la commande de Chrome. Firefox et Safari sont maintenant une minorité (et les deux utilisent également des ordres différents pour les objets / tableaux).
gsnedders
1
@Veverke il n'y a explicitement aucune garantie sur la commande, donc il faut toujours supposer que la commande est effectivement aléatoire.
Alnitak
1
@Veverke Non, la commande n'a rien de prévisible. Il dépend de l'implémentation et peut changer à tout moment, et peut changer (par exemple) chaque fois que votre navigateur se met à jour.
Alnitak
3
Cette réponse est fausse dans ES2015.
Benjamin Gruenbaum
42

Toute cette réponse se situe dans le contexte de la conformité aux spécifications, et non à ce que fait un moteur à un moment donné ou historiquement.

En règle générale, non

La vraie question est très vague.

les propriétés seront-elles dans le même ordre que je les ai ajoutées

Dans quel contexte?

La réponse est: cela dépend d'un certain nombre de facteurs. En général, non .

Parfois oui

Voici où vous pouvez compter sur l'ordre des clés de propriété pour plain Objects:

  • Moteur conforme ES2015
  • Propres propriétés
  • Object.getOwnPropertyNames(), Reflect.ownKeys(),Object.getOwnPropertySymbols(O)

Dans tous les cas, ces méthodes incluent des clés de propriété non énumérables et des clés de commande comme spécifié par [[OwnPropertyKeys]](voir ci-dessous). Ils diffèrent par le type de valeurs clés qu'ils incluent ( Stringet / ou Symbol). Dans ce contexte, Stringcomprend des valeurs entières.

Object.getOwnPropertyNames(O)

Renvoie Oles propriétés à Stringclé propre ( noms de propriété ).

Reflect.ownKeys(O)

Renvoie Oles propriétés propres Stringet Symbolsaisies.

Object.getOwnPropertySymbols(O)

Renvoie Oles Symbolpropriétés propres de .

[[OwnPropertyKeys]]

L'ordre est essentiellement: de type entier Stringsdans l'ordre croissant, non de type entier Stringsdans l'ordre de création, Symboles dans l'ordre de création. Selon la fonction qui l'invoque, certains de ces types peuvent ne pas être inclus.

La langue spécifique est que les clés sont retournées dans l'ordre suivant:

  1. ... chaque propre clé Pde propriété de O[l'objet en cours d'itération] qui est un index entier, dans l'ordre d'index numérique croissant

  2. ... chaque clé Pde propriété propre Oest une chaîne mais n'est pas un index entier, dans l'ordre de création de propriété

  3. ... chaque propre clé Pde propriété Oqui est un symbole, dans l'ordre de création de propriété

Map

Si vous êtes intéressé par les cartes ordonnées, vous devriez envisager d'utiliser le Maptype introduit dans ES2015 au lieu de plain Objects.

JMM
la source
13

Dans les navigateurs modernes, vous pouvez utiliser la Mapstructure de données au lieu d'un objet.

Développeur mozilla> Carte

Un objet Map peut itérer ses éléments dans l'ordre d'insertion ...

Topicus
la source
7

Dans ES2015, c'est le cas, mais pas à ce que vous pourriez penser

L'ordre des clés dans un objet n'était pas garanti avant ES2015. Il était défini par l'implémentation.

Cependant, dans ES2015 en a été spécifié. Comme beaucoup de choses en JavaScript, cela a été fait à des fins de compatibilité et reflète généralement une norme non officielle existante parmi la plupart des moteurs JS (avec vous-savez-qui étant une exception).

L'ordre est défini dans la spécification, sous l'opération abstraite OrdinaryOwnPropertyKeys , qui sous-tend toutes les méthodes d'itération sur les propres clés d'un objet. Paraphrasé, l'ordonnance est la suivante:

  1. Tous les index entier clés ( des choses comme "1123", "55", etc.) dans l' ordre numérique croissant.

  2. Toutes les clés de chaîne qui ne sont pas des indices entiers, par ordre de création (du plus ancien au plus ancien).

  3. Toutes les touches de symboles, par ordre de création (du plus ancien au plus ancien).

Il est idiot de dire que la commande n'est pas fiable - elle est fiable, ce n'est probablement pas ce que vous voulez, et les navigateurs modernes implémentent correctement cette commande.

Certaines exceptions incluent les méthodes d'énumération des clés héritées, telles que la for .. inboucle. La for .. inboucle ne garantit pas l'ordre selon les spécifications.

GregRos
la source
7

Depuis ES2015, l'ordre des propriétés est garanti pour certaines méthodes qui itèrent sur les propriétés. mais pas les autres . Malheureusement, les méthodes dont la commande n'est pas garantie sont généralement les plus utilisées:

  • Object.keys, Object.values,Object.entries
  • for..in boucles
  • JSON.stringify

Mais, à partir d'ES2020, l'ordre des propriétés pour ces méthodes auparavant non fiables sera garanti par la spécification à réitérer de la même manière déterministe que les autres, en raison de la proposition finale : la mécanique for-in .

Tout comme avec les méthodes qui ont un ordre d'itération garanti (comme Reflect.ownKeyset Object.getOwnPropertyNames), les méthodes non spécifiées précédemment seront également itérées dans l'ordre suivant:

  • Touches de tableau numérique, dans l'ordre numérique croissant
  • Toutes les autres clés non-Symbol, dans l'ordre d'insertion
  • Touches de symboles, dans l'ordre d'insertion

C'est déjà ce que font pratiquement toutes les mises en œuvre (et ce depuis de nombreuses années), mais la nouvelle proposition l'a officialisée.

Bien que la spécification actuelle laisse pour ... dans l'ordre d'itération " presque totalement non spécifié , les moteurs réels ont tendance à être plus cohérents:"

Le manque de spécificité de l'ECMA-262 ne reflète pas la réalité. Dans une discussion qui remonte à plusieurs années, les implémenteurs ont observé qu'il existe certaines contraintes sur le comportement de for-in que toute personne souhaitant exécuter du code sur le Web doit suivre.

Étant donné que chaque implémentation itère déjà de manière prévisible sur les propriétés, elle peut être ajoutée à la spécification sans rompre la compatibilité descendante.


Il y a quelques cas étranges sur lesquels les implémentations ne sont pas d' accord actuellement , et dans de tels cas, l'ordre résultant ne sera pas spécifié. Pour que la propriété soit garantie :

Ni l'objet en cours d'itération, ni quoi que ce soit dans sa chaîne de prototypes n'est un proxy, un tableau typé, un objet d'espace de noms de module ou un objet exotique hôte.

Ni l'objet ni rien dans sa chaîne de prototypes n'a son changement de prototype pendant l'itération.

Ni l'objet ni quoi que ce soit dans sa chaîne de prototype n'a une propriété supprimée lors de l'itération.

Rien dans la chaîne de prototype de l'objet n'a de propriété ajoutée pendant l'itération.

Aucune propriété de l'objet ou quoi que ce soit dans sa chaîne de prototype n'a son changement d'énumération pendant l'itération.

Aucune propriété non énumérable n'observe une propriété énumérable.

CertainPerformance
la source
5

Comme d'autres l'ont dit, vous n'avez aucune garantie quant à l'ordre lorsque vous parcourez les propriétés d'un objet. Si vous avez besoin d'une liste ordonnée de plusieurs champs, j'ai suggéré de créer un tableau d'objets.

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

De cette façon, vous pouvez utiliser une boucle for régulière et avoir l'ordre d'insertion. Vous pouvez ensuite utiliser la méthode de tri Array pour trier cela dans un nouveau tableau si nécessaire.

Drew Durham
la source
3

Je viens de découvrir cela à la dure.

En utilisant React avec Redux, le conteneur d'état dont je veux parcourir les clés afin de générer des enfants est actualisé à chaque changement de magasin (selon les concepts d'immuabilité de Redux).

Ainsi, pour prendre Object.keys(valueFromStore)j'ai utilisé Object.keys(valueFromStore).sort(), de sorte que j'ai au moins maintenant un ordre alphabétique pour les touches.

kriskate
la source
-7

De la norme JSON :

Un objet est une collection non ordonnée de zéro ou plusieurs paires nom / valeur, où un nom est une chaîne et une valeur est une chaîne, un nombre, un booléen, une valeur nulle, un objet ou un tableau.

(c'est moi qui souligne).

Donc, non, vous ne pouvez pas garantir la commande.

Kevin Lacquement
la source
7
cela est spécifié par la norme ECMAScript - pas par la spécification JSON.
Alnitak
7
@Alnitak, @Iacqui: JSON ne prend cela que de la spécification ECMAScript. Il est également spécifié pour JSON, mais cela n'a pas vraiment de rapport avec la question.
Paŭlo Ebermann