为啥 `"foo".bar = 42;` 在 ES6 的严格模式下会抛出 `TypeError`?
Posted
技术标签:
【中文标题】为啥 `"foo".bar = 42;` 在 ES6 的严格模式下会抛出 `TypeError`?【英文标题】:Why does `"foo".bar = 42;` throw `TypeError` in strict mode in ES6?为什么 `"foo".bar = 42;` 在 ES6 的严格模式下会抛出 `TypeError`? 【发布时间】:2018-09-15 21:12:59 【问题描述】:根据 ES5.1 规范,程序 "use strict;" "foo".bar = 42;
导致创建 String
对象,分配给它的属性,然后将对象扔掉,导致没有可观察到的效果 - 包括任何异常。 (可以通过在类似 Opera 12 中的 ES5 兼容 JS 实现中尝试来确认没有效果。)
在现代 JS 实现中,它会抛出一个 TypeError
来代替——试试吧:
"use strict"; "foo".bar = 42;
我很确定新行为是 ES6 规范强制要求的,但是尽管多次阅读相关部分,但我看不到在哪里指定 TypeError
被抛出。事实上,the key parts 似乎没有改变:
6.2.3.2 PutValue (V, W)#
ReturnIfAbrupt(V). ReturnIfAbrupt(W). 如果 Type(V) 不是 Reference,则抛出 ReferenceError 异常。 设 base 为 GetBase(V)。 如果 IsUnresolvableReference(V) 为真,则 … 否则,如果 IsPropertyReference(V) 为真,则 一个。如果 HasPrimitiveBase(V) 为真,则 i.断言:在这种情况下,base 永远不会为 null 或未定义。 二。将 base 设置为 ToObject(base)。 乙。让成功成为? base.[[Set]](GetReferencedName(V), W, GetThisValue(V)). c。 ReturnIfAbrupt(成功)。 d.如果 succeeded 为 false 且 IsStrictReference(V) 为 true,则抛出 TypeError 异常。 e.返回。 …
规范(ES6 或更高版本)要求在哪里抛出 TypeError
?
【问题讨论】:
我也没有在 Annex C 看到它。 我开始怀疑这实际上根本不在规范中。 这里是它进入 V8 的地方,像往常一样没有引用:codereview.chromium.org/408183002 现在看看其他人。 我的期望是ToObject
会显式返回一个不可扩展的String
,因此[[Set]]
会返回 false 并且第 6 步会抛出,但我实际上并没有看到它定义了字符串对象是不可扩展的。
@Paulpro:创建一个字符串对象,使用Object.preventExtensions
将其设置为不可扩展,并尝试在严格模式下对其设置属性会产生不同的错误消息,其中提到“对象不可扩展”。对于问题中的情况,它似乎根本没有经过对象。
【参考方案1】:
我猜它在这里:
http://www.ecma-international.org/ecma-262/7.0/#sec-ordinaryset
9.1.9.1。普通集(O、P、V、接收器)
[...]
4.b。如果 Type(Receiver) 不是 Object,则返回 false。
(以前称为[[Set]],在ES6 §9.1.9。)
尽管PutValue
将base
提升为一个对象,但它对接收者的作用并不相同——GetThisValue(V)
仍然在原始V
上调用(具有原始基数)。因此,GetThisValue
返回一个原语,OrdinarySet.4b
未能分配新创建的 ownDesc
并返回 false
,这反过来又导致 PutValue.6d
抛出 TypeError,前提是引用是严格的。
V8的对应部分似乎也遵循同样的逻辑:
Maybe<bool> Object::AddDataProperty(....
if (!it->GetReceiver()->IsJSReceiver())
return CannotCreateProperty(...
https://github.com/v8/v8/blob/3b39fc4dcdb6593013c497fc9e28a1d73dbcba03/src/objects.cc#L5140
【讨论】:
它不应该到达那里,因为ownDesc
应该是未定义的。
那又怎样?第 3 步没有返回。
不错的发现,对我来说确实不错。它将沿着原型向上走,最终到达顶层,创建一个数据描述符,并尝试在接收器上设置它,这仍然是原语。
是的,最好将其扩展为提及6.d
,尽管问题中已包含该内容。
奇怪的是,这似乎没有包含在附件 C 中。附件 C 只是提供信息,但仍然如此。很奇怪。【参考方案2】:
@georg 的回答似乎是正确的 ES6+ 解释,但看起来这种行为也不是新的。来自ES5.1 PutValue:
否则,如果 IsPropertyReference(V),则
一个。如果 HasPrimitiveBase(V) 为 false,则令 put 为 base 的 [[Put]] 内部方法,否则令 put 为特殊的 [[Put]] 内部方法定义如下。
b.使用 base 作为 this 值调用 put 内部方法,并传递 GetReferencedName(V) 作为属性名称,W 作为值, 和 IsStrictReference(V) 用于 Throw 标志。
并在引用的 [[Put]] 中:
否则,这是在瞬态对象O上创建自己的属性的请求
一个。如果 Throw 为 true,则抛出 TypeError 异常。
感觉我可能误读了一些东西……但是,相当尖锐的“这是在瞬态对象 O 上创建自己的属性的请求”还能指的是什么?
【讨论】:
这是一个很好的观察。我不太确定在检查 ES5.1 规范时我是如何忽略这一点的——但看起来 Opera 12 开发人员也这样做了。以上是关于为啥 `"foo".bar = 42;` 在 ES6 的严格模式下会抛出 `TypeError`?的主要内容,如果未能解决你的问题,请参考以下文章
为啥我在设置中收到“'Bars' 不能用作实体类型 'Foo' 的属性”?
比较 $("#foo .bar") 和 $(".bar", "#foo") 的性能