当我们创建 JavaScript 对象时,不论是使用对象字面量语法还是其它别的语法,我们都可以给这些对象添加属性。每一个属性默认会有一个属性描述符。属性描述符就是一个简单的 JavaScript 对象,与目标对象的属性关联起来,包含了该属性的各种信息,比如值value
和其它元数据。
var myObj = { myPropOne: 1, myPropTwo: 2 }; console.dir( myObj ); // { myPropOne: 1, myPropTwo: 2 }
在上面的代码中,我们使用字面量语法创建了一个 JavaScript 对象myObj
;该对象添加了两个属性myPropOne
和myPropTwo
,这两个属性分别给了初始值1
和2
。
现在,如果按照下面的方式给myPropOne
属性赋值,是可以成功添加的,并且值会有变化。
var myObj = { myPropOne: 1, myPropTwo: 2 }; // override `myPropOne` property myObj.myPropOne = 10; console.log( 'myObj.myPropOne =>', myObj.myPropOne ); // myObj.myPropOne => 10 console.log( 'myObj =>', myObj ); // { myPropOne: 10, myPropTwo: 2 }
为了访问属性的属性描述符,我们需要使用Object
的静态方法。Object.getOwnPropertyDescriptor
返回obj
对象的属性名为prop
的属性描述符。
Object.getOwnPropertyDescriptor(obj, prop);
函数名中的Own
意味着这个属性prop
属于obj
对象本身,而不是在其原型链上。如果obj
没有属性prop
,则返回undefined
。
如果你想了解有关原型和原型链的内容,请阅读这篇文章。
var myObj = { myPropOne: 1, myPropTwo: 2 }; // get property descriptor of `myPropOne` let descriptor = Object.getOwnPropertyDescriptor( myObj, 'myPropOne' ); console.log( descriptor ); // { value: 1, writable: true, enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor
函数返回一个对象,这个对象包含描述该属性的配置和当前值的信息。属性描述符的value
属性是属性的当前值;writable
是用户是否可以给这个属性赋新值;enumerable
是该属性是否会出现在枚举语句中,比如for...in
循环或者for...of
循环或者Object.keys
等等;configurable
用于设置用户是否有权限修改属性描述符的属性,例如设置writable
和enumerable
的值。
属性描述符还有set
和get
关键字,代表设置值和返回值的中间函数,不过这些是可选的。
为了给对象添加新的属性,或者使用自定义描述符更新已有属性,可以使用Object.defineProperty
。下面,我们修改已有属性myPropOne
的值,将writable
设置为false
,这将禁止给myObj.myPropOne
赋值。
'use strict'; var myObj = { myPropOne: 1, myPropTwo: 2 }; // modify property descriptor Object.defineProperty( myObj, 'myPropOne', { writable: false } ); // print property descriptor let descriptor = Object.getOwnPropertyDescriptor( myObj, 'myPropOne' ); console.log( descriptor ); // set new value myObj.myPropOne = 2;
上面的代码,我们将myPropOne
设置为不可写的,因此,当尝试给myPropOne
赋值时就会报错。
使用Object.defineProperty
更新已有属性的属性描述符时,原始的属性描述符会与新的描述符合并。Object.defineProperty
返回的是变更之后的原始对象myObj
。
下面我们看看将enumerable
设置为false
会发生什么。
var myObj = { myPropOne: 1, myPropTwo: 2 }; // modify property descriptor Object.defineProperty( myObj, 'myPropOne', { enumerable: false } ); // print property descriptor let descriptor = Object.getOwnPropertyDescriptor( myObj, 'myPropOne' ); console.log( descriptor ); // { value: 1, writable: true, enumerable: false, configurable: true } // print keys console.log( Object.keys( myObj ) ); // [ 'myPropTwo' ]
运行结果是,Object.keys
的返回值里面没有myPropOne
这个属性了。
使用Object.defineProperty
给对象定义新的属性时,如果参数是{}
,那么,默认的属性描述符类似下面这样:
{ value: undefined, writable: true, enumerable: false, configurable: false }
现在,我们使用自定义描述符定义一个新的属性,注意,confiurable
值为false
。我们将writable
设置为false
,enumerable
同样为true
,而value
值设置为3
。
var myObj = { myPropOne: 1, myPropTwo: 2 }; // modify property descriptor Object.defineProperty( myObj, 'myPropThree', { value: 3, writable: false, configurable: false, enumerable: true } ); // print property descriptor let descriptor = Object.getOwnPropertyDescriptor( myObj, 'myPropThree' ); console.log( descriptor ); // change property descriptor Object.defineProperty( myObj, 'myPropThree', { writable: true } );
通过设置configurable
为false
,我们就不能修改myPropThree
的描述符了。如果你不希望用户改变对象的推荐行为,这一点是非常有效的。
属性的get
和set
也可以在属性描述符中设置,其名字就是get
和set
。但在定义时,有一定的限制。你不能添加初始值或描述符的value
字段,因为只能由 getter 返回这个属性的值。你不能使用描述符的writable
字段,因为写操作是通过 setter 完成的,你可以通过 setter 完成写操作。详细介绍请阅读 MDN 的getter
和setter
。
使用Object.defineProperties
可以创建或更新多个属性。这个函数接受两个参数,第一个参数是目标对象,也就是属性被添加或修改到的对象;第二个参数是一个对象,其中,key
作为属性名,value
作为属性描述符。该函数返回目标对象。
Object.create
也可以用于创建对象。这是创建没有原型或使用自定义原型的对象的最简单方式。同时,这也是直接通过自定义属性描述符创建对象的简单方式。
Object.create
函数签名如下:
var obj = Object.create( prototype, { property: descriptor, ... } )
这里,prototype
是一个对象,作为obj
的原型。如果prototype
为null
,那么,obj
就没有任何原型。如果是只用var obj = {};
来创建对象,默认情况下,obj.__proto__
指向Object.prototype
,也就是说,obj
的原型是Object
类。
这与使用Object.prototype
作为第一个参数来调用Object.create
是等价的:
'use strict'; // create an object using `Object.create` var o = Object.create( Object.prototype, { a: { value: 1, writable: false }, b: { value: 2, writable: true } } ); // see prototype of `o` console.log( 'o.__proto__ =>', o.__proto__ ); // see the property descriptor of `a` console.log( 'o.hasOwnProperty( "a" ) =>', o.hasOwnProperty( "a" ) );
但是,当我们将protoype
设置为null
时,就会有下面的错误:
'use strict'; // create an object with `null` prototype var o = Object.create( null, { a: { value: 1, writable: false }, b: { value: 2, writable: true } } ); // log prototype console.log( o.__proto__ ); // get property descriptor of `a` console.log( 'o.hasOwnProperty( "a" ) =>', o.hasOwnProperty( "a" ) );