美文网首页
关于Proxy

关于Proxy

作者: LElysion | 来源:发表于2019-03-15 11:20 被阅读0次

    MDN

    Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

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

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

    通过Proxy可以实现很多对于对象本身的有趣能力, 下文有对应的一些实例可以参考

    可拦截操作,合共13种

    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)

    注意该拦截依然受目标对象不可扩展(non-extensible),不可写(writable),不可配置(configurable)等限制

    语法

    let p = new Proxy(target, handler);;

    target

    Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

    handler

    一个对象,其属性是当执行一个操作时定义代理的行为的函数。

    基础示例

    当对象不存在属性名时返回缺省值37

    let handler = {
        get: function(target, name){
            return name in target ? target[name] : 37;
        }
    };
    
    let p = new Proxy({}, handler);
    
    p.a = 1;
    p.b = undefined;
    
    console.log(p.a, p.b);    // 1, undefined
    
    console.log('c' in p, p.c);    // false, 37
    

    更多示例

    1. 使用get拦截实现数组读取负数的索引
    function createArray(...elements) {
      let handler = {
        get(target, propKey, receiver) {
          let index = Number(propKey);
          if (index < 0) {
            propKey = String(target.length + index);
          }
          return Reflect.get(target, propKey, receiver);
        }
      };
    
      let target = [];
      target.push(...elements);
      return new Proxy(target, handler);
    }
    
    let arr = createArray('a', 'b', 'c');
    arr[-1] // c
    
    1. 实现属性的链式操作。
    var pipe = (function () {
      return function (value) {
        var funcStack = [];
        var oproxy = new Proxy({} , {
          get : function (pipeObject, fnName) {
            if (fnName === 'get') {
              return funcStack.reduce(function (val, fn) {
                return fn(val);
              },value);
            }
            funcStack.push(window[fnName]);
            return oproxy;
          }
        });
    
        return oproxy;
      }
    }());
    
    var double = n => n * 2;
    var pow    = n => n * n;
    var reverseInt = n => n.toString().split("").reverse().join("") | 0;
    
    pipe(3).double.pow.reverseInt.get; // 63
    
    1. 实现一个生成各种 DOM 节点的通用函数dom
    const dom = new Proxy({}, {
      get(target, property) {
        return function(attrs = {}, ...children) {
          const el = document.createElement(property);
          for (let prop of Object.keys(attrs)) {
            el.setAttribute(prop, attrs[prop]);
          }
          for (let child of children) {
            if (typeof child === 'string') {
              child = document.createTextNode(child);
            }
            el.appendChild(child);
          }
          return el;
        }
      }
    });
    
    const el = dom.div({},
      'Hello, my name is ',
      dom.a({href: '//example.com'}, 'Mark'),
      '. I like:',
      dom.ul({},
        dom.li({}, 'The web'),
        dom.li({}, 'Food'),
        dom.li({}, '…actually that\'s it')
      )
    );
    
    document.body.appendChild(el);
    

    参数说明

    get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。

    set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 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;
      }
    };
    
    let person = new Proxy({}, validator);
    
    person.age = 100;
    
    person.age // 100
    person.age = 'young' // 报错
    person.age = 300 // 报错
    

    参考

    MDN-Proxy

    阮一峰ECMAScript6入门-Proxy

    相关文章

      网友评论

          本文标题:关于Proxy

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