【问题标题】:Why can ES6 Symbol properties be made enumerable by Object.defineProperty?为什么 ES6 Symbol 属性可以被 Object.defineProperty 枚举?
【发布时间】:2015-09-10 20:03:49
【问题描述】:

在 ES6 中属性可以定义为符号属性:

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

MDN 将可枚举属性定义为“可以通过 for..in 循环迭代的属性”(1)。符号属性永远不会被 for...in 循环迭代,因此它们可以被认为是不可枚举的 (2)。

那么,您可以这样做是否有意义:

Object.defineProperty(object, symbol, {
    value: 'value',
    enumerable: true
});

并且查询对象的描述符确实确认该属性是可枚举的:

Object.getOwnPropertyDescriptor(object, symbol)
// -> { enumerable: true }

为什么?这有什么用?

(1)https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties

(2) for...in 使用[[Enumerate]],它只包含字符串键。既然我们有了符号属性,可能应该更改 MDN 上的定义。

【问题讨论】:

  • 为什么不允许?你可以反过来问:如果普通属性默认是可枚举的,为什么它们可以设为不可枚举?请注意,确保我了解您的用途...
  • for ... in 迭代中是否显示了一个带有其键符号的可枚举属性? (似乎那样会有问题。)
  • 关键是它们仍然不可枚举——它们仍然没有在 for...in 或 Object.keys 中被枚举——所以从所有意图和目的来看,它们都是不可枚举的,但是他们说在描述符中是可枚举的。
  • @FelixKling:可以吗? I can't,带 FF38。规范说it shouldn't
  • 所以要明确一点,[[OwnPropertyKeys]] 也会返回符号。 Object.assign 只是查看可枚举标志,无论该值是普通键还是符号。所以,是的,因为符号的enumerable 标志也是true 默认情况下,它们被复制。似乎它们只在for/in/of循环中被特殊对待。

标签: javascript ecmascript-6


【解决方案1】:

是的,允许Symbol 属性可枚举是有原因的: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 在 Babel 的 REPL 上。

为了清楚起见:

MDN 将可枚举属性定义为“可以通过 for..in 循环迭代的属性”(1)。

这对于 ES6 (ES2015) 来说是完全错误的。这是 ES5 和更早版本中的一个合理的定义,如果简单的话,不,因为Symbols,它甚至不再简单正确。我已经修复了这篇文章。


这是一个 CW,因为它是问题上的 cmets 的产物。

【讨论】:

  • 啊哈!现在这很有用,因为我最初的问题是在为 Object.assign 编写 polyfill 时出现的。谢谢。
  • 所以我想这已经给“什么是enumerable?”这个问题增加了更多的困惑。我相信最简单的答案是enumerable可见的。换句话说,如果你console.log一个对象,你看到的任何东西都可以被认为是enumerable,反之亦然。
  • @SaeedAhadian - 我不会那样说,因为 A) 如果某些东西不是“可见的”,我希望尝试使用它的代码会失败,但它不会。 B)我知道的大多数控制台都显示不可枚举的属性。当然,Chrome/Chromium/Brave、Firefox、IE11、Edge(旧 JScript 版本)和 Edge(beta V8 版本)中的那些都可以。 Node.js 没有(这让我很惊讶)。
  • @T.J.Crowder 在使用 Node.js 时,我完全没有注意到即使不可枚举的属性也会出现在 Chrome 等其他控制台中。所以我总是用这个技巧来找出什么是可枚举的,什么不是现在看来并不总是一个好的衡量标准。
  • @SaeedAhadian - :-) 你可以使用 Object.entries(obj) 代替(假设你只关心 own 可枚举的属性)。或者 JSON.stringify 如果对象没有任何循环引用。
【解决方案2】:

这是因为枚举规则包含一个需要字符串键的子句。请记住,枚举和请求键是具有完全不同规则的不同操作。

查看the section for for ... in/for ... of head evaluation (13.7.5.12),它声明迭代已使用:

  1. 如果iterationKind是枚举的,那么

    c。返回obj.[[Enumerate]]()

The description of [[Enumerate]] (9.1.11) 非常清楚地表明它:

返回一个 Iterator 对象 (25.1.1.2),其 next 方法迭代 O 的可枚举属性的所有字符串值键。

可枚举属性的检查出现在正文的后面,伪代码示例使这一点更加清晰:

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
          }
      }
  }
  ...
}

(我的cmets)

显然,具有非字符串键的属性将不会被枚举。使用这个例子:

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);
}

你可以在 Babel 和 Traceur 中验证。

但是,您会看到两件有趣的事情:

  1. getOwnPropertyNames 包括不可枚举的属性。这是有道理的,就像it follows completely different rules
  2. for...in 包括两个转译器下的非字符串属性。这似乎不符合规范,但符合 ES5 的行为。

【讨论】:

  • 谢谢@ssube。这是有用的技术信息。我标记了 T.J.克劳德的答案是答案,因为它解决了“为什么?”,但这个答案确实解决了指定的“为什么?”,所以我认为两者同样有用。
  • @stephband 这完全公平。这是一个非常直接的原因。
猜你喜欢
  • 2017-12-05
  • 2015-11-10
  • 1970-01-01
  • 2019-03-24
  • 2013-02-05
  • 1970-01-01
  • 1970-01-01
  • 2012-07-26
  • 1970-01-01
相关资源
最近更新 更多