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