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‘);
                      }
                    }
                
                    // 对于满足条件的 age 属性以及其他属性,直接保存
                    obj[prop] = value;
                    return true;
                  }
                };
                
                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()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问

  • 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 上尝试

  •  

    ES6 Proxy

    上一篇:问题排查 Active Scheduler of name 'MyClusterScheduler' already registered in Quartz SchedulerR


    下一篇:Hadoop-之yarn容量调度器之多队列配置与解读