为什么`'foo" .bar = 42;`在ES6中以严格模式抛出`TypeError`?

问题描述:

根据ES5.1规范,程序use strict; foo.bar = 42; 导致创建一个 String 对象,分配给它上面的属性,然后抛弃该对象,导致没有可观察到的影响 - 包括任何例外。 (没有效果可以通过在与Opera 12中的ES5兼容的JS实现中尝试来确认。)

According to the ES5.1 spec, the program "use strict;" "foo".bar = 42; causes a String object to be created, assigns to a property on it, and then throws the object away, resulting in no observable effects - including any exceptions. (The absence of effect can be confirmed by trying it in an ES5-compatible JS implementation like that in Opera 12.)

在现代JS实现中,它会抛出 TypeError 而不是试试:

In modern JS implementations, it throws a TypeError instead—try it:

"use strict"; "foo".bar = 42;

我很确定ES6规范规定了新的行为,但是尽管多次阅读相关部分,但我无法看到指定 TypeError 被抛出的位置。事实上,关键部分似乎是不变:

I am pretty sure the new behaviour is mandated by the ES6 spec, but despite reading the relevant sections several times I cannot see where it is specified that TypeError be thrown. In fact, the key parts appear to be unchanged:


6.2.3.2 PutValue( V W )#


  1. ReturnIfAbrupt( V )。

  2. ReturnIfAbrupt( W )。

  3. 如果Type( V )不是Reference,则抛出 ReferenceError 异常。

  4. base 为GetBase( V )。

  5. 如果IsUnresolvableReference( V )是的,然后是


    • ...

  1. ReturnIfAbrupt(V).
  2. ReturnIfAbrupt(W).
  3. If Type(V) is not Reference, throw a ReferenceError exception.
  4. Let base be GetBase(V).
  5. If IsUnresolvableReference(V) is true, then

  • a。如果HasPrimitiveBase( V )为真,那么


    • i。断言:在这种情况下, base 永远不会为null或未定义。

    • ii。将 base 设置为ToObject( base )。

    • 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).


      规范(ES6或更高版本)在哪里强制抛出 TypeError

      Where does the spec (ES6 or later) mandate throwing TypeError?

我想它就在这里:

http://www.ecma-international.org/ecma-262/7.0/#sec-ordinaryset


9.1.9.1。 OrdinarySet(O,P,V,Receiver)

9.1.9.1. OrdinarySet (O, P, V, Receiver)

[...]

4.b。如果Type(Receiver)不是Object,则返回false。

4.b. If Type(Receiver) is not Object, return false.

(以前称为[[Set]],在ES6§9.1.9。)

(Previously called [[Set]], in ES6 §9.1.9.)

虽然 PutValue 促销 base 到一个对象,它与接收者不一样 - GetThisValue(V)仍在原始 V (带有原始基数)。所以, GetThisValue 返回一个原语, OrdinarySet.4b 无法分配新创建的 ownDesc 并返回 false ,这反过来导致 PutValue.6d 抛出一个TypeError,提供参考是严格的。

Although PutValue promotes the base to an object, it doesn't do the same with the receiver -- GetThisValue(V) is still called on the original V (with a primitive base). So, GetThisValue returns a primitive, OrdinarySet.4b fails to assign a freshly created ownDesc and returns false, and this in turn causes PutValue.6d to throw a TypeError, provided the reference is strict.

V8的相应部分似乎遵循相同的逻辑:

The corresponding part of V8 seems to follow the same logic:

Maybe<bool> Object::AddDataProperty(....
  if (!it->GetReceiver()->IsJSReceiver()) {
    return CannotCreateProperty(...

https://github.com/v8/v8/blob/3b39fc4dcdb6593013c497fc9e28a1d73dbcba03/src/objects.cc#L5140