The precise meaning of "For each named own enumerable property name P of O"
ES5 section 12.6 (for-in statement) says:
The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified. Properties of the object being enumerated may be deleted during enumeration. If a property that has not yet been visited during enumeration is deleted, then it will not be visited. If new properties are added to the object being enumerated during enumeration, the newly added properties are guaranteed not to be visited in the active enumeration.
ES3 says pretty much the same thing with minor wording tweaks.
Oops, this wasn't really about for-in.
In Object.defineProperties should really be (inerted new step 3 and modified renumbered step 4:
-
If Type(O) is not Object throw a TypeError exception.
-
Let props be ToObject(Properties).
- Let names be an internal list containing names of each enumerable own property of props
-
a. Let descObj be the result of calling the [[Get]] internal method of props with P as the argument. b. Let desc be the result of calling ToPropertyDescriptor with descObj as the argument. c. Call the [[DefineOwnProperty]] internal method of O with arguments P, desc, and true.For element P of names in list order,
-
Return O.
If an implementation defines a specific order of enumeration for the for-in statement, that same enumeration order must be used to order the list in step 3 of this algorithm
On 12.8.09 14:33 , Allen Wirfs-Brock wrote:
ES5 section 12.6 (for-in statement) says:
That's specifically for the for-in statement in the language, not for a term of art used in other locations in the specification. If we want to adopt the definition, I think this should either be noted in 5.2 or described in some sort of prefatory statement in 15.2.3.
Oops, belay that, didn't see the followup email -- modifications in that email look fine.
Allen Wirfs-Brock wrote:
ES5 section 12.6 (for-in statement) says:
The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified. Properties of the object being enumerated may be deleted during enumeration. If a property that has not yet been visited during enumeration is deleted, then it will not be visited. If new properties are added to the object being enumerated during enumeration, the newly added properties are guaranteed not to be visited in the active enumeration.
ES3 says pretty much the same thing with minor wording tweaks.
I'm resurrecting this thread from August because I just noticed that one of the "minor wording tweaks" between the ES3 version and the ES5 version is this one:
ES3: "the newly added properties are not guaranteed to be visited"
ES5: "the newly added properties are guaranteed not to be visited"
The transposition of the two words makes a big difference that doesn't seem like a minor tweak to me, and I can't find a discussion of it in the mailing lists so I wanted to make sure that this was an intentional rather than accidental change.
On Fri, Oct 9, 2009 at 10:35 PM, David Flanagan <david at davidflanagan.com>wrote:
Allen Wirfs-Brock wrote:
ES5 section 12.6 (for-in statement) says:
The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified. Properties of the object being enumerated may be deleted during enumeration. If a property that has not yet been visited during enumeration is deleted, then it will not be visited. If new properties are added to the object being enumerated during enumeration, the newly added properties are guaranteed not to be visited in the active enumeration.
ES3 says pretty much the same thing with minor wording tweaks.
I'm resurrecting this thread from August because I just noticed that one of the "minor wording tweaks" between the ES3 version and the ES5 version is this one:
ES3: "the newly added properties are not guaranteed to be visited"
ES5: "the newly added properties are guaranteed not to be visited"
The transposition of the two words makes a big difference that doesn't seem like a minor tweak to me, and I can't find a discussion of it in the mailing lists so I wanted to make sure that this was an intentional rather than accidental change.
It was intentional. It was discussed at least on the ES3.1 phone calls. Have you looked for a record at < meetings:meetings>?
On Oct 9, 2009, at 10:35 PM, David Flanagan wrote:
Allen Wirfs-Brock wrote:
ES5 section 12.6 (for-in statement) says: The mechanics and order of enumerating the properties (step 6.a in
the first algorithm, step 7.a in the second) is not specified.
Properties of the object being enumerated may be deleted during
enumeration. If a property that has not yet been visited during
enumeration is deleted, then it will not be visited. If new
properties are added to the object being enumerated during
enumeration, the newly added properties are guaranteed not to be
visited in the active enumeration. ES3 says pretty much the same thing with minor wording tweaks.I'm resurrecting this thread from August because I just noticed that
one of the "minor wording tweaks" between the ES3 version and the
ES5 version is this one:ES3: "the newly added properties are not guaranteed to be visited"
ES5: "the newly added properties are guaranteed not to be visited"
The transposition of the two words makes a big difference that
doesn't seem like a minor tweak to me, and I can't find a discussion
of it in the mailing lists so I wanted to make sure that this was an
intentional rather than accidental change.
The spec is ambiguously worded here. Most (all?) implementations do
not enumerate properties added to the directly referenced object after
the loop has started, even though ES1-3 permitted this as
implementation-dependent behavior:
js> o = {p:1,q:2} ({p:1, q:2}) js> for (i in o) {o.r = 3; print(i)}
p q js>
However, prototype objects may gain properties after the loop has
started and before the enumeration has reached the prototype in
question, and so long as the added prototype property is not shadowed,
it will be enumerated by some implementations (Mozilla's at least):
js> function f(){}
js> o = new f ({}) js> o.p = 1, o.q = 2
2 js> for (i in o) { f.prototype.r = 3; print(i); }
p q r js>
(HTML source you can test is here: pastebin.mozilla.org/675782
-- this particular pastebin page will stick around forever.)
The spec's wording shifts subtly from "If new properties are added to
the object being enumerated during enumeration, the newly added
properties are guaranteed not to be visited in the active
enumeration", which is arguably clear enough in context (adding
properties to an object makes "own" properties in JS, and "the
object being enumerated" seems to mean the object that was directly
referenced by the result of evaluating the expression on the right of
'in'), to "[e]numerating the properties of an object includes
enumerating properties of its prototype, and the prototype of the
prototype, and so on, recursively [with shadowing]."
So "added to the object" means both "own" properties and the directly-
referenced object (evaluated from the expression on the right of
'in'), while "enumerating properties of an object" includes prototype
properties if not shadowed.
The old "hasOwnProperty" vs. "in" confusion bites here. The spec
should be more precise, even if context makes the two usages clear
enough for most readers.
The upshot is that the last paragraph of 12.6.4 thus may be taken to
say properties "of an object" include prototype properties, and
therefore the earlier part about added properties may be taken to
apply to added prototype properties too, specifying that they also are
not enumerated.
But in fact added prototype properties are enumerated by some
implementations so long as the enumeration has not reached the
prototype in question yet.
Perhaps this is just something for those implementations to fix to
conform to ES5, but let's discuss, since as David notes there's a lack
of discussion on this point.
To suppress prototype properties added after enumeration starts, the
implementation must in effect take a snapshot of all the enumerable
prototype property names as well as the direct names before the loop
starts, instead of snapshotting the enumerable property names only of
each object along the prototype chain as the loop progresses up the
chain.
In either case shadowing must be handled. Implementations using the
"snapshot each object while traversing once" approach may see
different shadowing effects than implementations that snapshot the
whole chain up front and traverse again during the loop:
function f(){}
o = new f;
o.p = 1, o.q = 2;
f.prototype.r = 3;
a = [];
for (i in o) {
o.r = 4;
a.push(i);
}
alert('[' + a.join(', ') + ']');
(available at pastebin.mozilla.org/675804). This gives
different results in different browsers although the result "[p, q]"
shows what looks to me like a bug in Mozilla's SpiderMonkey
implementation (I didn't test Rhino) where the late addition of o.r
can shadow the prototype property. I'll file it.
Another observation: using two traversals rather than one has higher
inherent cost -- is this a consideration for the spec? I don't know,
but at least it is guaranteed to visit the same objects on each
traversal in the de-jure-standardized language.
However, the writable proto feature (a botch, my fault) is a de-
facto standard, and it complicates things for implementations that
support it. Do they traverse twice and risk the chain being mutated
after the loop starts but before the loop's traversal (as opposed to
the snapshotting one for all enumerable non-shadowed names)? The spec
doesn't have to worry about this but some implementations do for now
(Mozilla's, WebKit's).
And even if there's no writable proto, if host objects can mutate
[[Prototype]] then the possibility of inconsistent traversals arises.
Since the spec is unclear and at least Mozilla's implementation does
one traversal, and therefore shows added prototype properties so long
as they are added after the loop starts but before the enumeration
reaches the prototype in question, possibly this can remain an
unspecified, implementation-dependent behavior -- for now.
Or we can go with the ES5 change. But the trade-offs above need
deliberate discussion.
Comments welcome.
On Oct 10, 2009, at 1:09 PM, Brendan Eich wrote:
In either case shadowing must be handled. Implementations using the
"snapshot each object while traversing once" approach may see
different shadowing effects than implementations that snapshot the
whole chain up front and traverse again during the loop:function f(){} o = new f; o.p = 1, o.q = 2; f.prototype.r = 3; a = []; for (i in o) { o.r = 4; a.push(i); } alert('[' + a.join(', ') + ']');
(available at pastebin.mozilla.org/675804). This gives
different results in different browsers although the result "[p, q]"
shows what looks to me like a bug in Mozilla's SpiderMonkey
implementation (I didn't test Rhino) where the late addition of o.r
can shadow the prototype property. I'll file it.
Filed at
What is the behavior when the body of the loop may have side effects? There are a number of these for the Object.* hooks, but I'm most specifically considering Object.defineProperties, in the case when some getter executed in the body of that loop (also in ToPropertyDescriptor) results in a new property being added, a future property being removed, a property being readded with the same name (does it attempt to define the property twice?) -- all the usual enumeration hazards, but most particularly the last one because of potential weirdness of defining the same property twice with conflicting attributes between the two property descriptors (if the appropriate behavior were specified to trigger that).