1、Object.defineProperty() 是什么?
Object.defineProperty()
方法会直接在一个对象上定义
一个新属性,或者修改
一个对象的现有属性,并返回此对象,Vue 的底层中很多地方用到了这个方法,比如数据代理
、计算属性
等等,下面让我们开始掌握它 👌。
2、Object.defineProperty() 使用方法
Object.defineProperty(obj, prop, descriptor)
- obj:要添加或者修改属性的对象
- prop: 要添加或者修改属性的名称或 Symbol
- descriptor: 要添加或者修改的属性描述符
例:
let person = {
name: '张三',
gender: '男'
};
object.defineProperty(person, 'age', {
value: '18'
});
console.log(person) // => { name: '张三', gender: '男', age: 18 }
在ES6中,由于 Symbol 类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而Object.defineProperty 是定义key为Symbol的属性的方法之一。
3、 属性描述符(descriptor)
属性描述符选项实际上就是一个对象。一共有 6 个可选键值,这些可选键值又可分为两类,数据描述符
和存取描述符
,又称数据属性描述符
和访问器属性描述符
。这两种描述符可看作一个独立的对象。它们分别拥有以下可选键值 :
-
configurable
configurable
属性表示对象的属性是否可以被删除,以及除value
和writable
特性外的其他特性是否可以被修改。 -
enumerable:只有当该属性的
enumerable
键值为true
时,才可以在for...in
循环和Object.keys()
中被枚举。 -
value:该属性对应的值。可以是任何有效的
JavaScript
值(数值,对象,函数等)。 -
writable:只有该属性的
writable
键值为true
时,属性的值,也就是上面的value
,才能被 赋值运算符 (zh-CN) 改变,当writable
属性设置为false
时,该属性被称为“不可写的”。它不能被重新赋值。 -
get:属性的
getter
函数,如果没有getter
,则为undefined
。当访问该属性时会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 -
set:属性的
setter
函数,如果没有setter
,则为undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
数据描述符 | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
存取描述符 | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
默认值 | false | false | undefined | false | undefined | undefined |
默认值是指在使用 Object.defineProperty() 定义属性时的默认值
如果一个描述符配置了数据描述符独有的键值,那么将不能再使用存取描述符独有的键值,也就是不能同时拥有 value 或 writable
和 get 或 set
键,否则程序将抛出下列错误:
4、与普通字面量定义的对象属性作对比
4.1、可枚举性(enumerable)
为
true
时可枚举,为false
时不可枚举。
/** 字面量定义 ———————————————*/
let person = {
name: '张三',
gender: '男'
}
console.log(Object.keys(person)) // => ['name', 'gender']
/** 通过 Object.defineProperty() 修改描述符为不可枚举后 ———————————————*/
Object.defineProperty(person, 'gender', {
enumerable: false
})
console.log(Object.keys(person)) // => ['name']
/** Object.defineProperty() 定义, enumerable 默认为 false ———————————————*/
let person2 = {
name: '法外狂徒'
}
Object.defineProperty(person2, 'gender', {
value: '男',
enumerable: false // => 写 false 与不写描述符时效果相同,默认 false
})
console.log(Object.keys(person2)) // => ['name']
for(let key in person2) {
console.log(key) // => name
}
/** Object.defineProperty() 定义, enumerable: true ———————————————*/
let person3 = {
name: '张三年'
}
Object.defineProperty(person3, 'gender', {
value: '男',
enumerable: true
})
console.log(Object.keys(person3)) // => ['name', 'gender']
for(let key in person3) {
console.log(key) // => name, gender
}
4.2、可配置性(configurable)
为
true
时可以重复使用Object.defineProperty()
进行对属性描述符的修改,false
时不可修改,再次定义或修改将抛异常。
/** 字面量定义 ———————————————*/
let person = {
name: '张三',
age: 18
}
delete person.age // => true
console.log(person) // => { name: '张三' }
/** Object.defineProperty() 定义, configurable默认为 false ———————————————*/
let person2 = {
name: '隔壁老王'
}
Object.defineProperety(person2, 'age', {
value: 18
})
delete person2.age // => false
console.log(person2) // => { name: '隔壁老王', age: 18 }
try{
Object.defineProperty(person2, 'age', {
value: 80
})
} catch(err) {
console.log(err) // => 抛错,无法修改属性的描述符,包括: value, configurable, enumerable 等
/**
TypeError: Cannot redefine property: address
at Function.defineProperty (<anonymous>)
at index.html:27
*/
}
/** Object.defineProperty() 定义, configurable: true ———————————————*/
let person3 = {
name: '老罗'
}
Object.defineProperty(person3, 'age', {
value: 18,
configurable: true
})
console.log(person3) // => { name: '老罗', age: 18 }
Object.defineProperty(person3, 'age', {
value: 80,
enumerable: true
})
console.log(person3) // => { name: '老罗', age: 80 }
console.log(Object.keys(person3)) // => ['name', 'age']
delete person3.age // => true
console.log(person3) // => { name: '老罗' }
4.3、可重写性(writable)
为
true
时可使用字面量定义属性值,反之无法使用。
/** 字面量定义 ———————————————*/
let person = {
name: '张三',
age: 18
}
person.address = '广州'
console.log(person) // => { name: '张三', age: 18, address: '广州' }
/** Object.defineProperty() 定义, enumerable 默认为 false ———————————————*/
let person2 = {
name: '李四',
age: 18
}
Object.defineProperty(person2, 'address', {
value: '广州'
})
console.log(person2.address) // => '广州'
person2.address = '深圳'
console.log(person2.address) // => '广州'
/** Object.defineProperty() 定义, enumerable: true ———————————————*/
let person3 = {
name: '王五',
age: 18
}
Object.defineProperty(person3, 'address', {
value: '广州',
enumerable: true
})
console.log(person3.address) // => '广州'
person3.address = '深圳'
console.log(person3.address) // => '深圳'
4.4、查找属性描述符(Object.getOwnPropertyDescriptor)
Object.getOwnPropertyDescriptor()
方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
参数:
- obj:需要查找的对象
- prop:目标对象内属性名称
字面量定义的属性默认描述符:
let person2 = {
name: '张三',
age: 18
}
console.log(Object.getOwnPropertyDescriptor(person2, 'age'))
Object.defineProperty() 定义的属性默认描述符:
let person2 = {
name: '张三'
}
Object.defineProperty(person2, 'age', {
value: 8
})
console.log(Object.getOwnPropertyDescriptor(person2, 'age'))
字面量定义的对象属性默认可枚举,可配置删除,可写的原因就在于,它是一个键值默认都为 true
的数据描述符,除默认值与 Object.defineProperty() 定义的数据描述符不同外,使用方法一致。
5、getter + setter 实现数据绑定
在字面量定义对象属性时,如果我们的一个属性依赖另一个变量,需要做到数据更新,代码如下:
let address= '广州'
let person = {
name: '小李子',
address // 名称相同时简写,等同 address: address
}
console.log(person) // => { name: '小李子', address: '广州' }
address = '深圳'
console.log(address) // => 深圳
console.log(person) // => { name: '小李子', address: '广州' }
person.address= address
console.log(person) // => { name: '小李子', address: '深圳' }
而在 Object.defineProperty() 中,我们只需要使用存取描述符 get
和 set
,就能实现数据的双向绑定:
let address = '广州'
let person = {
name: '小李子'
}
Object.defineProperty(person, 'address', {
get() {
return address
},
set(val) {
address = val
}
})
console.log(person.address) // => '广州'
address = '深圳'
console.log(address) // => '深圳'
console.log(person.address) // => '深圳'
person.address = '北京'
console.log(address) // => '北京'
console.log(person.address) // => '北京'
在严格模式下 getter setter 必须是成对出现的,否则会抛异常。
以上就是我对于 Object.defineProperty() 方法的个人理解,觉得写的不错,别忘了来个点赞和收藏,你的鼓励是我坚持的动力~~