当别人对你说起对象属性描述符,可能会蒙逼。而若是说起对象属性的 get/set 方法就秒懂了,标准描述和习惯表述在这里有些差异,可是指向的是同一个概念所涉及的东西。对象属性描述符
在编程实践中是经过 Object 对象的defineProperty
方法暴露给咱们。因此搞清楚Object.defineProperty
是理解对象属性描述符
的惟一途径。javascript
Object.defineProperty
, define Property 翻译成中文就是定义属性,顾名思义就是为对象定义或修改属性的细节,即经过属性描述符来定义属性读写的细节。使用该方法容许精确添加或修改对象的属性,熟悉 vue 的朋友对 defineProperty 因该不陌生:vue
Object.defineProperty(obj, prop, descriptor)
defineProperty
接受3个参数, obj 表示要修改或者定义属性的对象,prop 是要定义或者修改属性的名称, descriptor
属性描述符用于定义该属性的特性。java
descriptor 是一个对象,对象里的属性描述符有两种类型:数据描述符和存取描述符。编程
数据描述符是一个具备值的属性,该值多是可写的,也可能不是可写的。浏览器
存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是二者。框架
数据描述符和存取描述符均具备一下可选键值(特性):函数
存取描述符:this
描述符可同时具备的键值:翻译
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
数据描述符 | Yes | Yes | Yes | Yes | No | No |
存取描述符 | Yes | Yes | No | No | Yes | Yes |
若是一个描述符不具备value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。若是一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。因此 value、writable 与 get/set 不能同时设置。code
var obj = {} obj.a = 123 Object.defineProperty(obj, "newDataProperty", { value: 101, // 设置值 writable: true, // 值能够被修改 enumerable: true, // 能够被枚举 configurable: true // 属性能够被删除、特性能够修改 })
上面给对象 obj 添加一个新属性 'newDataProperty',而且设置了属性的特性。
在ES5以前对象的属性咱们只能设置一个字面量值或者一个引用,在浏览器支持Object.defineProperty
方法以后,就像给了咱们一台显微镜,可以在更低的粒度层控制属性的行为和特性:定义属性的可访问行、值的读写规则等。
若是对象中不存在指定的属性,Object.defineProperty()
会建立这个属性。若是属性已经存在,Object.defineProperty()
将尝试根据描述符中的值以及对象当前的配置来修改这个属性。若是旧描述符将其configurable
属性设置为false
,则该属性被认为是“不可配置的”,而且没有属性能够被改变(除了单向改变 writable 为 false)。当属性不可配置时,不能在数据和访问器属性类型之间切换。
描述符中未显示设置的特性使用其默认值。
下面用几个栗子来演示这些特性的具体表现:
let foo = { a: 1 } delete foo.a Object.defineProperty(foo, 'b', { value: 2, // 默认值为2 configurable: false // 不允许被删除和修改 }) delete foo.b // 没法删除 foo.b = 999 // 没法修改 console.log(foo.b) // 2
let foo = { a: 1, b: 2, c: 3 } for (let i in foo) { // a、b、c能够被枚举 console.log(`key: ${i}, value: ${foo[i]}`) } Object.defineProperty(foo, 'a', { enumerable: false // 设置属性不能够被枚举 }) for (let i in foo) { // a没有被枚举 console.log(`key: ${i}, value: ${foo[i]}`) }
let foo = { a: 1 } // 修改 foo.a 的值 foo.a = 2 console.log(foo.a) // 2 Object.defineProperty(foo, 'a', { writable: false // 设置值不能被修改 }) // 尝试修改 foo.a 的值 foo.a = 3 // 没法修改 console.log(foo.a) // 2
let foo = {} Object.defineProperty(foo, 'a', { value: 1 // 设置属性的值为 1 }) console.log(foo.a) // 1
let foo = { a: 1 } Object.defineProperty(foo, 'b', { get: function () { return `hi, ${this.value}` }, set: function (value) { this.a = value // 将输入值保存在同对象下属性 a 里 this.value = value + 1 } }) console.log(foo.b) // 'hi, undefined' foo.b = 1 console.log(foo.a) // 1 console.log(foo.b) // hi, 2
注意: get没有参数,set接受实参为当前设置的值.。在get、set函数内部能够经过this.value
访问value
特性,从而经过该特性来获取或者着设置属性的值。get/set 经常使用于值依赖内部数据的场合。须要尽可能同时设置get、set。若是仅仅只设置了get,那么咱们将没法设置该属性值。若是仅仅只设置了set,咱们也没法读取该属性的值。
Object.defineProperty
只能设置一个属性的描述符,当须要设置多个属性描述符时可使用Object.defineProperties
:
let foo = {} Object.defineProperties(foo, { a: { value: 1, configurable: true }, b: { get: function() { return this.value ? `hi, ${this.value}` : 0 }, set: function(value) { this.value = value + 1 } } }) console.log(foo.a) // 1 console.log(foo.b) // 0 foo.b = 2 console.log(foo.b) // 'hi, 3'
咱们能够经过Object.getOwnPropertyDescriptor
获取某一属性的特性集合:
let foo = { a: 1 } Object.defineProperty(foo, 'a', { value: 2, // 设置值为 2 writable: false, // 值不可修改 configurable: false // 设置属性不可删除,特性不可修改 }) let fooDescripter = Object.getOwnPropertyDescriptor(foo, 'a') console.log(fooDescripter) // 获取的特性以下 // { // configurable:false, // enumerable:true, // value:2, // writable:false // }
这里须要注意,Object.defineProperty
建立一个对象的新属性与修改一个已经存在属性的区别。建立一个新属性默认描述符的键值都是 false 或者 undefined。而修改一个已经存在的属性的描述符时,若是以前没有被设置过或过原始方式给对象添加的属性,则属性的 configurable、enumerable、writable 描述符都默认为 true。具体差别举个例子细细体会:
let foo = {} Object.defineProperty(foo, 'a', { value: 2 // 设置值为 2 }) let fooDescripter = Object.getOwnPropertyDescriptor(foo, 'a') console.log(fooDescripter) // 获取的特性以下 // { // configurable:false, // 不允许被删除和修改 // enumerable: false, // 不能被枚举 // value:2, // writable:false // 值不可修改 // }
变量 a 是经过 Object.defineProperty
方法建立的,默认全部属性描述符的值都为 false。 咱们能够经过最后两个代码示例体会一下区别:enumerable
属性描述符在两个例子中都没有被事先设置,可是不一样情形下的值不同。
原则上这个系列不会去讲某个API,可是属性描述符
可以加深咱们对 javascript 、框架底层的理解。