一文带你深入掌握 ES6 Proxy 数据代理
前言
在ES6
之前,我们常使用Object.defineProperty()
方法来进行数据代理从而实现数据的劫持(如:Vue2的响应式原理)
而在ES6
之后诞生了一个全新的对象(构造器):Proxy
,作为数据代理而言,它比Object.defineProperty()
要强大许多,这也是为什么Vue3
的响应式要使用Proxy
来做的原因
这篇文章将深入去研究Proxy
代理,让我们开始吧!
一、为什么要使用代理?
之所以使用代理,就是不希望用户能够直接访问某个对象,直接操作对象的某个成员(因为这样是不可控的,我们不知道用户在访问操作哪一个对象)
通过代理,我们可以拦截用户的访问(称为数据劫持),拦截住后我们就可以对数据进行一些处理,比如做一些数据的验证或者像 Vue 一样做一些视图更新的额外操作,之后再允许用户的访问操作(因为我们拦截了用户的每一次访问,这样用户操作对象就完全是在我们可控的范围内)
简单来说,就是我们希望用户在访问对象时我们能够清除的知道用户在访问什么并且能够在中间做一些我们自己的操作
二、Proxy 是什么?
Proxy
对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
Proxy
是ES6
中新增的一个构造函数,也可以叫类,通过new
操作符调用使用。但在JavaScript
中函数和类本质上也是对象,所以我们也能将Proxy
直接作为对象访问它的属性进行操作,如Proxy.revocable()
但需要注意的是,Proxy
并没有prototype
原型对象:
ECMA 的官方说明: 因为 Proxy
构造出来的实例对象仅仅是对目标对象的一个代理,所以 Proxy
在构造过程中是不需要 prototype
进行初始化的
其他构造函数之所以需要
prototype
,是因为构造出来的对象需要一些初始化的成员,所以将这些成员定义到了protoype
上
三、基础语法
👉 参数:
target
:Proxy 会对 target 对象进行包装。它可以是任何类型的对象,包括内置的数组,函数甚至是另一个代理对象。handler
:它是一个对象,它的属性提供了某些操作发生时所对应的处理函数。一个空的
handler
参数将会创建一个与被代理对象行为几乎完全相同的代理对象。通过在handler
对象上定义一组处理函数,你可以自定义被代理对象的一些特定行为。例如, 通过定义 get() 你就可以自定义被代理对象的 属性访问器。
👉 返回值:
proxyTarget
:经过 Proxy 包装后的 target 对象
👉 基础使用:
需要注意的是,返回值proxyTarget
并不是target
的深拷贝,而只是浅引用:
四、handler 处理函数
Proxy
代理的灵魂就在于它的第二个参数:handler
对象,在这个对象内我们可以定义一些处理函数来进行数据劫持,从而实现一些额外的操作
🎉 apply() 拦截函数的调用
handler.apply()
方法用于拦截函数的调用
👉 语法:
👉 参数:
下面的参数将会传递给
apply()
方法,this
绑定在handler
上
target
:目标对象(函数)thisArg
:被调用时的上下文对象argumentsList
:被调用时的参数数组
👉 返回值:
apply
方法可以返回任何值
👉 使用:
🎉 construct() 拦截 new 操作符
handler.construct()
方法用于拦截 new
操作符。为了使 new
操作符在生成的 Proxy
对象上生效,用于初始化代理的目标对象自身必须具有 [[Construct]]
内部方法(即 new target
必须是有效的)
👉 语法:
👉 参数:
下面的参数将会传递给
construct
方法,this
绑定在handler
上
target
:目标对象。argumentsList
:constructor 的参数列表。newTarget
:最初被调用的构造函数,就上面的例子而言是 newTarget
👉 返回值:
construct
方法必须返回一个对象
👉 使用:
🎉 get() 拦截对象属性的读取操作
handler.get()
方法用于拦截对象的读取属性操作
👉 语法:
👉 参数:
以下是传递给
get
方法的参数,this
上下文绑定在handler
对象上
target
:目标对象。property
:被获取的属性名。receiver
:Proxy 或者继承 Proxy 的对象
👉 返回值:
get
方法可以返回任何值,这些返回值就是用户真正获取到的属性值
👉 使用:
🎉 set() 拦截对象属性的修改/设置操作
handler.set()
方法是设置属性值操作的捕获器
👉 语法:
👉 参数:
下面的参数将会传递给
set()
方法,this
绑定在handler
上
target
:目标对象property
:将被设置的属性名或 Symbol。value
:新属性值。receiver
:最初被调用的对象。通常是proxy
本身,但handler
的set
方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是proxy
本身)假设有一段代码执行
obj.name = "Ailjx"
,obj
不是一个proxy
,且自身不含name
属性,但是它的原型链上有一个proxy
,那么,那个proxy
的set()
处理器会被调用,而此时,obj
会作为receiver
参数传进来
👉 返回值:
set()
方法应当返回一个布尔值
返回
true
代表属性设置成功在严格模式下,如果
set()
方法返回false
,那么会抛出一个 TypeError 异常
👉 使用:
🎉 deleteProperty()拦截对象属性的删除操作
handler.deleteProperty()
方法用于拦截对对象属性的删除操作
👉 语法:
👉 参数:
deleteProperty
方法将会接受以下参数。this
被绑定在handler
上
target
:目标对象property
:待删除的属性名。
👉 返回值:
deleteProperty
必须返回一个 Boolean
值,表示了该属性是否被成功删除
👉 使用:
🎉 has() 拦截 in 操作符
handler.has()
方法是针对 in
操作符的代理方法
👉 语法:
👉 参数:
下面的参数将会传递给
has()
方法,this
绑定在handler
上
target
:目标对象prop
:需要检查是否存在的属性的名称
👉 返回值:
has
方法返回一个 boolean
值
👉 使用:
🎉 更多处理函数
handler
对象内还有以下处理函数:
defineProperty():拦截对对象的
Object.defineProperty()
操作getOwnPropertyDescriptor():
Object.getOwnPropertyDescriptor
调用劫持getPrototypeOf() :拦截对象原型的读取操作
isExtensible():拦截对对象的
Object.isExtensible()
ownKeys():
Object.getOwnPropertyNames
和Object.getOwnPropertySymbols
的调用劫持preventExtensions():对
Object.preventExtensions()
的拦截setPrototypeOf():拦截
Object.setPrototypeOf()
五、可撤销代理
在前面说过:能将Proxy
直接作为对象访问它的属性进行操作,如Proxy.revocable()
这个Proxy.revocable()
方法可以用来创建一个可撤销的代理对象,先看下面这个例子:
Proxy.revocable()
方法具有和Proxy
一样的两个参数:target
目标对象和handler
对象
但它的返回值有点特殊,它返回一个包含了代理对象本身和它的撤销方法的可撤销 Proxy
对象,其结构为:
proxy
:表示新生成的代理对象本身,和用一般方式new Proxy(target, handler)
创建的代理对象没什么不同,只是它可以被撤销掉revoke
:撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象一旦某个代理对象被撤销,它将变得几乎完全不可调用,在它身上执行任何的可代理操作都会抛出
TypeError
异常(可代理操作指的就是我们在handler
对象函数属性上能拦截到的操作,一共有 14 种,执行这 14 种以外的情况不会报错)一旦被撤销,这个代理对象便不可能被直接恢复到原来的状态,同时和它关联的目标对象以及处理器对象都有可能被垃圾回收掉。再次调用撤销方法
revoke()
则不会有任何效果,但也不会报错
👉 示例:
结语
深入了解了Proxy
之后,真的会被它强大的代理拦截功能所折服,在它的基础上我们可以创建几乎任何我们想要的响应式系统,它像是一个硕大的地基,至于地基之上需要建筑什么,全由我们自己掌握!
看完本篇文章相信你已经对Proxy
有了深入的理解,学习Proxy
是我们学习像Vue3
这种响应式原理的第一步,大家加油!
如果本篇文章对你有所帮助,还请客官一件四连!❤️
版权声明: 本文为 InfoQ 作者【海底烧烤店ai】的原创文章。
原文链接:【http://xie.infoq.cn/article/806c0536cf8c0df19e6e98bde】。文章转载请联系作者。
评论