I don't think it is valid to use Object.keys as part of your definition of
for-in because that leaves you with a circular definition as the specification
for Object.keys says it orders the keys in the same order used by for-in.
Ignoring algorithms for the moment and simply looking at semantics, I don't see
how your interpretation makes any sense at all. The semantics of prototype
inheritance creates the illusion that inherited properties are part of the
inheriting object unless they are explicitly over-ridden by an own property.
The semantics you describe break this illusion by essentially allowing the
[[enumerable]] attribute of an inherited property to over-ride the
[[enumerable]] attribute of a same-named own property. This is essentially the
same mistake that the JScript has historically made with ES1-3 when it allowed
an inherited dontenum attribute to suppress the enumeration of a like-name own
property. I think there is universal agreement that JScript was wrong in this
regard. What you are proposing is equally wrong but with the boolean value of
the attribute reversed.
I believe that the correct list of enumerated names for for-in (in the absence
of side-effects that modify properties or property attributes) is:
function getEnumerableNames(obj) {
var encounteredKeys= Object.create(null);
var result = [];
while (obj) {
Object.getOwnPropertyNames(obj).forEach(function(n) {
if (!(n in encounteredKeys)) {
encounteredKeys[n]=true;
if (Object.getOwnPropertyDescriptor(obj,n).enumerable)
result.push(n);
}
}
obj=Object.getPrototypeOf(obj);
}
return result;
}
This is also the list of names that Object.keys should produce. The actual
order of the names in the result of the above function is not specified by ES5.
The prose of 12.6.4 (but not the algorithm) concerning deleted properties
means that side-effects that delete properties should be handled as if for-in
was then implemented as:
getEnumerableNames(obj).forEach(function(n) {if (n in obj)
execute-for-in-body-with-n});
But note that since the order is undefined, if the body does any deletes there
is no guarantee whether you will or won't see a key that is subject to
deletion. The only guarantee is that if all occurrences of a particular
property name (there may be multiple along the prototype chain) are deleted
before processing that name, then the for-in body will not be execute for that
name.
Things are even more unspecified if properties are added as side-effects or if
enumerable attribute values are dynamically changed.
However, I believe the most important use case is the one where there are no
evil side-effects in the for-in body. For that case the set of property names
for which the for-in body is executed is supposed to be the set generated by
the above getEnumerableNames function.
Allen
From: Oliver Hunt [mailto:[email protected]]
Sent: Friday, May 07, 2010 10:40 AM
To: Allen Wirfs-Brock
Cc: Mark S. Miller; [email protected]; es-discuss; Mike Stay
Subject: Re: Yet more ambiguities in property enumeration
On May 7, 2010, at 10:22 AM, Allen Wirfs-Brock wrote:
While 12.6.4 is does not explicitly talk about this case I think it implicitly
covers it.
Line 6.a (of the first algorithm, 7.a of the second) says "Let P be the name of
the next property of obj whose [[Enumerable]] property is true".
I don't believe that there is any other operation or function in ES5 that
when doing a property access by-passes a own property based upon some condition
and instead accesses as shadowed inherited property. Given that it's pretty
clear that the quoted sentence should be read as if it said "Let P be the name
of the next property accessible via the [[Get]] internal method of obj whose
[[Enumerable]] property is true"
Also note that the final paragraph says "a property of a prototype is not
enumerated if it is "shadowed" because some previous object in the prototype
chain has a property with the same name". Note this just says "shadowed"
which I believe (I probably wrote it :-) show be interpreted in the accessible
via [[Get]] sense. In particular, it does not say something like 'has the same
name of a enumerated property of some previous object in the prototype chain"
Given these two points of the specification, I think it takes some pretty
creative interpretation to arrive at the Webkit/V8 interpretation. I suspect,
that their behavior is probably an implementation artifact rather than
something intentional. I can enter the more explicit formulation of (6.a/7.a)
into the errata if there is a consensus that it is really needed, but I think
we are also fine without it.
The JSC implementation is deliberate (and i believe brendan has said that SM
will be moving to match it), the interpretation is that enumeration is a
breadth first search of all enumerable properties of an object that are present
at the start of iteration, in the order of insertion, skipping any properties
that subsequently disappear from the object.
The algorithm (as implemented in JSC and V8) for 'for (lhs in object) S' is
essentially:
names = enumerablePropertyNames(object)
for (var i = 0; i < names.length; i++) {
if (!names[i] in object)
continue;
lhs = names[i];
S
}
with
function enumerablePropertyNames(object) {
var result = [];
while (object) {
var keys = Object.keys(object);
for (var i = 0; i < keys.length; i++) {
if (result.indexOf(keys[i]) != -1)
continue;
result.push(keys[i]);
}
}
return result;
}
I'm 99% sure this code is correct, but i just typed it into Mail so there maybe
typos
--Oliver
Allen
From: [email protected]<mailto:[email protected]>
[mailto:[email protected]] On Behalf Of Mark S. Miller
Sent: Friday, April 23, 2010 9:01 AM
To: [email protected]<mailto:[email protected]>; es-discuss
Cc: Mike Stay
Subject: Yet more ambiguities in property enumeration
Mike Stay (cc'ed) noticed the following ambiguity:
What should the following print?
var base = {x:8};
function showProps(obj) {
var result = [];
for (var k in obj) {
result.push(k, ': ', ''+obj[k], '\n');
}
return result.join('');
}
var derived = Object.create(base, {x: {value: 9, enumerable: false}});
showProps(derived);
Of current ES5 implementations in progress,
* TraceMonkey and SES5/3 (Mike Stay's emulation of the Secure subset of
EcmaScript 5 strict on current ES3R browsers) currently gives
the empty string,
meaning that the non-enumerable "x" in derived shadows the enumerable "x"
from base.
* WebKit nightly and V8 currently gives
"x: 9"
meaning that the enumerable "x" from base shows through, causing the above
loop to look up the value of the unenumerable shadowing "x" property.
I have not tested any other partial ES5 implementations (Rhino, ObjectPascal,
??).
The relevant test seems to be ES5 section 12.6.4 step 6.a (and likewise 7.a for
the next production):
Let P be the name of the next property of obj whose [[Enumerable]]
attribute is true.
>From this text, I think either interpretation is possible. However, since a
>common pattern for using for-in uses the enumerated key to look up the
>corresponding value on the object being enumerated (e.g., "obj[k]" above), I
>think that the more useful interpretation is the one currently employed by
>TraceMonkey and SES5/3.
I think a clarification of this issue be added to the ES5 errata. I propose
that this clarification adopt the more useful suppression behavior, and suggest
that ES5 implementations in progress switch to that behavior. Whichever
disambiguation is adopted, I'll be happy to file bugs against the
implementations with the non-conforming behavior. We should also add a test for
this to es5conform.
--
Cheers,
--MarkM
_______________________________________________
es-discuss mailing list
[email protected]<mailto:[email protected]>
https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss