js Object.defineProperty
亦黑迷失 人气:0Object.defineProperty()
Object.defineProperty()
用于给一个对象定义一个新属性或是修改某个现有属性,并返回此对象,事实上就算不定义变量去接收返回值,该对象也会被直接修改(所以它不是一个纯函数)。它接收 3 个参数,第 1 个是要定义属性的对象;第 2 个是要定义或修改的属性的属性名或 Symbol;第 3 个是对该属性的描述,称之为属性描述符,为一个对象,可以拥有 4 个 key。
属性描述符
属性描述符可以分为 2 类——数据属性描述符和存取属性描述符,它们有 2 个共同的可拥有的 key: configurable 和 enumerable。区别在于剩下 2 个,数据属性描述符为 value 和 writable 而存储属性描述符为 get 和 set。
下图截取自 MDN 文档:
一个属性如果拥有了 value 或 writable,那么它就是数据描述符,它就不能同时拥有 get 或 set;反之,如果拥有了 get 或 set,那么它就是存取描述符,同理不能再拥有 value 或 writable。 接下来详细介绍下上图中的 6 个特性:
configurable
描述该属性是否可以被删除:
'use strict' const obj = { singer: 'Jay' } Object.defineProperty(obj, 'singer', { configurable: false }) delete obj.singer
我们将 obj 的 singer 属性的 configurable 设置为了 false,所以在第 6 行使用 delete 删除 singer 属性时,在严格模式下(在非严格模式下不会报错,但也不会删除 singer,即静默失败),浏览器会报错:
2. 描述该属性是否可以再次通过 Object.defineProperty()
来修改属性描述:
const obj = { singer: 'Jay' } Object.defineProperty(obj, 'age', { configurable: false }) Object.defineProperty(obj, 'age', { enumerable: flase })
当我们将 age 的 configurable 设为 false 后,在第 6 行试图修改 age 的 enumerable 特性,会报错“TypeError: Cannot redefine property: age”。当设置 configurable 为 false 后(writable 默认为 false),该属性的任意描述符(enumerable,value 或是 get、set)都不能被改变了。但是,如果初始定义时 writable 为 true,即使 configurable 为 false,那么接下去还是可以将 writable 改为 false,同时也可以修改 value:
const obj = new Object() obj.singer = 'Jay' Object.defineProperty(obj, 'age', { configurable: false, writable: true }) console.log(obj.age) // undefined Object.defineProperty(obj, 'age', { writable: false, value: 40 }) console.log(obj.age) // 40
注:上面采用了 new Object()
的方式定义了一个对象,和直接通过字面量定义对象作用一样。
描述该属性是否可以修改属性描述符类型:
const obj = { singer: 'Jay' } Object.defineProperty(obj, 'singer', { configurable: false }) Object.defineProperty(obj, 'singer', { get() { return 'Zhou' } })
上面这个例子里,在第 1 行我们通过字面量的方式直接定义了 obj 对象,其属性 singer 的描述符默认为数据描述符,在第 3 行我们将其的 configurable 设为 false,然后在第 6 行给它定义一个 getter 函数,也就是试图将它改为存取描述符,浏览器同样会报 "Cannot redefine property: singer" 错误。 如果我们新定义一个 age 属性,让其的属性描述符为存取描述符,但是 configurable 依旧设置为 false:
const obj = { singer: 'Jay', _age: 40 } Object.defineProperty(obj, 'age', { configurable: false, enumerable: true, get() { return this._age }, set(val) { this._age = val } }) Object.defineProperty(obj, 'age', { value: 35 })
那么我们想再次给 age 的属性描述符一个 value 特性,想将之改为数据描述符,浏览器也会报错。
enumerable
描述该属性是否是可枚举的。比如现在有如下代码,我们通过 Object.defineProperty()
给 obj 定义 age 属性, 并设置 enumerable 为 false:
var obj = { singer: 'Jay' } Object.defineProperty(obj, 'age', { enumerable: false })
现在,当我使用 for in 遍历 obj 时,只能得到 singer 而得不到 age,因为 for in 是以任意顺序遍历 obj 的除 Symbol 以外的可枚举属性(包括原型上的属性):
for (const key in obj) { console.log(key) // singer }
使用 Object.keys()
遍历得到的数组也只包含 singer,因为 Object.keys()
返回的是 obj 自身的可枚举属性组成的数组:
console.log(Object.keys(obj)) // ['singer'] 复制代码
如果是直接打印 obj 对象,那么在 Node.js 中运行将看不到 age,在 Chrome 浏览器中可以看到,但是 age 是浅色的:
如果想查看一个 enumerable 为 false 的属性(比如 obj 的 age 属性),除了可以直接通过 obj.age
查看,还可以通过 Object.getOwnPropertyNames()
——返回自身除 Symbol 值作为名称的属性之外的所有属性,或是 Reflect.ownKeys()
——获取自身所有的属性:
console.log(Object.getOwnPropertyNames(obj)) // [ 'singer', 'age' ] console.log(Reflect.ownKeys(obj)) // [ 'singer', 'age' ]
writable
数据描述符专有特性,描述属性是否可修改。
'use strict' var obj = { singer: 'Jay' } Object.defineProperty(obj, 'singer', { writable: false }) obj.singer = 10
上例中,在严格模式下,在第 6 行给 writable 为 false 的属性 singer 赋值,会报错 “TypeError: Cannot assign to read only property 'age' of object '#'”。在非严格模式下不会报错,但也不会修改属性 singer 的值。 另外,通过前面的例子可以看出,writable 的优先级是高于 configurable 的。
value
数据描述符专有特性,为属性的值。读取属性时返回该值;修改属性时则修改该值。
get
当属性被获取时,会执行 getter 函数。
set
当属性被设置时,会执行 setter 函数。
const obj = { singer: 'Jay', _age: 40 } Object.defineProperty(obj, 'age', { get() { return this._age }, set(value) { this._age = value } }) console.log(obj.age) // 40 obj.age = 50 console.log(obj.age) // 50
用存取描述符定义的属性,直接打印查看对象时,比如我们将上例中 obj 的 age 属性的 enumerable 设为 true,然后 console.log(obj)
,会发现其结果为“{ singer: 'Jay', _age: 40, age: [Getter/Setter] }” 。 顺便说一句,vue2 的响应式原理就用到了 getter 和 setter,具体可前往《vue.js数据响应式原理解析》。
默认值
直接给对象定义属性时:
- configurable 默认为 true;
- enumerable 默认为 true;
- writable默认为 true;
- value 默认为定义时赋的值;
- get 默认为 undefined;
- set 默认为 undefined;
通过属性描述符定义一个属性时:
- configurable 默认为 false;
- enumerable 默认为 false;
- writable默认为 false;
- value 默认为 undefined;
- get 默认为 undefined;
- set 默认为 undefined;
获取属性的描述符
如果想要验证上面的结论,我们可以通过 Object.getOwnPropertyDescriptor(对象, '属性名')
查看某个对象自有属性的属性描述符,或是 Object.getOwnPropertyDescriptors(对象)
查看某个对象的所有的自身属性的属性描述符。
Object.defineProperties()
Object.defineProperty()
是一次定义 / 修改一个属性,传入 3 个参数。如果我们想一次性通过属性描述符定义 / 修改多个属性,可以使用 Object.defineProperties()
,它传入 2 个参数,第 1 个参数为要定义属性的对象;第二个参数为一个对象,其键名为要定义的属性,键值为一个对象,里面就是关于该属性的属性描述符定义。
比如:
const obj = { singer: 'Jay', _age: 40 } Object.defineProperties(obj, { age: { configurable: true, enumerable: true, get() { return this._age }, set(val) { this._age = val } }, gender: { configurable: true, enumerable: true, writable: true, value: '男' } })
对象本身的两个方法
其实每个对象本身都可以直接使用 getter —— 得到当前属性值的回调函数,和 setter —— 监视当前属性值变化的回调函数,来定义属性,比如:
const obj = { firstName: 'Jay', lastName: 'Zhou', get fullName() { return this.firstName + ' ' + this.lastName }, set fullName(val) { const tempArr = val.split(' ') this.firstName = tempArr[0] this.lastName = tempArr[1] } }
加载全部内容