为什么 `"foo".bar = 42;` 在 ES6 中以严格模式抛出 `TypeError`?
Why does `"foo".bar = 42;` throw `TypeError` in strict mode in ES6?
根据 ES5.1 规范,程序 "use strict;" "foo".bar = 42;
导致创建一个 String
对象,分配给一个 属性 对象,然后将对象扔掉,导致没有可观察到的影响——包括任何例外。 (没有效果可以通过在兼容 ES5 的 JS 实现中尝试来确认,就像 Opera 12 中那样。)
在现代 JS 实现中,它会抛出一个 TypeError
——试试看:
"use strict"; "foo".bar = 42;
我很确定新行为是 ES6 规范强制要求的,但是尽管多次阅读相关部分我还是看不出在哪里指定 TypeError
被抛出。事实上,the key parts 看起来没有变化:
6.2.3.2 PutValue (V, W)#
- ReturnIfAbrupt(V).
- ReturnIfAbrupt(W).
- If Type(V) is not Reference, throw a ReferenceError exception.
- Let base be GetBase(V).
- If IsUnresolvableReference(V) is true, then
- …
- Else if IsPropertyReference(V) is true, then
- a. If HasPrimitiveBase(V) is true, then
- i. Assert: In this case, base will never be null or undefined.
- ii. Set base to ToObject(base).
- b. Let succeeded be ? base.[[Set]](GetReferencedName(V), W, GetThisValue(V)).
- c. ReturnIfAbrupt(succeeded).
- d. If succeeded is false and IsStrictReference(V) is true, throw a TypeError exception.
- e. Return.
- …
规范(ES6 或更高版本)在哪里强制抛出 TypeError
?
我猜它在这里:
http://www.ecma-international.org/ecma-262/7.0/#sec-ordinaryset
9.1.9.1. OrdinarySet (O, P, V, Receiver)
[...]
4.b. If Type(Receiver) is not Object, return false.
(在ES6 §9.1.9中以前称为[[Set]]。)
尽管 PutValue
将 base
提升为一个对象,但它对接收者的作用不同——GetThisValue(V)
仍然在原始 V
上被调用(具有原始基础)。因此,GetThisValue
returns 原语,OrdinarySet.4b
无法分配新创建的 ownDesc
和 returns 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
@georg 的回答似乎是正确的 ES6+ 解释,但看起来这种行为也不是新的。来自 ES5.1 PutValue:
Else if IsPropertyReference(V), then
a. If HasPrimitiveBase(V) is false, then let put be the [[Put]] internal method of base, otherwise let put be the special [[Put]]
internal method defined below.
b. Call the put internal method using base as its this value, and passing GetReferencedName(V) for the property name, W for the value,
and IsStrictReference(V) for the Throw flag.
并在引用的 [[Put]] 中:
Else, this is a request to create an own property on the transient object O
a. If Throw is true, then throw a TypeError exception.
感觉好像我可能误读了一些东西……但是,“这是一个在瞬态对象 O 上创建自己的 属性 的请求”还能指代什么?
根据 ES5.1 规范,程序 "use strict;" "foo".bar = 42;
导致创建一个 String
对象,分配给一个 属性 对象,然后将对象扔掉,导致没有可观察到的影响——包括任何例外。 (没有效果可以通过在兼容 ES5 的 JS 实现中尝试来确认,就像 Opera 12 中那样。)
在现代 JS 实现中,它会抛出一个 TypeError
——试试看:
"use strict"; "foo".bar = 42;
我很确定新行为是 ES6 规范强制要求的,但是尽管多次阅读相关部分我还是看不出在哪里指定 TypeError
被抛出。事实上,the key parts 看起来没有变化:
6.2.3.2 PutValue (V, W)#
- ReturnIfAbrupt(V).
- ReturnIfAbrupt(W).
- If Type(V) is not Reference, throw a ReferenceError exception.
- Let base be GetBase(V).
- If IsUnresolvableReference(V) is true, then
- …
- Else if IsPropertyReference(V) is true, then
- a. If HasPrimitiveBase(V) is true, then
- i. Assert: In this case, base will never be null or undefined.
- ii. Set base to ToObject(base).
- b. Let succeeded be ? base.[[Set]](GetReferencedName(V), W, GetThisValue(V)).
- c. ReturnIfAbrupt(succeeded).
- d. If succeeded is false and IsStrictReference(V) is true, throw a TypeError exception.
- e. Return.
- …
规范(ES6 或更高版本)在哪里强制抛出 TypeError
?
我猜它在这里:
http://www.ecma-international.org/ecma-262/7.0/#sec-ordinaryset
9.1.9.1. OrdinarySet (O, P, V, Receiver)
[...]
4.b. If Type(Receiver) is not Object, return false.
(在ES6 §9.1.9中以前称为[[Set]]。)
尽管 PutValue
将 base
提升为一个对象,但它对接收者的作用不同——GetThisValue(V)
仍然在原始 V
上被调用(具有原始基础)。因此,GetThisValue
returns 原语,OrdinarySet.4b
无法分配新创建的 ownDesc
和 returns 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
@georg 的回答似乎是正确的 ES6+ 解释,但看起来这种行为也不是新的。来自 ES5.1 PutValue:
Else if IsPropertyReference(V), then
a. If HasPrimitiveBase(V) is false, then let put be the [[Put]] internal method of base, otherwise let put be the special [[Put]] internal method defined below.
b. Call the put internal method using base as its this value, and passing GetReferencedName(V) for the property name, W for the value, and IsStrictReference(V) for the Throw flag.
并在引用的 [[Put]] 中:
Else, this is a request to create an own property on the transient object O
a. If Throw is true, then throw a TypeError exception.
感觉好像我可能误读了一些东西……但是,“这是一个在瞬态对象 O 上创建自己的 属性 的请求”还能指代什么?