Does ES6 introduce a well-defined order of enumeration for object properties?

Note: As of ES2020, even older operations like for-in and Object.keys are required to follow property order. That doesn’t change the fact that using property order for fundamental program logic probably isn’t a good idea, since the order for non-integer-index properties depends on when the properties were created.

Answer for ES2015-ES2019:

For for-in, Object.keys, and JSON.stringify: No.

For some other operations: Yes, usually.

While ES6 / ES2015 adds property order, it does not require for-in, Object.keys, or JSON.stringify to follow that order, due to legacy compatibility concerns.

for-in loops iterate according to [[Enumerate]], which is defined as (emphasis mine):

When the [[Enumerate]] internal method of O is called the following
steps are taken:

Return an Iterator object ( whose next method iterates
over all the String-valued keys of enumerable properties of O. The
Iterator object must inherit from %IteratorPrototype% (25.1.2).
The mechanics and order of enumerating the properties is not
but must conform to the rules specified below [1].

ES7 / ES2016 removes the [[Enumerate]] internal method and instead uses the abstract operation EnumerateObjectProperties, but just like [[Enumerate]] it doesn’t specify any order.

And also see this quote from Object.keys:

If an implementation defines a specific order of enumeration for the
for-in statement, […]

That means implementations are NOT required to define a specific order of enumeration. This has been confirmed by Allen Wirfs-Brock, Project Editor of the ECMAScript 2015 Language Specification, in a post made after the specification was complete.

Other operations, like Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.defineProperties, and Reflect.ownKeys do follow the following order for ordinary objects:

  1. Integer indices (if applicable), in ascending order.
  2. Other string keys (if applicable), in property creation order.
  3. Symbol keys (if applicable), in property creation order.

This behavior is defined in the [[OwnPropertyKeys]] internal method. But certain exotic objects define that internal method slightly differently. For example, a Proxy’s ownKeys trap may return an array in any order:

console.log(Reflect.ownKeys(new Proxy({}, {
  ownKeys: () => ['3','1','2']
}))); // ['3','1','2'], the integer indices are not sorted!

[1] Below it says:

[[Enumerate]] must obtain the own property keys of the target object
as if by calling its [[OwnPropertyKeys]] internal method.

And the order of [[OwnPropertyKeys]] is well-defined. But don’t let that confuse you: that “as if” only means “the same properties”, not “the same order”.

This can be seen in EnumerableOwnNames, which uses [[OwnPropertyKeys]] to get the properties, and then it orders them

in the same relative order as would be produced by the Iterator that
would be returned if the [[Enumerate]] internal method was invoked

If [[Enumerate]] were required to iterate with the same order as [[OwnPropertyKeys]], there wouldn’t be any need to reorder.

Leave a Comment