有什么用?
提供了拦截并向基本操纵嵌入额外行为的能力。
可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。 在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。
基本使用
创建空代理
new Proxy(目标对象,处理程序对象)
- 返回一个代理对象
- 两个参数缺一不可,缺少一个会报错
//定义了两个对象
const obj1 = {//用作目标对象
name:'拉妮'
}
const obj2 = {//用作处理程序对象
}
//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)
测试:
console.log(obj1.name)//拉妮
console.log(test.name)//拉妮
obj1.name = '娇小拉妮'
console.log(obj1.name)//娇小拉妮
console.log(test.name)//娇小拉妮
test.name = '娇小拉妮!!!!!'
console.log(obj1.name)//娇小拉妮!!!!!
console.log(test.name)//娇小拉妮!!!!!
-
不管是对目标对象的操作还是对代理对象的操作,都会反应到两个对象身上。
-
目标对象和代理对象进行严格比较(===),并不相等。
Proxy.prototype 是 undefined,因此不可以对其使用 instanceof 操作符,会报错
console.log(target instanceof Proxy); // 报错
console.log(proxy instanceof Proxy); // 报错
定义捕获器
每个捕获器都对应一种基本操作,可以直接或间接在代理对象上调用。
实际是通过代理对象对于目标方法各类操作和行为的拦截
例:
//定义了两个对象
const obj1 = {//用作目标对象
name:'拉妮'
}
const obj2 = {//用作处理程序对象
get(){//get捕获器
console.log('被读取!!')
},
set(){//set捕获器
console.log('试图将其修改为'+arguments[2])
}
}
//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)
test.name//被读取!!
test.name = '娇小拉妮!'//试图将其修改为娇小拉妮!
反射-Reflect
-
Reflect-封装了原始行为
-
作用:简化基于自己的参数重建原始行为的工作
使用捕获的部分方法
const obj1 = {//用作目标对象
name:'拉妮'
}
const obj2 = {//用作处理程序对象
get(){
return Reflect.get(...arguments)
},
//get:Reflect.get //---简易写法
}
//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)
捕获所有方法,并将每个方法转发给对应反射API的空代理
即:使用捕获的全部方法
const obj1 = {//用作目标对象
name:'拉妮'
}
//test实现了对obj1的代理
const test = new Proxy(obj1,Reflect)
捕获不变式
使用捕获器几乎可以改变所有基本方法的行为,但也不是没有限制。
捕获器不变式(trap invariant) 因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为。比如说,目标对象有一个不可配置且不可写的数据属性, 那么捕获器在返回一个与该属性不同的值时,就会抛出错误。
可撤销代理
不是使用new Proxy()创建,而是使用Proxy.revocable()创建并暴露出一个revoke()方法实现代理撤销。
const obj1 = {//用作目标对象
name:'拉妮'
}
const obj2 = {//用作处理程序对象
get:Reflect.get
}
//test实现了对obj1的代理
const {proxy,revoke} = Proxy.revocable(obj1,obj2)
console.log(proxy.name)//拉妮
revoke()//使用revoke()撤销代理----此操作是幂等的,调用多少次结构都一样
// console.log(proxy.name)//撤销代理之后再使用就会报错
使用反射API的好处
object上的方法适用于通用程序,反射方法则适用于更加精细与协调的对象控制与操作。
状态标记
用一等函数代替操作符
| 方法名 | 替代 |
|---|---|
Reflect.get | 可替代对象访问操作符 . |
Reflect.set | 可替代 = 赋值操作符 |
Reflect.has | 可替代 in 操作符 和 with() |
Reflect.deleteProperty | 可替代 delete 操作符 |
Reflect.construct | 可替代 new 操作符 |
安全地应用函数
- 设想一个场景,假设我们在通过
apply函数调用函数时,被调用的函数自身也定义了自己的apply属性,那么调用的结果肯定不是我们所期望的。这种问题可以通过使用定义在 Function 原型上的apply方法来解决。
Function.prototype.apply.call(Fun,thisVal,args)
复制代码
- 但是更好的解决方式则是使用反射API
Reflect.apply来解决
Reflect.apply(Fun,thisVal,args)
代理另一个代理
多层代理-->多层拦截网
const obj1 = {//用作目标对象
name:'拉妮'
}
const obj2 = {//用作处理程序对象
get:Reflect.get
}
//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)
const obj3 = {//用作处理程序对象
get:Reflect.get
}
//test2实现了对test的代理
const test2 = new Proxy(test,obj3)
console.log(test2.name)//拉妮
代理存在的问题
-
代理中的this值可能会存在问题
-
Date类型与代理不兼容---内置槽位不协同
部分反射方法
get()
读取属性值。
const obj1 = {//用作目标对象
name:'拉妮'
}
const obj2 = {//用作处理程序对象
get(){//get接收三个参数:目标对象,属性,代理对象
console.log(...arguments)
}
}
//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)
test.name
//{ name: '拉妮' } name { name: '拉妮' }
set()
修改属性值。
const obj1 = {//用作目标对象
name:'拉妮'
}
const obj2 = {//用作处理程序对象
set(){//接收到四个参数:目标对象,属性,接收的值,接收赋值的对象
console.log(...arguments)
}
}
//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)
console.log(test.name = '娇小拉妮')
//{ name: '拉妮' } name 娇小拉妮 { name: '拉妮' }
has()
在in操作符中被调用。
deleteProperty()
使用delete删除属性值时的调用。
const obj1 = {//用作目标对象
name:'拉妮'
}
const obj2 = {//用作处理程序对象
deleteProperty(){//接收到两个参数:目标对象,属性
console.log('检测到删除操作!!')
}
}
//test实现了对obj1的代理
const test = new Proxy(obj1,obj2)
delete test.name
//检测到删除操作!!
construct()
在使用new操作符时被调用。
ownKeys()
在使用Object,keys()及类似方法中被调用。
apply()
在调用函数时被调用。
。。。。。。
代理模式
代理的应用
跟踪属性访问
通过捕获get,set,has等操作,实现对于对象的监视。
隐藏属性
通过get和has实现对于目标对象属性的隐藏。
属性验证
赋值操作都会触发set捕获器,所有可以在set内部控制是否可以更改。
函数与构造函数参数验证
在apply拦截器内部验证参数,如控制函数只接收某一种类型的值。
数据绑定与可观察对象
通过代理把运行中原本不相关的部分联系到一起,实现不同代码间的相互操作。