Why can ES6 Symbol properties be made enumerable by Object.defineProperty?

后端 未结 2 507
野性不改
野性不改 2020-12-30 02:11

In ES6 properties can be defined as symbol properties:

var symbol = Symbol();
var object = {};
object[symbol] = \'value\';

MDN defines enum

相关标签:
2条回答
  • 2020-12-30 02:47

    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";                // Enumerable
    Object.defineProperty(original, s2, {   // Enumerable
      enumerable: true,
      value: "value2"
    });
    Object.defineProperty(original, s3, {   // Non-enumerable
      value: "value3"
    });
    let copy = {};
    Object.assign(copy, original);
    console.log("copy[s1] is " + copy[s1]); // value1, because it was enumerable
    console.log("copy[s2] is " + copy[s2]); // value2, because it was enumerable
    console.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.

    0 讨论(0)
  • 2020-12-30 03:11

    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=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:

    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.
    0 讨论(0)
提交回复
热议问题