This is because the rules for enumeration include a clause requiring string keys. Bear in mind that enumeration and asking for keys are different operations with entirely different rules.
Looking at the section for for ... in
/for ... of
head evaluation (13.7.5.12), it states that the iteration is done using:
If iterationKind is enumerate, then
c. Return obj.[[Enumerate]]()
.
The description of [[Enumerate]]
(9.1.11) very clearly states that it:
Return an Iterator object (25.1.1.2) whose next
method iterates over all the String-valued keys of enumerable properties of O
.
The check for enumerable properties comes later in the body, and the pseudo-code example makes this even more clear:
function* enumerate(obj) {
let visited=new Set;
for (let key of Reflect.ownKeys(obj)) {
if (typeof key === "string") { // type check happens first
let desc = Reflect.getOwnPropertyDescriptor(obj,key);
if (desc) {
visited.add(key);
if (desc.enumerable) yield key; // enumerable check later
}
}
}
...
}
(comments mine)
Clearly, properties with non-string keys will not be enumerated. Using this example:
var symbol = Symbol();
var object = {};
Object.defineProperty(object, symbol, {
value: 'value',
enumerable: true
});
Object.defineProperty(object, 'foo', {
value: 'bar',
enumerable: true
});
Object.defineProperty(object, 'bar', {
value: 'baz',
enumerable: false
});
Object.defineProperty(object, () => {}, {
value: 'bin',
enumerable: true
});
for (let f in object) {
console.log(f, '=', object[f]);
}
for (let k of Object.getOwnPropertyNames(object)) {
console.log(k);
}
you can verify that in Babel and Traceur.
However, you'll see two interesting things:
getOwnPropertyNames
includes non-enumerable properties. This makes sense, as it follows completely different rules.
for...in
includes non-string properties under both transpilers. This does not seem to match the spec, but does match ES5's behavior.