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: http://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 http://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.

/be
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to