js 通过Object.defineProperty() 定义和控制对象属性
作者:亦黑迷失
Object.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] } }
到此这篇关于js 通过Object.defineProperty() 定义和控制对象属性的文章就介绍到这了,更多相关js Object.defineProperty内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!