Proxy和Reflect

作者: 秋天de童话 | 来源:发表于2017-05-21 17:24 被阅读96次

    1、Proxy概述
    Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

    Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

    var obj = new Proxy({}, {
      get: function (target, key, receiver) {
        console.log(`getting ${key}!`);
        return Reflect.get(target, key, receiver);
      },
      set: function (target, key, value, receiver) {
        console.log(`setting ${key}!`);
        return Reflect.set(target, key, value, receiver);
      }
    });
    

    上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为

    obj.count = 1
    //  setting count!
    ++obj.count
    //  getting count!
    //  setting count!
    //  2
    

    上面代码说明,Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。

    var proxy = new Proxy(target, handler);
    

    举个栗子

    var proxy = new Proxy({}, {
      get: function(target, property) {
        return 35;
      }
    });
    
    proxy.time // 35
    proxy.name // 35
    proxy.title // 35
    

    上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。

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

    var handler = {
      get: function(target, name) {
        if (name === 'prototype') {
          return Object.prototype;
        }
        return 'Hello, ' + name;
      },
    
      apply: function(target, thisBinding, args) {
        return args[0];
      },
    
      construct: function(target, args) {
        return {value: args[1]};
      }
    };
    
    var fproxy = new Proxy(function(x, y) {
      return x + y;
    }, handler);
    
    fproxy(1, 2) // 1
    new fproxy(1,2) // {value: 2}
    fproxy.prototype === Object.prototype // true
    fproxy.foo // "Hello, foo"
    

    下面是 Proxy 支持的拦截操作一览。
    对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。

    (1)get(target, propKey, receiver)
    拦截对象属性的读取,比如proxy.foo和proxy['foo']。
    最后一个参数receiver是一个对象,可选,参见下面Reflect.get的部分。
    
    (2)set(target, propKey, value, receiver)
    拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
    
    (3)has(target, propKey)
    拦截propKey in proxy的操作,返回一个布尔值。
    
    (4)deleteProperty(target, propKey)
    拦截delete proxy[propKey]的操作,返回一个布尔值。
    
    (5)ownKeys(target)
    拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
    
    (6)getOwnPropertyDescriptor(target, propKey)
    拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
    
    (7)defineProperty(target, propKey, propDesc)
    拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
    
    (8)preventExtensions(target)
    拦截Object.preventExtensions(proxy),返回一个布尔值。
    
    (9)getPrototypeOf(target)
    拦截Object.getPrototypeOf(proxy),返回一个对象。
    
    (10)isExtensible(target)
    拦截Object.isExtensible(proxy),返回一个布尔值。
    
    (11)setPrototypeOf(target, proto)
    拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。
    如果目标对象是函数,那么还有两种额外操作可以拦截。
    
    (12)apply(target, object, args)
    拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
    
    (13)construct(target, args)
    拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
    

    2、具体方法的实例(见http://es6.ruanyifeng.com/#docs/proxy

    3、Proxy.revocable

    Proxy.revocable方法返回一个可取消的 Proxy 实例。
    let target = {};
    let handler = {};
    let {proxy, revoke} = Proxy.revocable(target, handler);
    proxy.foo = 123;
    proxy.foo // 123
    revoke();
    proxy.foo // TypeError: Revoked
    

    4、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代理target.m,后者内部的this就是指向proxy,而不是target。

    const _name = new WeakMap();
    class Person {
      constructor(name) {
        _name.set(this, name);
      }
      get name() {
        return _name.get(this);
      }
    }
    const jane = new Person('Jane');
    jane.name // 'Jane'
    const proxy = new Proxy(jane, {});
    proxy.name // undefined
    

    上面代码中,目标对象jane的name属性,实际保存在外部WeakMap对象_name上面,通过this键区分。由于通过proxy.name访问时,this指向proxy,导致无法取到值,所以返回undefined。

    5、Reflect 概述
    Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。
    (1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
    (2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。

    // 老写法
    try {
      Object.defineProperty(target, property, attributes);
      // success
    } catch (e) {
      // failure
    }
    
    // 新写法
    if (Reflect.defineProperty(target, property, attributes)) {
      // success
    } else {
      // failure
    }
    

    (3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。

    // 老写法
    'assign' in Object // true
    
    // 新写法
    Reflect.has(Object, 'assign') // true
    

    4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

    Proxy(target, {
      set: function(target, name, value, receiver) {
        var success = Reflect.set(target,name, value, receiver);
        if (success) {
          log('property ' + name + ' on ' + target + ' set to ' + value);
        }
        return success;
      }
    });
    

    上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用Reflect.set方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。

    var loggedObj = new Proxy(obj, {
      get(target, name) {
        console.log('get', target, name);
        return Reflect.get(target, name);
      },
      deleteProperty(target, name) {
        console.log('delete' + name);
        return Reflect.deleteProperty(target, name);
      },
      has(target, name) {
        console.log('has' + name);
        return Reflect.has(target, name);
      }
    });
    

    上面代码中,每一个Proxy对象的拦截操作(get、delete、has),内部都调用对应的Reflect方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。

    有了Reflect对象以后,很多操作会更易读。

    // 老写法
    Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
    // 新写法
    Reflect.apply(Math.floor, undefined, [1.75]) // 1
    

    6、Reflect的静态方法

    Reflect.apply(target,thisArg,args)
    Reflect.construct(target,args)
    Reflect.get(target,name,receiver)
    Reflect.set(target,name,value,receiver)
    Reflect.defineProperty(target,name,desc)
    Reflect.deleteProperty(target,name)
    Reflect.has(target,name)
    Reflect.ownKeys(target)
    Reflect.isExtensible(target)
    Reflect.preventExtensions(target)
    Reflect.getOwnPropertyDescriptor(target, name)
    Reflect.getPrototypeOf(target)
    Reflect.setPrototypeOf(target, prototype)
    

    相关文章

      网友评论

        本文标题:Proxy和Reflect

        本文链接:https://www.haomeiwen.com/subject/irfvxxtx.html