在ES6(ECMAScript 6)中访问[[NativeBrand]] / [[Class]]

7
我正在阅读ES6的草案,并注意到在Object.prototype.toString部分中有这样一条注释:
历史上,此函数偶尔被用于访问[[Class]]内部属性的字符串值,该属性在本规范的先前版本中用作各种内置对象的名义类型标记。toString的这个定义保留了将其用作可靠测试特定类型的内置对象的能力,但对于其他类型的内置或程序定义对象,它不提供可靠的类型测试机制。
从阅读es-discuss上的this thread可以得知,听起来像是在ES6草案中将[[Class]]替换为[[NativeBrand]],以便他们可以将其指定为不可扩展的(至少Allen Wirfs-Brock's thoughts是这样的)。
好奇心驱使下,我在FireFox和Chrome(启用实验性JavaScript)上进行了快速测试:
Object.prototype.toString.apply(new WeakMap());
=> '[object WeakMap]'

""WeakMap"不是ES6草案中指定的[[NativeBrand]]之一。然而,这个测试在两个浏览器上都返回了"[object WeakMap]"。"
"所以我很困惑。我有几个问题。"

1. Chrome和Firefox表现正常吗?

从某种程度上来看,根据草案的一种阅读方式,它们应该返回[object Object](所有这些都是相当新的,所以我不会对这在未来的浏览器版本中发生变化感到意外)。然而,我很难理解草案这部分的意图,特别是因为有一些地方有"???"

有没有一直关注es-discuss的人有相关信息?或者有谁能更好地理解草案语言?


2. 是否有替代 Object.prototype.toString 的方法?

从上面引用的注释中可以看出,Object.prototype.toString 似乎是为了保留向后兼容性而保留的,好像现在有一些新的东西应该被代替。特别是那部分读作 "它不提供可靠的类型测试机制,用于其他类型的内置...对象"。这是否意味着未来的内置对象无法使用此方法进行测试?

让我们举一个具体的例子。

如果我想确保从未知来源接收到的对象是一个 String 对象(一个实际构造的 String 对象,而不是一个原始字符串),我可以执行以下操作:

if (Object.prototype.toString.apply(unknownObject) != '[object String]')
    throw new TypeError('String object expected.');

这让我知道无论在哪个框架中构建,unknownObject是否为String对象。

我的问题是,这是否应该是我进入ES6时采取的方法?还是有其他选择?例如Object.getNativeBrandOf


3. 由于[[NativeBrand]]似乎不包括未来的对象类型,那么如何测试这些对象?

这个方法可行吗?

if (Object.prototype.toString.apply(unknownObject) != '[object Symbol]')
    throw new TypeError('Symbol expected.');

假设Symbol是私有名称的最终名称。

我应该使用它吗?

if (Object.prototype.toString.apply(unknownObject) != '[object WeakMap]')
    throw new TypeError('WeakMap expected.');

...还是其他什么?


我询问的原因是,我正在编写代码,并希望在一两年后尽可能轻松地转换为ES6。如果有Object.prototype.toString的替代方法,那么我可以只需添加一个shim并继续进行。谢谢!

更新

benvie的回答为我提供了正确的术语,让我理解了我的问题的答案。

我找到了关于这个问题的一封来自Allen Wirfs-Brock在es-discuss邮件

以下是我为任何其他提出相同问题的人所找到的内容:

1. Chrome和Firefox是否表现正确?

是的,为什么会在下面解释。

2. 是否有替代Object.prototype.toString的方法?

现在有一些“替代方案”可以作为可能性,但不能作为替代品

a. 使用@@toStringTag符号。然而,我的理解是应该仍然使用Object.prototype.toString。提供@@toStringTag是为了允许扩展可以从Object.prototype.toString返回的结果。如果您有一个原型想要添加自己的字符串标签,可以使用@@toStringTag将值设置为任何字符串。Object.prototype.toString将返回此值,除非此值是ES5内置之一,在这种情况下,字符串标记将以“~”开头。

b. 在用户定义的对象上使用私有符号。我读过一封电子邮件,推广这种方法作为对用户定义对象进行相同类型检查的最佳方式。然而,我不明白它如何成为跨框架解决方案,也不能让您检查ES6内置。

因此,即使有一些替代方案,现在和未来都坚持使用Object.prototype.toString是好的,但有一个注意点:

这将确保您拥有ES5内置对象(例如String),但它不能完全保证您拥有ES6内置对象,因为它们可以使用@@toStringTag进行欺骗。我不确定为什么会这样,可能是我遗漏了一些东西,或者随着规范的发展可能会改变。

3. 由于[[NativeBrand]]似乎不包括未来类型的对象,那么如何测试这些对象呢?

如上所述,Object.prototype.toString仍然可以用于ES6内置对象,但它并不是完全可靠的,因为任何人都可以使用@@toStringTag符号进行欺骗。但是,也许不应该有一个完全可靠的方法,因为Object.prototype.toString(weakmap) == '[object WeakMap]'并不意味着weakmap instanceof WeakMap(也不应该!)。weakmap可能来自另一个框架,或者它可能是用户创建的类WeakMap对象。您唯一知道的是它声称在功能上等同于WeakMap。

这似乎引出了一个问题,为什么不能有一个用户定义的对象,它报告自己在功能上等同于一个StringArray(没有前缀"~")。


关于最后一个问题(关于〜等),答案是网络上存在依赖于现有O.p.toString结果值不可欺骗的现有ES5内置代码。我们希望保持这个保证,但仅适用于O.p.toString在ES<=5中已知的对象/ [[Class]]值。 - Allen Wirfs-Brock
第32版中删除了~步骤。 - Knu
这让我知道unknownObject是否为字符串对象,无论它是在哪个帧中构建的。不包括Opera浏览器。 - Knu
在http://www.ecma-international.org/ecma-262/6.0/的ES6标准中,我没有找到“NativeBrand”,因此我猜想“class”没有被“NativeBrand”所替代。 - Mocuishle
1个回答

1

这是ES6规范中目前正在变化的一个方面。对于现有的对象集,由于兼容性等各种原因,现有的机制得以保留。在最新的ES6规范中(发布于10月26日),您可以找到一些潜在未来方向的提示。

15.4.6.2.4 ArrayIterator.prototype.@@toStringTag
@@toStringTag属性的初始值为字符串"Array Iterator"。

15.14.5.13 Map.prototype.@@toStringTag
@@toStringTag属性的初始值为字符串"Map"。

您可以在es-discuss上的此线程中找到引发此讨论的原始内容。


谢谢你指引我正确的方向并给了我所需的术语。为了后人,我已经在上面更新了我的问题,并提供了更完整的答案。 - Nathan Wall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接