当我们创建 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" ) ); 