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.)
In modern JS implementations, it throws a TypeError
instead—try it:
"use strict"; "foo".bar = 42;
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)#
- 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.
- …
Where does the spec (ES6 or later) mandate throwing TypeError
?
I guess it's here:
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.
(Previously called [[Set]], in ES6 §9.1.9.)
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.
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
@georg’s answer seems to be the right ES6+ interpretation, but it looks like the behaviour isn’t new, either. From 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.
and in the referenced [[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.
It feels like I’m probably misreading something… but what else could the rather pointed “this is a request to create an own property on the transient object O” be referring to?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With