1. Proxy
1.1 proxy是什么
MDN给出的定义是: proxy对象用于定义基本操作的自定义行为, 例如属性查找, 赋值, 枚举, 函数调用等
proxy
捕获对其目标对象
进行的调用和操作, 然后可以更优雅的处理这些调用和操作, 它在目标对象
周围创建了一个无法检测的屏障, 将所有操作重定向到处理程序对象
基本用法
使用new Proxy()
来创建, 该构造函数接受两个必要的参数: 目标对象target
和处理程序对象handler
let target = {}
let handler = {
// target操作的处理方法
}
let proxyObject = new Proxy(target, handler)
@理解:
proxy相当于一个黑匣子, 封装对目标对象的处理, proxy的实例对象相当于对外暴露的接口,外部人员对目标对象的操作不再直接作用于目标对象, 而是通过proxy实例对象重定向到proxy内部的处理方法上(像get set等) , 最后再将处理后的最终结果返回给proxy实例
1.2 常用的代理方法
这些代理方法其实就是proxy对原生方法的监听, 然后用内部对应的方法去处理
1.2.1 get方法
可以接收三个参数: 目标对象、属性名、[ proxy实例 ]
get方法可以看作是对目标对象
取
操作的代理
let target = { id: 100 }
let handler = {
get: function (target, prop) {
if (target.hasOwnProperty(prop)) {
return target[prop]
}
}
}
let proxy = new Proxy(target, handler)
console.log(proxy.id)
1.2.2 set方法
可以接收四个参数: 目标对象, 属性名, 属性值, [ proxy实例 ]
set方法可以看作是对目标对象某个属性的赋值代理
let target = { id: 100 }
let handler = {
set: function (target, prop, value) {
if (value > 500) {
console.log('超出最大值');
} else {
target[prop] = value
}
}
}
let proxy = new Proxy(target, handler)
proxy.id = 600 // 超出最大值
proxy.name = '猫13'
proxy.id = 200
console.log(proxy.id) // 200
console.log(proxy.name) // 猫13
1.2.3 deleteProperty方法
接收两个参数: 目标对象, 待删除的属性名
相当于delete obj.prop
let person = {
usr1: [1, {
name: '猫13'
}],
usr2: [2, {
name: '猫14'
}],
}
// 之前删除是这样的
// delete person.name 下面用Proxy来拦截
var handler = {
deleteProperty(target, key) {
if (target[key][0] === 1) {
console.log('usr1不能被删除');
} else {
delete target[key]
}
}
};
let proxy = new Proxy(person, handler);
delete proxy.usr1 // usr1不能被删除
delete proxy.usr2
console.log(proxy) // Proxy {usr1: Array(2)}
1.2.4 apply方法
该方法主要用于拦截函数的调用
接受的第一个参数是目标函数
let t1 = function () {
console.log('hi');
}
let handler = {
apply: function (sayHi) {
console.log(`你的${t1.name}方法被拦截了`)
}
}
let proxy = new Proxy(target, handler)
proxy() // 你的t1方法被拦截了
1.2.5 has方法
has()
方法是针对 in
操作符的代理方法
接收两个参数: 目标对像, 属性名
let target = { iid: 100, uid: 200 }
let handler = {
has: function (target, prop) {
if (prop[0] === 'i') {
return false
}
return prop in target
}
}
let proxy = new Proxy(target, handler)
console.log('iid' in proxy); // false
console.log('uid' in proxy); // true
1.2.6 Proxy的13种捕获器:
handler.get():属性读取操作的捕捉器。
handler.set():属性设置操作的捕捉器。
handler.has():in 操作符的捕捉器。
handler.deleteProperty():delete 操作符的捕捉器。
handler.getPrototypeOf():Object.getPrototypeOf 方法的捕捉器。
handler.setPrototypeOf():Object.setPrototypeOf 方法的捕捉器。
handler.isExtensible():Object.isExtensible 方法的捕捉器。
handler.preventExtensions():Object.preventExtensions 方法的捕捉器。
handler.getOwnPropertyDescriptor():Object.getOwnPropertyDescriptor 方法的捕捉器。
handler.defineProperty():Object.defineProperty 方法的捕捉器。
handler.ownKeys():Object.getOwnPropertyNames 方法和Object.getOwnPropertySymbols 方法的捕捉器。
handler.apply():函数调用操作的捕捉器。(函数也是一个对象,这里就对函数调用时进行监听)
handler.construct():new 操作符的捕捉器。(函数执行new操作符的时候进行监听)
2. Reflect
在 Javascript 的世界中,一切皆对象,比如函数也是对象,所以调用函数也是对象的基本操作,Proxy 只能够拦截对一个对象的基本操作。
什么是基本操作呢?
obj.fn(); // 这个其实是一个复合操作 包括 obj.fn 和 fn()
Reflect是一个对象,字面意思是”反射”。
2.1 Reflect有什么用?
它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法; 比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf();
如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?
这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的;所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上。
那么Object和Reflect对象之间的API关系,可以参考MDN文档:
MDN Web Docswww
、
const objProxy = new Proxy(obj,{
get:function(target,key){
console.log(`监听到访问${key}属性`,target)
return Reflect.get(target,key) //改为Reflect.get
},
set:function(target,key,newValue){
console.log(`监听到给${key}属性设置值`,target)
Reflect.set(target,key,newValue) //改为Reflect.set
}
})
console.log(objProxy.name)
objProxy.name = 'wx'
2.2 用Reflect的好处是什么?
之前的方式是说到底还是在操作原对象,因为都是在用target、key等直接去操作,改用Reflect就真正意义上不直接操作原对象。
在有的时候Reflect会更加有用。比如:使用Object.freece(obj)将对象冻结后,之前的方式就无法判断出设置值到底是设置成功了还是失败了。而Reflect可以有返回值,代表是设置成功还是失败。
示例
const istrue = Reflect.set(target,key,newValue)
const result = istrue?'设置成功':"设置失败"
2.3 常见的Reflect方法
Reflect.getPrototypeOf(target):类似于 Object.getPrototypeOf()。
Reflect.setPrototypeOf(target, prototype):设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true。
Reflect.isExtensible(target):类似于 Object.isExtensible()
Reflect.preventExtensions(target):类似于 Object.preventExtensions()。返回一个Boolean。
Reflect.getOwnPropertyDescriptor(target, propertyKey) :类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined.
Reflect.defineProperty(target, propertyKey, attributes):和 Object.defineProperty() 类似。如果设置成功就会返回 true
Reflect.ownKeys(target):返回一个包含所有自身属性(不包含继承属性)的数组。(类似于Object.keys(), 但不会受enumerable影响)。
Reflect.has(target, propertyKey) :判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
Reflect.get(target, propertyKey[, receiver]):获取对象身上某个属性的值,类似于 target[name]。
Reflect.set(target, propertyKey, value[, receiver]):将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
Reflect.deleteProperty(target, propertyKey) :作为函数的delete操作符,相当于执行 delete target[name]。
Reflect.apply(target, thisArgument, argumentsList) :对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和Function.prototype.apply() 功能类似。
Reflect.construct(target, argumentsList[, newTarget]):对构造函数进行 new 操作,相当于执行 new target(…args)。
2.4 receiver参数
Proxy的get、set捕获器还有receiver参数,receiver代表的就是代理对象本身。那它存在的作用是什么呐?
请看下面示例:
const obj = {
_name:'cj',
age:18,
get name(){ //将存取描述符写入对象体内
return this._name //this === obj
},
set name(newValue){
this._name = newValue
}
}
const objProxy = new Proxy(obj,{
get:function(target,key){
console.log(`监听到访问${key}属性`)
return Reflect.get(target,key)
},
set:function(target,key,newValue){
console.log(`监听到给${key}属性设置值`)
Reflect.set(target,key,newValue)
}
})
console.log(objProxy.name)
通过objProxy.name去访问属性首先会进入get捕获器,然后通过Reflect会执行name存取描述符,在name存取描述符中,会通过this访问_name。
这里进行了两次属性的访问,一次是objProxy的name属性访问,一次是obj的_name属性访问,那这里会打印几次?
答案是:一次
因为objProxy.name访问是会触发捕获器的,这里会打印一次。然后通过this访问_name是不会再触发捕获器的,因为捕获器只会当访问代理对象的属性时才会触发,而通过this访问,这里的this是指向obj的,而不是objProxy,所以不会触发捕获器。
但是我们要做到通过this访问对象内部属性也要能触发捕获器,这个时候receiver参数就排上用场了。将receiver传递给Reflect,就能将this指向为objProxy代理对象了,再通过this访问就会触发捕获器了。这样使得无论是外部访问objProxy的属性还是obj内部通过this访问属性都会经过捕获器。
配合使用receiver
const obj = {
_name:'cj',
age:18,
get name(){
return this._name //this === objProxy
},
set name(newValue){
this._name = newValue
}
}
const objProxy = new Proxy(obj,{
get:function(target,key,receiver){
console.log(`监听到访问${key}属性`,target[key],target)
return Reflect.get(target,key,receiver) //将receiver传递给Reflect.get
},
set:function(target,key,newValue,receiver){
console.log(`监听到给${key}属性设置值`,target[key],target)
Reflect.set(target,key,newValue,receiver)
}
})
console.log(objProxy.name)
//第一次打印:console.log(`监听到访问name属性`,'cj',{_name:"cj",age:18})
//第二次打印:console.log(`监听到访问_name属性`,'cj',{_name:"cj",age:18})
将receiver传递给Reflect,然后Reflect执行get存取描述符,这个时候的this就是指向objProxy这个代理对象了。通过this访问也就会触发捕获器了。
总结
Proxy能很好的监听整个对象的变化,vue3放弃了Object.defineproperty改用Proxy,弥补了vue2响应式无法对引用数据类型的增添、删除无法监听到的缺陷。vue2只能通过提供的$setAPI手动将添加或删除的属性加入响应式中