Proxy
为什么要使用代理?
之所以使用代理,就是不希望用户能够直接访问某个对象,直接操作对象的某个成员(因为这样是不可控的,我们不知道用户在访问操作哪一个对象)
通过代理,我们可以拦截用户的访问(称为数据劫持),拦截住后我们就可以对数据进行一些处理,比如做一些数据的验证,之后再允许用户的访问操作(因为我们拦截了用户的每一次访问,这样用户操作对象就完全是在我们可控的范围内)
简单来说,就是我们希望用户在访问对象时我们能够清除的知道用户在访问什么并且能够在中间做一些我们自己的操作
Proxy是什么?
Proxy
是 ES6
中新增的一个构造函数,也可以叫类,通过new
操作符调用使用。
Proxy
对象用于创建目标对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
需要注意的是,Proxy
并没有prototype
原型对象
官方说明是因为 Proxy
构造出来的实例对象仅仅是对目标对象的一个代理,所以 Proxy
在构造过程中是不需要 prototype
进行初始化的
其他构造函数之所以需要
prototype
,是因为构造出来的对象需要一些初始化的成员,所以将这些成员定义到了protoype
上
基础语法
const proxyTarget = new Proxy(target, handler) |
参数:
target
:Proxy 会对 target 对象进行包装。它可以是任何类型的对象,包括内置的数组,函数甚至是另一个代理对象。handler:它是一个对象,它的属性提供了某些操作发生时所对应的处理函数。
一个空的
handler
参数将会创建一个与被代理对象行为几乎完全相同的代理对象。通过在handler
对象上定义一组处理函数,你可以自定义被代理对象的一些特定行为。
返回值:
proxyTarget
:经过Proxy包装后的target对象
基础使用:
const obj = { |
handler处理函数
Proxy
代理的灵魂就在于它的第二个参数:handler
对象,在这个对象内我们可以定义一些处理函数来进行数据劫持,从而实现一些额外的操作
1.get() 拦截对象属性的读取操作
handler.get()
方法用于拦截对象的读取属性操作
语法:
var proxyTarget = new Proxy(target, { |
参数:
以下是传递给
get
方法的参数
target
:目标对象。property
:被获取的属性名。receiver
:Proxy 或者继承 Proxy 的对象
返回值:
get
方法可以返回任何值,这些返回值就是用户真正获取到的属性值
使用:
const obj = { |
2.set() 拦截对象属性的修改/设置操作
handler.set()
方法是设置属性值操作的捕获器
语法:
const p = new Proxy(target, { |
参数:
下面的参数将会传递给
set()
方法
target
:目标对象property
:将被设置的属性名value
:新属性值。receiver
:最初被调用的对象。通常是proxy
本身,但handler
的set
方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是proxy
本身)假设有一段代码执行
obj.name = "Ailjx"
,obj
不是一个proxy
,且自身不含name
属性,但是它的原型链上有一个proxy
,那么,那个proxy
的set()
处理器会被调用,而此时,obj
会作为receiver
参数传进来
返回值:
set()
方法应当返回一个布尔值
- 返回
true
代表属性设置成功 - 在严格模式下,如果
set()
方法返回false
,那么会抛出一个 TypeError 异常
使用:
const obj = { |
3.deleteProperty()拦截对象属性的删除操作
handler.deleteProperty()
方法用于拦截对对象属性的删除操作
语法:
var p = new Proxy(target, { |
参数:
deleteProperty
方法将会接受以下参数。
property
:待删除的属性名。
返回值:
deleteProperty
必须返回一个 Boolean
值,表示了该属性是否被成功删除
使用:
const obj = { |
更多处理函数
handler
对象内还有以下处理函数:
- defineProperty():拦截对对象的
Object.defineProperty()
操作 - apply(): 拦截函数的调用
- getOwnPropertyDescriptor():
Object.getOwnPropertyDescriptor
调用劫持 - getPrototypeOf() :拦截对象原型的读取操作
- isExtensible():拦截对对象的
Object.isExtensible()
- ownKeys():
Object.getOwnPropertyNames
和Object.getOwnPropertySymbols
的调用劫持 - preventExtensions():对
Object.preventExtensions()
的拦截 - setPrototypeOf]():拦截
Object.setPrototypeOf()
可撤销代理
Proxy.revocable()
方法可以用来创建一个可撤销的代理对象
// 创建可撤销的代理 |
Proxy.revocable()
方法具有和Proxy
一样的两个参数:target
目标对象和handler
对象
但它的返回值有点特殊,它返回一个包含了代理对象本身和它的撤销方法的可撤销 Proxy
对象,其结构为:
{"proxy": proxy, "revoke": revoke} |
proxy
:表示新生成的代理对象本身,和用一般方式new Proxy(target, handler)
创建的代理对象没什么不同,只是它可以被撤销掉revoke
:撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象一旦某个代理对象被撤销,它将变得几乎完全不可调用,在它身上执行任何的可代理操作都会抛出
TypeError
异常(可代理操作指的就是我们在handler
对象函数属性上能拦截到的操作,一共有14种,执行这14种以外的情况不会报错)一旦被撤销,这个代理对象便不可能被直接恢复到原来的状态,同时和它关联的目标对象以及处理器对象都有可能被垃圾回收掉。再次调用撤销方法
revoke()
则不会有任何效果,但也不会报错
示例:
// 创建可撤销的代理 |
这种机制非常有利于一种情况,就是在我们使用一些不信任的第三方库时候。如果必须向一个自己不信任的库传递一个函数,这时候当然不能直接将自己的函数传递过去,我们可以向其传递一个可以撤销的代理,这样同样可以达到效果,当使用完这个库,我们就将这个代理撤销。保证了安全性