ES6 Proxy

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。

var proxy = new Proxy(target, handler);

target:

    表示所要拦截的目标对象(必输)

handler:

    handler参数也是一个对象,用来定制拦截行为(必输)

Proxy 实例也可以作为其他对象的原型对象

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

let obj = Object.create(proxy);

同一个拦截器函数,可以设置拦截多个操作

 

    1. Proxy 支持的拦截操作一共13种(handler中的有效属性共13种)
      • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']
        • 参数:
          • target:目标对象
          • propKey:属性名
          • receiver:proxy 实例本身(严格地说,是操作行为所针对的对象)
        • var person = {
            name: "张三"
          };
          

          var proxy = new Proxy(person, {
          get: function(target, propKey) {
          if (propKey in target) {
          return target[propKey];
          } else {
          throw new ReferenceError("Prop name "" + propKey + "" does not exist.");
          }
          }
          });

          proxy.name // "张三"
          proxy.age // 抛出一个错误

          在 CodeSandBox 上尝试

        • get方法可以继承
          • let proto = new Proxy({}, {
              get(target, propertyKey, receiver) {
                console.log('GET ' + propertyKey);
                return target[propertyKey];
              }
            });
            

            let obj = Object.create(proto);
            obj.foo // "GET foo"

            在上面的代码中,变量obj继承了proto这个Proxy实例,所以只要是与obj有关的读取操作,get方法都会被执行

      • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值
          • 参数:
            • target:目标对象
            • propKey:属性名
            • value:属性值
            • receiver:Proxy 实例
          • let validator = {
              set: function(obj, prop, value) {
                if (prop === 'age') {
                  if (!Number.isInteger(value)) {
                    throw new TypeError('The age is not an integer');
                  }
                  if (value > 200) {
                    throw new RangeError('The age seems invalid');
                  }
                }
              }
            };
            

            let person = new Proxy({}, validator);

            person.age = 100;

            person.age // 100
            person.age = 'young' // 报错
            person.age = 300 // 报错

            set方法用来拦截赋值操作,可以使数据格式统一,避免格式不同所引发的报错,强制统一格式,提升逻辑的准确性

                         在 CodeSendBox 上尝试

        • 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(...)
          • 参数
            • target:目标对象
            • object:目标对象的上下文对象(this
            • args:目标对象的参数数组
          • var target = function () { return 'I am the target'; };
            var handler = {
              apply: function (target, ctx, args) {
                return 'I am the proxy';
              }
            };
            

            var p = new Proxy(target, handler);

            p()
            // "I am the proxy"

            apply方法用来拦截函数调用、call和apply操作,apply的目标对象是一个函数,当函数被调用时会进入apply函数中

            在 CodeSendBox 上尝试
          • 当执行call和apply操作时
          • var twice = {
              apply (target, ctx, args) {
                return Reflect.apply(...arguments) * 2;
              }
            };
            function sum (left, right) {
              return left + right;
            };
            var proxy = new Proxy(sum, twice);
            proxy(1, 2) // 6
            proxy.call(null, 5, 6) // 22
            proxy.apply(null, [7, 8]) // 30

            在 CodeSendBox 中尝试


      • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

 

  1. Proxy.revocable()
    • Proxy.revocable()方法返回一个可取消的 Proxy 实例,就像生命周期中的销毁期,对一个js元素的销毁
      • let {proxy, revoke} = Proxy.revocable(target, handler);
      • 参数:
        • target:表示所要拦截的目标对象(必输)
        • handler:handler参数也是一个对象,用来定制拦截行为(必输)
      • 返回参数:
        • proxy:Proxy实例
        • revoke:revoke属性是一个函数,用于取消Proxy实例
      • Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问

  2. 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

      在 CodeSendBox 上尝试

 

posted @ 2021-07-16 14:30  调用Function  阅读(210)  评论(0编辑  收藏  举报