带你了解ES6的代理对象proxy!!!

4,727 阅读4分钟

相信大家都听说过proxy这个词把!

从单词的意思来看就是指代理,我们也会在一些框架上使用这个‘词’,配置一些脚手架的代理。(不管是Vue还是React,都可以通过proxy进行配置代理)

但今天我们介绍的是ES6的新特性Proxy代理对象。

一、为什么使用Proxy 以及 Proxy的概念

首先我先说一说它的强大之处:

  • 使用它可以修改js对象的基础行为。
  • 它提供了一种途径,让我们能够自己实现一些对象的基本操作,并创建一个比普通对象还要高级很多的代理对象。
let proxy = new Proxy(target,handlers)
//target 所要拦截的目标对象
//handlers 为处理器对象,用来定制拦截行为。

代理对象创建的是一个没有自己状态或行为的对象。

  var proxy = new Proxy(
    { time: 10 },
    {
      get: function (target, propKey, receiver) {
        console.log(target, propKey, receiver);
        return 999;
      },
    }
  );
  console.log(proxy.time);//999
  proxy.time = 11; //此处执行的时原对象上的set方法。
  proxy.time;

  console.log("-------------------------");
  
  var proxy1 = new Proxy(
    { time: 10 },
    {
      set: function (target, propKey, receiver) {
        target[propKey] = receiver;
        console.log(target, propKey, receiver);
      },
    }
  );

  console.log(proxy1.time); //10
  proxy1.a = 11; //此处执行的时代理的set方法。
  console.log(proxy1.a); //11

通过上方这段代码,我们不难发现,当我们为一个js对象,配置代理时并且代理对象配置了一些方法,这些方法会覆盖到目标对象上,当我们进行一些操作时,会执行代理对象的方法,而不会执行原对象上的方法和属性。只有没为原对象配置该属性或方法上代理的时候,才会调用对象自己的属性或方法。

注意: 要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target。这也可以解释上方的内容。

二、Proxy让我们可以定义一个可撤销的代理

可撤销的代理不使用Proxy构造函数创建,而是使用Proxy.revocable()工厂函数。这个函数返回一个对象,其中包含一个代理对象和一个revoke()函数,一旦调用revoke()函数,代理立刻失效。

  function func() {
    return 111;
  }

  let { proxy, revoke } = Proxy.revocable(func, {});
  console.log(proxy()); //111
  revoke();
  console.log(proxy()); //Uncaught TypeError: Cannot perform 'apply' on a proxy that has been revoked

这种机制非常有利于一种情况,就是在我们使用一些不信任的第三方库时候。如果必须向一个自己不信任的库传递一个函数,这时候当然不能直接将自己的函数传递过去,我们可以向其传递一个可以撤销的代理,这样同样可以达到效果,当使用完这个库,我们就将这个代理撤销。保证了安全性

三、Proxy中this的问题

虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。

const target = {
    m: function () {
      console.log(this === proxy);
    }
  };
  const handler = {};
  
  const proxy = new Proxy(target, handler);
  
  target.m() // false
  proxy.m()  // true

四、Proxy支持的拦截操作

  • get(target, propKey, receiver) :拦截对象属性的读取,比如proxy.fooproxy['foo']
  • set(target, propKey, value, receiver) :拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
  • has(target, propKey) :拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey) :拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target) :拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey) :拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc) :拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target) :拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target) :拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target) :拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto) :拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args) :拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args) :拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

这些拦截操作就不一一讲啦,我只讲一些自己的看法。 如果大家想了解拦截操作,可以去看一下阮一峰老师的博客: es6.ruanyifeng.com/#docs/proxy


最后,也正因为Proxy可以对整个对象进行代理,不需要像Object.defineProperty()那样递归遍历对象的每一个属性来为它们添加get、set达到监听效果。Vue3使用Proxy代替了Object.defineProperty()实现数据的劫持,优化了代码,减少了大部分性能。(Vue3这部分底层暂时只是了解,还没有深入学习!!!)


最后真切的希望这篇文章可以给大家带来一定的帮助。

周末快乐!