# 属性描述符 ## 经典真题 - *JavaScript* 中对象的属性描述符有哪些?分别有什么作用? ## 属性描述符详解 在 *JavaScript* 中,对象的属性可以分为两种: - 数据属性:它的本质就是一个数据 - 存取器属性:它的本质是一个函数,但是可以将它当作普通属性来使用,当给该属性赋值时,会运行相应的 *setter* 函数,当获取该属性的值时,会运行相应的 *getter* 函数。除了存取器,还有一些其他的关键字,用以表示当前属性是否可写、是否有默认值、是否可枚举等,这些关键字就是属性描述符。 属性描述符是 *ECMAScript* 5 新增的语法,它其实就是一个内部对象,用来描述对象的属性的特性。 ### 属性描述符的结构 在定义对象、定义属性时,我们曾经介绍过属性描述符,属性描述符实际上就是一个对象。 属性描述符一共有 *6* 个,可以选择使用。 - *value*:设置属性值,默认值为 *undefined*。 - *writable*:设置属性值是否可写,默认值为 *false*。 - *enumerable*:设置属性是否可枚举,即是否允许使用 *for/in* 语句或 *Object.keys( )* 函数遍历访问,默认为 *false*。 - *configurable*:当设置为 false 时,该属性的类型不能在数据属性和访问器属性之间更改,且该属性不可被删除,且其描述符的其他属性也不能被更改(但是,如果它是一个可写的数据描述符,则 value 可以被更改,writable 可以更改为 false)。默认值为 false。 - *get*:取值函数,默认为 *undefined*。 - *set*:存值函数,默认为 *undefined*。 注意这几个属性不是都可以一起设置,具体如下图: ![image-20211021111647398](https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2021-10-21-031647.png) **示例 1** 下面示例演示了使用 *value* 读写属性值的基本用法。 ```js var obj = {}; //定义空对象 Object.defineProperty(obj, 'x', {value : 100}); //添加属性x,值为100 console.log(Object.getOwnPropertyDescriptor(obj, 'x').value); //返回100 ``` **示例 2** 下面示例演示了使用 *writable* 属性禁止修改属性 *x*。 ```js var obj = {}; Object.defineProperty(obj, 'x', { value : 1, //设置属性默认值为1 writable : false //禁止修改属性值 }); obj.x = 2; //修改属性x的值 console.log(obj.x); // 1 说明修改失败 ``` 在正常模式下,如果 *writable* 为 *false*,重写属性值不会报错,但是操作失败,而在严格模式下则会抛出异常。 **示例 3** *configurable* 可以禁止修改属性描述符,当其值为 *false* 时,*value、writable、enumerable* 和 *configurable* 禁止修改,同时禁止删除属性。 在下面示例中,当设置属性 *x* 禁止修改配置后,下面操作都是不允许的,其中 *obj.x=5;* 若操作失败,则后面 *4* 个操作方法都将抛出异常。 ```js var obj = Object.defineProperty({}, 'x', { configurable : false // 禁止配置 }); obj.x = 5; //试图修改其值 console.log(obj.x); //修改失败,返回undefined Object.defineProperty(obj, 'x', {value : 2}); //抛出异常 Object.defineProperty(obj, 'x', {writable: true}); //抛出异常 Object.defineProperty(obj, 'x', {enumerable: true}); //抛出异常 Object.defineProperty(obj, 'x', {configurable: true}); //抛出异常 ``` 当 *configurable* 为 *false* 时,如果把 *writable=true* 改为 *false* 是允许的。只要 *writable* 或 *configurable* 有一个为 *true*,则 *value* 也允许修改。 ### *get* 和 *set* 函数 除了使用点语法或中括号语法访问属性的 *value* 外,还可以使用访问器,包括 *set* 和 *get* 两个函数。 其中,*set( )* 函数可以设置 *value* 属性值,而 *get( )* 函数可以读取 *value* 属性值。 借助访问器,可以为属性的 *value* 设计高级功能,如禁用部分特性、设计访问条件、利用内部变量或属性进行数据处理等。 **示例 1** 下面示例设计对象 *obj* 的 *x* 属性值必须为数字。为属性 *x* 定义了 *get* 和 *set* 特性,*obj.x* 取值时,就会调用 *get*;赋值时,就会调用 *set*。 ```js var obj = Object.create(Object.prototype, { _x : { //数据属性 value : 1, //初始值 writable : true }, x : { //访问器属性 get : function () { //getter return this._x; //返回_x属性值 }, set : function (value) { //setter if (typeof value != "number"){ throw new Error('请输入数字'); } this._x = value; //赋值 } } }); console.log(obj.x); //1 obj.x = "2"; //抛出异常 ``` **示例 2** *JavaScript* 也支持一种简写方法。针对示例 *1*,通过以下方式可以快速定义属性。 ```js var obj = { _x : 1, // 定义 _x 属性 get x() { return this._x }, //定义 x 属性的 getter set x(value) { //定义 x 属性的 setter if (typeof value != "number"){ throw new Error('请输入数字'); } this._x = value; // 赋值 } }; console.log(obj.x); //1 obj.x = 2; console.log(obj.x); //2 ``` 取值函数 *get( )* 不能接收参数,存值函数 *set( )* 只能接收一个参数,用于设置属性的值。 ### 操作属性描述符 属性描述符是一个内部对象,无法直接读写,可以通过下面几个函数进行操作。 - *Object.getOwnPropertyDescriptor( )*:可以读出指定对象私有属性的属性描述符。 - *Object.defineProperty( )*:通过定义属性描述符来定义或修改一个属性,然后返回修改后的描述符。 - *Object.defineProperties( )*:可以同时定义多个属性描述符。 - *Object.getOwnPropertyNames( )*:获取对象的所有私有属性。 - *Object.keys( )*:获取对象的所有本地可枚举的属性。 - *propertyIsEnumerable( )*:对象实例方法,直接调用,判断指定的属性是否可枚举。 **示例 1** 在下面示例中,定义 *obj* 的 *x* 属性允许配置特性,然后使用 *Object.getOwnPropertyDescriptor( )* 函数获取对象 *obj* 的 *x* 属性的属性描述符。修改属性描述符的 *set* 函数,重设检测条件,允许非数值型数字赋值。 ```js var obj = Object.create(Object.prototype, { _x: { //数据属性 value: 1, //初始值 writable: true }, x: { //访问器属性 configurable: true, //允许修改配置 get: function () { //getter return this._x; //返回_x属性值 }, set: function (value) { if (typeof value != "number") { throw new Error('请输入数字'); } this._x = value; //赋值 } } }); var des = Object.getOwnPropertyDescriptor(obj, "x"); //获取属性x的属性描述符 des.set = function (value) { //修改属性x的属性描述符set函数 //允许非数值型的数字,也可以进行赋值 if (typeof value != "number" && isNaN(value * 1)) { throw new Error('请输入数字'); } this._x = value; } obj = Object.defineProperty(obj, "x", des); console.log(obj.x); //1 obj.x = "2"; //把一个给数值型数字赋值给属性x console.log(obj.x); //2 ``` **示例 2** 下面示例先定义一个扩展函数,使用它可以把一个对象包含的属性以及丰富的信息复制给另一个对象。 【实现代码】 ```js function extend (toObj, fromObj) { //扩展对象 for (var property in fromObj) { //遍历对象属性 if (!fromObj.hasOwnProperty(property)) continue; //过滤掉继承属性 Object.defineProperty( //复制完整的属性信息 toObj, //目标对象 property, //私有属性 Object.getOwnPropertyDescriptor(fromObj, property) //获取属性描述符 ); } return toObj; //返回目标对象 } ``` 【应用代码】 ```js var obj = {}; //新建对象 obj.x = 1; //定义对象属性 extend(obj, { get y() { return 2} }) //定义读取器对象 console.log(obj.y); //2 ``` ### 控制对象状态 *JavaScript* 提供了 *3* 种方法,用来精确控制一个对象的读写状态,防止对象被改变。 - *Object.preventExtensions*:阻止为对象添加新的属性。 - *Object.seal*:阻止为对象添加新的属性,同时也无法删除旧属性。等价于属性描述符的 *configurable* 属性设为 *false*。注意,该方法不影响修改某个属性的值。 - *Object.freeze*:阻止为一个对象添加新属性、删除旧属性、修改属性值。 同时提供了 *3* 个对应的辅助检查函数,简单说明如下: - *Object.isExtensible*:检查一个对象是否允许添加新的属性。 - *Object.isSealed*:检查一个对象是否使用了 *Object.seal* 方法。 - *Object.isFrozen*:检查一个对象是否使用了 *Object.freeze* 方法。 **示例** 下面代码分别使用 *Object.preventExtensions、Object.seal* 和 *Object.freeze* 函数控制对象的状态,然后再使用 *Object.isExtensible、Object.isSealed* 和 *Object.isFrozen* 函数检测对象的状态。 ```js var obj1 = {}; console.log(Object.isExtensible(obj1)); //true Object.preventExtensions(obj1); console.log(Object.isExtensible(obj1)); //false var obj2 = {}; console.log(Object.isSealed(obj2)); //true Object.seal(obj2); console.log(Object.isSealed(obj2)); //false var obj3 = {}; console.log(Object.isFrozen(obj3)); //true Object.freeze(obj3); console.log(Object.isFrozen(obj3)); //false ``` ## 真题解答 - *JavaScript* 中对象的属性描述符有哪些?分别有什么作用? > 参考答案: > > 属性描述符一共有 *6* 个,可以选择使用。 > > - *value*:设置属性值,默认值为 *undefined*。 > - *writable*:设置属性值是否可写,默认值为 *false*。 > - *enumerable*:设置属性是否可枚举,即是否允许使用 *for/in* 语句或 *Object.keys( )* 函数遍历访问,默认为 *false*。 > - *configurable*:当设置为 false 时,该属性的类型不能在数据属性和访问器属性之间更改,且该属性不可被删除,且其描述符的其他属性也不能被更改(但是,如果它是一个可写的数据描述符,则 value 可以被更改,writable 可以更改为 false)。默认值为 false。 > - *get*:取值函数,默认为 *undefined*。 > - *set*:存值函数,默认为 *undefined*。 > > 使用属性描述符的时候,*get* 和 *set* 以及 *value* 和 *writable* 这两组是互斥的,设置了 *get* 和 *set* 就不能设置 *value* 和 *writable*,反之设置了 *value* 和 *writable* 也就不可以设置 *get* 和 *set*。 -*EOF*-