on
Proxy
说实话这对我来说是一个基本没啥印象的ES6特性
。
平时开发中基本用不到,工作中也很少看到有人使用Proxy
。
而最近随着面试的增多,在问道Vue双向数据绑定
时,总是会听到Proxy
,所以今天来学一下~
Proxy是什么?
先来放上MDN官方的解释:
The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object.
翻译:Proxy对象能让你给其他对象创建一个可以拦截和重定义基本操作的代理(proxy)
那定义里的基本操作
都有哪些呢?
基本操作包含查找,赋值,枚举,函数调用。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
Proxy该怎么用呢?
先来看看基本用法
const proxy = new Proxy(target, handler);
target
参数表示所要拦截的目标对象,handler
则是用来配置我们拦截后要做些什么。
我这里就给出一些最基本的使用方法,基本的使用并不是今天讨论的主题,可以参考ES6简介-proxy
const testObj = { a: 1, b: 2, c: 3 };
const testProxy = new Proxy(testObj, {
get: (target, name) => {
console.log('target: ', target);
console.log('name: ', name);
return 4;
},
set: (target, name) => {
console.log('set: ', name);
return target[name];
}
});
testObj.a
// 1
testProxy.a
// 4
// target: {a: 1, b: 2, c: 3}
// name: a
testProxy.d = 11
// set: d
// 11
可以看到handler
其实也是一个对象,用于配置这层拦截要做些什么的。通过配置get / set
可以完成对文章开头所说的查找 / 赋值
的拦截。
proxy还能对很多操作进行拦截,这里就不一一展开了
Proxy能做什么?
接下来我举几个Proxy的"骚操作"
数组的负值索引
JavaScript不支持数组索引给负值
来进行反方向的取值
const a = [1, 2, 3];
a[-1] // undefined
用Proxy就可以实现这一操作,接下来我们就看看该如何实现~
首先,我们肯定是要拦截值的获取
这一步,那么也就是get
const testArray = [1, 2, 3, 4]
const arrayProxy = new Proxy(testArray, {
get: (target, propKey) => {
console.log('target: ', target, 'propKey: ', propKey);
return target[propKey];
}
});
arrayProxy[1]
// target: [ 1, 2, 3, 4 ] propKey: 1
// 2
可以看到如果是拦截数组
,那么propKey
就是该数组的下标。
但是需要注意的是,JavaScript 中对象的 key
值只能是 String 或 Symbol
类型的。
所以我们所给的下标1
其实是字符串1
,而这次拦截的主要目的就是将负值下标
那么我们在转换负值的时候,就不能直接进行数值运算。
所以要先判断是否能转换字符串为数字
。
const testArray = [1, 2, 3, 4]
const arrayProxy = new Proxy(testArray, {
get: function (target, propKey) {
if (
Number(propKey) != NaN &&
Number.isInteger(Number(propKey)) &&
Number(propKey) < 0
) {
propKey = String(target.length + Number(propKey));
}
return target[propKey];
},
});
arrayProxy[-1] // 4
这样就能完成负值也能作为数组索引的功能了
这里只是给出一种思路,可以看到当我们拦截下了对象的一些操作之后,能做的事情是很多很多的!
需要注意的是
兼容性
Proxy源自ES6,如果要使用Babel编译成ES5的语法, 那么就无法使用Proxy了。
this
Proxy在大部分情况下都能满足,但是有一种情况需要特别注意,就是this!
当对象被Proxy代理后,目标对象内部的this关键字会指向 Proxy 代理。
const test = {
testThis: function () {
console.log(this === testProxy);
}
}
const testProxy = new Proxy(test, {});
test.testThis(); // false
testProxy.testThis(); // true
可以看到例子中this
是指向testProxy
的,而不是对象本身。
但是还需要注意,如果testThis
如果是箭头函数的话,this连Proxy都不指向了,是因为箭头函数本身也有this的指向特点。
还有一种特殊情况
const _data = new WeakMap();
class Test {
constructor(data) {
_data.set(this, data);
}
get data() {
return _data.get(this);
}
}
const testData = new Test('weakMapTest');
testData.data // 'weakMapTest'
const proxy = new Proxy(testData, {});
proxy.data // undefined
这个例子可能有点绕,我们来一步一步看一下
- Test类的
constructor
所做的事情是给类外部的WeakMap
设置一个key为this
,他对应的value为'weakMapTest'
。 - 所以通过
Test
类的实例testData
获取data时,会触发类内的get方法,返回的是外部_data
这个weakMap的get,也就顺理成章的拿到了key为this
的value ->'weakMapTest'
- 但是现在我们来代理一下
testData
,刚才说到,代理过的对象,this会指向proxy
而不是实例对象本身,而这个时候proxy本身没有设置任何的getter
所以返回就是undefined了。
这个例子有一个特点就是WeakMap
,_data
是外部的一个WeakMap
。由于通过proxy.name
访问时,this指向proxy,导致无法取到值,所以返回undefined
。