您与对象属性的大部分交互可能停留在表面,包括创建对象字面量以及使用键设置和访问属性值。但是,您可以内部配置对象的任何属性,以便精细控制这些属性的访问、更改和定义方式。每个对象属性都有一组包含与该属性关联的元数据的不可见属性,称为“属性描述符”。
与任何属性关联的描述符有两种类型:数据描述符和访问器描述符。数据描述符包括键值对,其中包含属性的值,无论该值是否可写、可配置或可枚举。访问器描述符包含在属性被设置、更改或访问时执行的函数。
属性 | 描述符类型 | 默认值来自Object.defineProperty() |
描述 |
---|---|---|---|
[[Value]] |
数据 | undefined |
包含属性的值。 |
[[Writable]] |
数据 | false |
确定您是否可以更改属性的值。 |
[[Get]] |
访问器 | undefined |
属性的 getter 函数,在访问属性时执行。 |
[[Set]] |
访问器 | undefined |
属性的 setter 函数,在属性被设置或更改时执行。 |
[[Configurable]] |
两者 | false |
如果为 false ,则无法删除属性,也无法更改其属性。如果为 false 且 [[Writable]] 为 true ,则属性的值仍然可以更改。 |
[[Enumerable]] |
两者 | false |
如果为 true ,则可以使用 for...in 循环或 Object.keys() 静态方法迭代属性。 |
这些属性中的每一个都使用与 [[Prototype]]
相同的简写形式,表明这些属性不应直接访问。相反,使用 Object.defineProperty()
静态方法来定义或修改对象的属性。Object.defineProperty()
接受三个参数:要操作的对象、要创建或修改的属性键,以及包含与要创建或修改的属性关联的描述符的对象。
默认情况下,使用 Object.defineProperty()
创建的属性不可写、不可枚举或不可配置。但是,您创建的任何属性(无论是作为对象字面量的一部分还是使用点或方括号表示法)都是可写、可枚举和可配置的。
const myObj = {};
Object.defineProperty(myObj, 'myProperty', {
value: true,
writable: false
});
myObj.myProperty;
> true
myObj.myProperty = false;
myObj.myProperty;
> true
例如,当 [[Writable]]
的值为 false
时,尝试为关联属性设置新值在严格模式之外会静默失败,并在严格模式下抛出错误
{
const myObj = {};
Object.defineProperty(myObj, 'myProperty', {
value: true,
writable: false
});
myObj.myProperty = false;
myObj.myProperty;
}
> true
(function () {
"use strict";
const myObj = {};
Object.defineProperty(myObj, 'myProperty', {
value: true,
writable: false
});
myObj.myProperty = false;
myObj.myProperty;
}());\
> Uncaught TypeError: "myProperty" is read-only
有效利用描述符是一个相当高级的概念,但了解对象的内部结构对于理解以更常见的方式处理对象所涉及的语法至关重要。例如,当使用 Object.create()
静态方法时,这些概念就会发挥作用,该方法使您可以精细控制附加到新对象的任何原型。
Object.create()
使用现有对象作为其原型创建一个新对象。这使新对象能够从另一个用户定义的对象继承属性和方法,就像对象从 JavaScript 内置的 Object
原型继承属性一样。当您使用对象作为参数调用 Object.create()
时,它会创建一个空对象,并将传递的对象作为其原型。
const myCustomPrototype = {
'myInheritedProp': 10
};
const newObject = Object.create( myCustomPrototype );
newObject;
> Object { }
<prototype>: Object { myInheritedProp: 10 }
myInheritedProp: 10
<prototype>: Object { … }
Object.create
可以接受第二个参数,使用类似于 Object.defineProperty()
的语法为新创建的对象指定自有属性——即,一个将键映射到一组描述符属性的对象
const myCustomPrototype = {
'myInheritedProp': 10
};
const myObj = Object.create( myCustomPrototype, {
myProperty: {
value: "The new property value.",
writable: true,
configurable: true
}
});
myObj;
> Object { … }
myProperty: "The new property value."
<prototype>: Object { myInheritedProp: 10 }
在此示例中,新对象 (myObj
) 使用对象字面量 (myCustomPrototype
) 作为其原型,而对象字面量本身包含继承的 Object.prototype
,从而产生一系列称为原型链的继承原型。每个对象都有一个原型,无论是分配的还是继承的,它都有自己的分配或继承的原型。此链在 null
原型处结束,该原型没有自己的原型。
const myPrototype = {
'protoProp': 10
};
const newObject = Object.setPrototypeOf( { 'objProp' : true }, myPrototype );
newObject;
> Object { objProp: true }
objProp: true
<prototype>: Object { protoProp: 10 }
protoProp: 10
<prototype>: Object { … }
值原型中包含的属性在对象的“顶层”可用,而无需直接访问原型属性
const objectLiteral = {
"value" : true
};
objectLiteral;
> Object { value: true }
value: true
<prototype>: Object { … }
objectLiteral.toString();
"[object Object]"
此模式适用于与对象关联的整个原型链:当尝试访问属性时,解释器会在原型链的每个“级别”中从上到下查找该属性,直到找到该属性或链结束
const myCustomPrototype = {
'protoProp': "Prototype property value."
};
const myObj = Object.create( myCustomPrototype, {
myProperty: {
value: "Top-level property value.",
writable: true,
configurable: true
}
});
myObj.protoProp;
> "Prototype property value."
检查您的理解
哪些描述符是访问器?
[[Get]]
[[Set]]
[[Writable]]