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

这个例子可能有点绕,我们来一步一步看一下

这个例子有一个特点就是WeakMap_data是外部的一个WeakMap。由于通过proxy.name访问时,this指向proxy,导致无法取到值,所以返回undefined

参考