(() => {
const obj = {}
Object.defineProperty(obj, 'x', { value: 1 }) // obj.x 不可写
const o = Object.create(obj)
o.x = 2
console.log(o.x) // 1 还是 2?
})()
关键语句在o.x = 2
,可以从ECMA-262规范中看看这行语句是如何执行的。
对于赋值语句,在规范 Runtime Semantics: Evaluation 中可以发现实际执行的是 PutValue(lref, rval)
。而 PutValue 中对于对象属性赋值,调用的是 o.[[Set]]('x', 2, o)
。因此,可以根据Set来梳理流程。
o.[[set]]('x', 2, o)
OrdinarySet(o, 'x', 2, o)
OrdinarySetWithOwnDescriptor(o, 'x', 2, o, undefined)
obj.[[Set]]('x', 2, o)
OrdinarySet(obj, 'x', 2, o)
OrdinarySetWithOwnDescriptor(obj, 'x', 2, o, { writable: false })
OrdinarySetWithOwnDescriptor
执行了 2 次,第 1 次传入的 ownDesc
为 undefined
,因为 o
上没有 x
属性。
从而导致调用 obj.[[Set]]('x', 2, o)
,触发第二次 OrdinarySetWithOwnDescriptor
的调用,不过这次使用的是 obj
上的 ownDesc
。
所以,给对象上非自身属性赋值时,会使用原型上该属性的 ownDesc
,如果不可写,则这次写操作会失败。
When the abstract operation OrdinarySetWithOwnDescriptor is called with Object O, property key P, value V, ECMAScript language value Receiver, and Property Descriptor (or undefined) ownDesc, the following steps are taken:
-
Assert: IsPropertyKey(P) is true.
-
If ownDesc is undefined, then
a. Let parent be ? O.[GetPrototypeOf].
b. If parent is not null, then
1. Return ? parent.[[Set]](P, V, Receiver).
c. Else,
1. Set ownDesc to the PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
-
If IsDataDescriptor(ownDesc) is true, then
a. If ownDesc.[[Writable]] is false, return false.
b. If Type(Receiver) is not Object, return false.
c. Let existingDescriptor be ? Receiver.[GetOwnProperty].
d. If existingDescriptor is not undefined, then
1. If IsAccessorDescriptor(existingDescriptor) is true, return false. 2. If existingDescriptor.[[Writable]] is false, return false. 3. Let valueDesc be the PropertyDescriptor { [[Value]]: V }. 4. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
e. Else,
1. Assert: Receiver does not currently have a property P. 2. Return ? CreateDataProperty(Receiver, P, V).
-
Assert: IsAccessorDescriptor(ownDesc) is true.
-
Let setter be ownDesc.[[Set]].
-
If setter is undefined, return false.
-
Perform ? Call(setter, Receiver, « V »).
-
Return true.