53. ES6+ Proxy
1. 前言
本节我们将学习 ES6 的新增知识点 ——Proxy,Proxy 是代理的意思。Proxy 是一个对象,用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。这是 MDN 上的定义,但是不容易理解,想要理解 Proxy 我们首先需要知道什么是代理?
在日常开发中比较常见的代理常见有,使用 Charles 代理抓包、nginx 服务器的反向代理,以及 VPN 等,都用到了代理。什么是代理呢?我们先看一张图:
上图是客户端访问网络的示意图,客户端不能直接访问网络,它只能先访问代理服务器,只有代理服务器才能有权限访问,然后代理服务器把客户端请求的信息转发给目标服务器,最后代理服务器在接收到目标服务器返回的结果再转发给客户端,这样就完成了整个请求的响应过程。这是现在大多数服务器的架构,我们可以把上图的 Proxy Server 理解为 Nginx。代理有正向代理和反向代理,有兴趣的小伙伴可以去深入了解一下。
本节说的 Proxy 就是作用在 JavaScript 中的一种代理服务,代理的过程其实就是一种对数据的劫持过程,Proxy 可以对我们定义的对象的属性进行劫持,当我们访问或设置属性时,会去调用对应的钩子执行。在 ES5 中我们曾学习过Object.defineProperty()
它的作用和 Proxy 是相同的,但是Object.defineProperty()
存在一些性能问题,Proxy 对其进行了升级和扩展更加方便和易用。本节我们将学习 Proxy 的使用。
2. Object.defineProperty()
在学习 Proxy 之前,我们先来回归一下 ES5 中的Object.defineProperty()
,接触过前端框架的同学应该都知道 Vue 和 React,其中 Vue 中的响应式数据底层就是使用Object.defineProperty()
这个 API 来实现的。下面是Object.defineProperty()
的语法。
Object.defineProperty()
会接收三个参数:
- obj 需要观察的对象;
- prop 是 obj 上的属性名;
- descriptor 对 prop 属性的描述。
当我们去观察一个对象时需要在 descriptor 中去定义属性的描述参数。在 descriptor 对象中提供了 get 和 set 方法,当我们访问或设置属性值时会触发对应的函数。
上面的代码中,我们使用一个变量 value 来保存值,这里需要注意的是,不能直接使用 obj 上的值,否则就会出现死循环。
Object.defineProperty()
是 Vue2 的核心, Vue2 在初始化时会对数据进行劫持,如果劫持的属性还是对象的话需要递归劫持。下面我们把 Vue2 中数据劫持的核心代码写出来。
上面代码的核心是 defineReactive 方法,它是递归的核心函数,用于重新定义对象的读写。从上面的代码中我们发现Object.defineProperty()
是有缺陷的,当观察的数据嵌套非常深时,这样是非常耗费性能的,这也是为什么现在 Vue 的作者极力推广 Vue3 的原因之一,Vue3 的底层使用了 Proxy 来代替Object.defineProperty()
那 Proxy 具体有什么好处呢?
3. Proxy
首先我们来看下 Proxy 是如何使用的,语法:
Proxy 对象是一个类,需要通过 new 去实例化一个 Proxy 对象,它接收的参数比较简单,只有两个:
- target:需要使用 Proxy 进行观察的目标对象;
- handler:对目标对象属性进行处理的对象,包含了处理属性的回调函数等。
对比上面的Object.defineProperty()
API 直观的看 Proxy 做了一些精简,把对象、属性和值作为 get 和 set 的参数传入进去,不必考虑死循环的问题了。这是直观的感受。
上面我们使用了Object.defineProperty()
API 简单地实现了 Vue2 的响应式原理,那么 Vue 使用 Proxy 是怎么实现的呢?它带来了哪些好处呢?下面我们看实现源码:
上面的代码是从 Vue3 中摘出来的 reactive 函数的实现,我们可以直观地看到没有对 target 进行递归循环去创建观察对象。而且,当我们对 obj 下的 a 属性设置值时,执行 get 函数,这是为什么呢?这就是 Proxy 的优点,在对 obj 下属性设置值时,首先需要调用 set 方法获取 target 下 obj 的值,然后判断 obj 又是一个对象再去调用 reactive 函数进行观察。这样就不需要递归地去对嵌套数据进行观察了,而是在获取值的时候,判断获取的值是不是一个对象,这样极大地节约了资源。
4. 小结
本节主要通过代理和Object.defineProperty()
API 的学习来理解 ES6 的新增知识点 ——Proxy,并且通过 Vue2 和 Vue3 实现响应式原理来对比Object.defineProperty()
和 Proxy 的优缺点,从而更深入地理解 Proxy。