Skip to content Skip to sidebar Skip to footer

Why Can Es6 Symbol Properties Be Made Enumerable By Object.defineproperty?

In ES6 properties can be defined as symbol properties: var symbol = Symbol(); var object = {}; object[symbol] = 'value'; MDN defines enumerable properties as 'those which can be i

Solution 1:

Yes, there's a reason for allowing Symbol properties to be enumerable: Object.assign:

let s1 = Symbol();
let s2 = Symbol();
let s3 = Symbol();
let original = {};
original[s1] = "value1";                // EnumerableObject.defineProperty(original, s2, {   // Enumerableenumerable: true,
  value: "value2"
});
Object.defineProperty(original, s3, {   // Non-enumerablevalue: "value3"
});
let copy = {};
Object.assign(copy, original);
console.log("copy[s1] is " + copy[s1]); // value1, because it was enumerableconsole.log("copy[s2] is " + copy[s2]); // value2, because it was enumerableconsole.log("copy[s3] is " + copy[s3]); // undefined, because it wasn't enumerable

Live Copy on Babel's REPL.

Just for clarity:

MDN defines enumerable properties as 'those which can be iterated by a for..in loop' (1).

That's simply wrong for ES6 (ES2015). It was a reasonable, if simplistic, definition in ES5 and earlier, no it's no longer even simplistically correct because of Symbols. I've fixed the article.


This is a CW because it was the outgrowth of the comments on the question.

Solution 2:

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:

  1. 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=newSet;
  for (let key ofReflect.ownKeys(obj)) {
      if (typeof key === "string") { // type check happens firstlet 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();
varobject = {};

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 inobject) {
  console.log(f, '=', object[f]);
}

for (let k ofObject.getOwnPropertyNames(object)) {
  console.log(k);
}

you can verify that in Babel and Traceur.

However, you'll see two interesting things:

  1. getOwnPropertyNames includes non-enumerable properties. This makes sense, as it follows completely different rules.
  2. for...in includes non-string properties under both transpilers. This does not seem to match the spec, but does match ES5's behavior.

Post a Comment for "Why Can Es6 Symbol Properties Be Made Enumerable By Object.defineproperty?"