Assignment statements like me.name = 'forceddd'
can be seen everywhere in JavaScript. But, do we really understand what this code does? Did you create a new attribute? Did you modify the value of the original attribute? Did this operation succeed or fail? This is just a simple assignment statement, but if we think about it carefully, we will find that this kind of detail is actually not that simple.
Everything can be classified and discussed. First of all, it is divided into two categories: the object me
has the name
attribute and there is no name
attribute.
Of course, we all know that there is a prototype chain mechanism in JavaScript, so when the name
attribute does not exist in the me
, it can also be divided into two situations: me
name
attribute and the 061b9c3c070357 attribute does not exist on the prototype chain of name
. It can be divided into three categories to discuss.
name attribute already exists in me
const me = { name: 'me', }; me.name = 'forceddd'; console.log(me.name); //forceddd
In this case, it is obvious that our purpose is to reset name
attribute, and the result seems to be obvious. me.name
been modified to 'forceddd'
.
But don’t forget that the attributes of an object also have its own attributes, including whether it is read-only ( writable
), (usually an object used to describe the various characteristics of an object’s attributes is called The attribute descriptor or attribute description object 161b9c3c07059b), so when the writable
) is false
, what will the result be like? Not only that, the name
attribute defines the getter
or setter
, which becomes a access descriptor , what will the result be like?
name
propertywritable
isfalse
timeconst me = { name: 'me', }; Object.defineProperty(me, 'name', { writable: false, value: me.name, }); me.name = 'forceddd'; // name的属性值仍是 'me' console.log(me.name); //me
因为 `name` 属性是只读的的,所以赋值操作 `me.name = 'forceddd'` 失败了,这恒河狸。但是要注意的是,这种操作失败是静默的,如果我们在操作之后没有校验 `name` 值的话,是很难发现的。使用严格模式可以把这种静默失败变成显式的**TypeError**。
```js
'use strict';//使用严格模式
const me = {
name: 'me',
};
Object.defineProperty(me, 'name', {
writable: false,
value: me.name,
});
me.name = 'forceddd';
//TypeError: Cannot assign to read only property 'name' of object '#<Object>'
```
When the
name
attribute is an access descriptor (getter
orsetter
defined)const me = { _name: 'me', get name() { return this._name; }, set name(v) { console.log('调用setter,设置name'); this._name = v; }, }; me.name = 'forceddd';//调用setter,设置name console.log(me.name);//forceddd
此时,`name` 属性存在 `setter` 函数,在进行赋值操作时,就会调用 `setter` 函数。一般来说,我们在定义访问描述符时,`getter ` 和 `setter `都是**成对出现**的,但是只定义其中一个也是没有任何问题的,只是这样的话可能会出现一些我们不期望的情况,比如,当 `name` 只定义了一个 `getter` 函数时,不存在 `setter` 函数,赋值操作便没有意义了。
**非严格模式**下,因为没有 `setter` 函数,所以赋值静默失败了。
```js
const me = {
_name: 'me',
get name() {
return this._name;
},
};
me.name = 'forceddd';
console.log(me.name); //me
```
**严格模式**下,对没有 `setter` 函数的访问描述符进行赋值操作,会出现一个**TypeError**,也是非常河狸的。
```js
'use strict';
const me = {
_name: 'me',
get name() {
return this._name;
},
};
me.name = 'forceddd';
//TypeError: Cannot set property name of #<Object> which has only a getter
```
To sum up, when the me
attribute exists in name
, there are three situations when performing the assignment operation:
- When the attribute is a Access Descriptor , if present
setter
, is calledsetter
; if there is nosetter
, in the non-strict mode silent failures, in strict mode generates a TypeError . writable
of the attribute descriptor (061b9c3c0708e4) isfalse
, it will fail silently in non-strict mode, and a TypeError- When the attribute does not belong to the above two cases, the value is set to the value of the attribute, and the assignment is successful.
not exist on 161b9c3c0709c1 me and its prototype chain 161b9c3c0709e0
At this time, a new attribute
name
will be created on the objectme
with a value of'forceddd'
. This is also a way we often use.const human = {}; const me = {}; Object.setPrototypeOf(me, human); me.name = 'forceddd'; console.log({ me, human }); //{ me: { name: 'forceddd' }, human: {} }
The attribute we created in this way can of course be modified by assignment, which means that writable
in the attribute descriptor of this attribute at this time is true
. In fact, at this time attributes configurability , enumerable of also as true
, and by which defineProperty
adding attribute default values are different, defineProperty
method default attribute descriptor added in writable
, configurable
, enumerable
default values of false
are all 061b9c3c070acd. This is also a point worth noting.
console.log(Object.getOwnPropertyDescriptor(me, 'name'));
// {
// value: 'forceddd',
// writable: true,
// enumerable: true,
// configurable: true
// }
Add attributes through the defineProperty
Object.defineProperty(me, 'prop', { value: 'forceddd' });
console.log(Object.getOwnPropertyDescriptor(me, 'prop'));
// {
// value: 'forceddd',
// writable: false,
// enumerable: false,
// configurable: false
// }
name attribute does not exist in
At this point,
me
prototype objecthuman
presence onname
property, very often, we useme.name = 'forceddd'
assignment statement like, just to modify the objectme
, but did not want to involve other objects. But in JS, when accessing object properties, if the property does not exist on the object, it will go to the prototype chain to find it, so the prototype chain of the object is difficult to bypass.const human = {}; const me = {}; Object.setPrototypeOf(me, human);
As mentioned earlier, there are also three cases for the name
attribute on the human
name
attribute descriptor attributewritable
isfalse
//设置human对象的name属性 Object.defineProperty(human, 'name', { writable: false, value: 'human', }); me.name = 'forceddd'; console.log(me);//{}
此时,当我们进行了赋值操作后,检查 `me` 对象,会发现它并没有 `name` 属性。WTF?很难理解是吧,因为 `human` 中的 `name` 属性是只读的,所以将 `human` 对象为原型的对象就不能通过 `=` 赋值操作新增 `name` 属性了。
其实这是为了**模仿类的继承行为**,如果父类中的 `name` 属性是只读的,那么继承它的子类中的 `name` 属性自然应该也是只读的。又因为在JS中时通过原型链来模拟的类的继承行为,所以就导致了这一看起来很奇怪的现象。
同样地,如果是在**严格模式**下,不会是静默失败了,而是会产生一个**TypeError**。
name
attribute is a access descriptorObject.defineProperty(human, 'name', { set() { console.log('调用了human的setter'); } }); me.name = 'forceddd';//'调用了human的setter' console.log(me);//{}
此时,同样不会在 `me` 对象上创建 `name` 属性,而是会调用 `human` 上 `name` 属性的 `setter` 函数。类似地,当只存在`getter`,**不存在** `setter` 时,**严格模式**下会产生**TypeError**,**非严格模式**下会静默失败。
name
attribute is not in the previous two casesThis situation is the simplest and most in line with our expectations. A
name
me
To summarize: When the me
attribute does not exist in name
name
attribute exists on the prototype chain, there are three situations:
- If the property is in a prototype chain Access Descriptor and there
setter
, this will be calledsetter
; if there is onlygetter
, the non-strict mode silently fail with strict mode TypeError . - If the attribute in the prototype chain is a
writable
beingfalse
, it will fail silently in non-strict mode, and TypeError will appear in strict mode. - If the attribute is not the first two cases on the prototype chain, but a
writable
astrue
, the attribute will be created on theme
object.
If you must, must be modified or set me
of name
property, you can use defineProperty
ways to circumvent =
these restrictions assignment.
Object.defineProperty(human, 'name', {
set() {
console.log('调用了human的setter');
// me.name = 'forceddd';
},
});
Object.defineProperty(me, 'name', {
value: 'forceddd',
enumerable: true,
writable: true,
configurable: true,
});
console.log(me); //{ name: 'forceddd' }
The description on the text is always not so intuitive, and finally summarized into a picture, which is easy to understand.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。