用于给一个对象添加或者修改一个属性,返回操作后的对象。
写法:Object.defineProperty(对象,属性,配置对象)
通过对配置对象不同的配置,可以将属性分为数据属性和存取属性。
数据属性不包括get和set方法,包括value和writable属性;存取属性包括get和set方法,不能含有value和writable属性。即这四个具有一定的相斥性。
无论是数据属性还是存取属性都会包含configurable和enumerable属性。
具体看下图
【图来自王红元】
1、configurable:控制能否对属性描述符(也就是配置对象)的修改(false时会爆redefine的错误)与能否删除该属性
2、enumerable:控制属性是否可以枚举。【forin会能遍历自身和原型上的枚举属性,Object.keys能遍历自身的枚举属性】
3、writable:控制属性值的修改,(注意是属性值,不是配置对象)
这三个属性都是布尔类型,默认都是false。
vue2的响应式原理就是应用,在get和set时做对应的响应式处理。
在set中直接修改对象的属性会触发“set陷阱”,陷入死循环。
解决办法是:可以在Object.definproperty的外面定一个变量,set去修改这个变量,get去读取这个变量,以达到类似的效果,比如下方代码的val。
let obj = {
name:'aa',
age:12
}
Object.keys(key=>{
let val = obj[key] //闭包变量,不会随着遍历结束消失。又因为get也是读这个变量,看起来对象的属性值就变了
Object.defineProperty(obj,key,{
set(newVal){
console.log('set',newVal)
val = newVal
// 不能直接obj[key] =newVal ,会死循环
},
get(){
console.log('get')
return val
}
})
})
obj.name = 111
console.log(obj.age)
Proxy能创建出一个代理对象,之后对源对象的操作,都可以通过这个代理对象完成,也就实现了监听整个对象的功能。
写法 : new Proxy(源对象,捕获器对象)
直接看下面一段包含set和get捕获器的代码。
let obj = {
name:'aa',
age:12
}
let objProxy = new Proxy(obj,{
// 各种捕获器
get(target,key,receiver){
console.log('get',target,key,receiver)
return target[key]
},
set(target,key,newVal,receiver){
target[key] = newVal
console.log('set',target,key,receiver)
//下面3个等式告诉我们 target就是源对象(obj),receiver就是接收对象/代理对象(objProxy)
console.log(target === receiver) //false
console.log(target === obj) //true
console.log(receiver === objProxy) //true
}
})
objProxy.name = 'rr'//修改代理后的对象
代码说明:
get和set捕获器的入参第一个会拿到target(源对象),最后一个入参是receiver(代理对象)。
set中对源对象的修改就直接修改target,而不用去改obj,并且不需要借助外部变量了,也不再陷入死循环。
【触发捕获器需要修改代理对象,而不是源对象】
上面的set和get捕获器就能实现Object.defineProperty的功能,并且更强大。下面介绍其他捕获器,总共13个。见下图。
【图来自王红元】
总结:
触发的方法/操作 | 捕获器名 |
Object.getPropertyOf() 拿对象的隐式原型 |
getPropertyOf() |
Object.setPropertyOf() 设置对象的隐式原型 |
setPropertyOf() |
Object.isExtensible() 用于判断对象是否可以拓展 Object.preventExtensions()能禁止拓展 |
isExtensiable() |
Object.preventExtensions() 用于禁止往对象上添加属性,即禁止拓展 |
preventExtensions() |
Object.getOwnPropertyDescriptor() 获取对象的属性描述符 |
getOwnPropertyDescriptor() |
Object.defineProperty() 用于设置对象属性 |
defineProperty() |
Object.getOwnPropertyNames() Object.getOwnPropertySymbols() 拿对象自身的普通属性和symbol属性名, 无论是否枚举 |
ownKeys() |
in操作符 | has() |
属性读取 | get() |
属性设置 | set() |
delete操作符 | deleteProperty() |
函数调用的apply | apply() |
new操作符 | construct() |
他们都可以用于拦截和设置对象属性,但是Proxy提供了更多的拦截操作。Proxy可以拦截所有属性,包括新增的,而defineProperty只能拦截定义时的属性,还不能拦截delete属性。
Reflect是一个内置的全局对象。
Reflect也有Proxy捕获器上那13个方法。见下图:
【图来时王红元】
1. 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。 现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。
2. 修改某些Object方法的返回结果,让其变得更合理。 比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
3. 让Object操作都变成函数行为。 某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect有对应的方法Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)。
4. Reflect对象的方法与Proxy对象的方法一一对应。 无论Proxy对象怎么修改默认行为,你总可以在Reflect上获取默认行为。
个人总结:之前Object太臃肿了,现在设计Reflect是为了规范js的对象操作。
说明 | 方法 |
---|---|
禁止往对象添加属性,返回对象 | Object.preventExtensions(Obj) |
禁止设置对象属性的配置 和 属性值的删除,就是将configurable设为false | Object.seal(obj) |
禁止对象属性的修改,冻结对象,不允许修改属性值,是浅层冻结。 | Object.freeze(obj) |