This proposal has been accepted and merged into the main specification. This repository exists only for historical interest.
Specifying for-in enumeration order
(Partially.)
ECMA-262 leaves the order of for (a in b) ...
almost totally unspecified, but real engines tend to be consistent in at least some cases. Furthermore, over the years implementations have observed that anyone who wants to run code on the web needs to follow some constraints not captured by the spec.
This is a stage 4 (finished) proposal to begin fixing that.
Background
Historical efforts to get consensus on a complete specification of the order of for-in have repeatedly failed, in part because all engines have their own idiosyncratic implementations which are the result of a great deal of work and which they don't really want to revisit.
See the exploration directory for background and test cases from before this was a concrete proposal.
A conservative underapproximation of interop semantics
From this list of interop semantics we can derive a conservative underapproximation of cases where engines already agree, which I believe covers the most common common cases. Specifically:
- Neither the object being iterated nor anything in its prototype chain is a proxy, typed array, module namespace object, or host exotic object.
- Neither the object nor anything in its prototype chain has its prototype change during iteration.
- Neither the object nor anything in its prototype chain has a property deleted during iteration.
- Nothing in the object's prototype chain has a property added during iteration.
- No property of the object or anything in its prototype chain has its enumerability change during iteration.
- No non-enumerable property shadows an enumerable one.
All but the last are fairly easy to specify in prose; the last is somewhat harder. As far as I know JavaScriptCore is the only engine which will output anything in this case, because of this longstanding bug, so I am hopeful that point can be discarded.
Effects
There are a variety of APIs which make a property enumeration order observable. for-in
is the most complex, because it, uniquely, enumerates properties from the prototype chain as well. The remaining can be split into those which use the same order as for-in
does for own properties, which are affected by this proposal, and those which use the same order as Reflect.ownKeys
, which are not.
for-in
-ordered APIs
The following APIs use EnumerableOwnPropertyNames
, which requires that its results be ordered "in the same relative order as would be produced by the Iterator that would be returned if the EnumerateObjectProperties internal method were invoked with [the object in question]". EnumerateObjectProperties
is the spec-internal method which is used by for-in
, and is the part of the spec which this proposal is concerned with improving.
Object.keys
Object.values
Object.entries
JSON.parse
viaInternalizeJSONProperty
JSON.stringify
viaSerializeJSONObject
The other-effects directory contains tests demonstrating simple examples of how each of these are observable. All major engines already agree in all of these cases.
Because all of the objects produced by JSON.parse
are within the interop semantics, it will be fully specified after this proposal. The others can all be passed exotic arguments and so will not.
Reflect.ownKeys
-ordered APIs
The following APIs invoke the [[OwnPropertyKeys]]
internal method directly, whose behavior is fully specified. They are therefore not affected by this proposal.
Reflect.ownKeys
Object.getOwnPropertyNames
(viaGetOwnPropertyKeys
)Object.getOwnPropertySymbols
(viaGetOwnPropertyKeys
)Object.assign
Object.create
(viaObjectDefineProperties
)Object.defineProperties
viaObjectDefineProperties
Object.getOwnPropertyDescriptors
Object.freeze
(viaSetIntegrityLevel
)Object.seal
(viaSetIntegrityLevel
)Object.isFrozen
(viaTestIntegrityLevel
)Object.isSealed
(viaTestIntegrityLevel
)- object spread (via
CopyDataProperties
) - object rest in both assignment and binding position (via
CopyDataProperties
)
Spec text
See candidate spec text. This does not yet capture the "no non-enumerable property shadows an enumerable one" constraint above, because I am having trouble figuring out how to say that.