• Proxy详解


    一.Proxy基础

    1. 什么是Proxy?

    Proxy是一个构造函数,可以通过它生成一个Proxy实例。

    const proxy = new Proxy(target, handler);
    // target: 目标对象,待操作对象(可以是普通对象,数组,函数,proxy实例对象)
    // handler: 一个对象,最多可包含13个操作方法,也可以是空对象

    2. Proxy的作用

    1. Proxy是一个ES6语法,它的作用主要是通过handler对象中的拦截方法拦截目标对象target的

    某些操作(如读取,赋值,函数调用,new等),然后过滤或者改写这些操作的默认行为,

    或者只是在完成这些操作的默认行为的基础上,增加一些副作用(如打印前置log等)。

    2. 生成的实例对象是针对target对象的“拦截器”。也可以叫做“代理器”。

    3. 然后必须通过操作proxy,即“拦截器”(拦截器对象本身性质和目标对象target一样,比如:target是函数,

    那么proxy也是函数)才能触发拦截方法,来完成对目标对象的操作和访问。

    4. 如果handler是个空对象({}),那么操作拦截器相当于直接操作目标对象target。

    3. Proxy构造函数的特征

    1. Proxy函数没有prototype属性,所以也就不能使用instanceof判断是否是Proxy实例

    2. Proxy实例的数据类型和target数据类型一致。

    var proxy1 = new Proxy([], {});
    proxy1 instanceof Array; // true
    var proxy2 = new Proxy(function(){}, {});
    typeof proxy2; //"function"

    二. Proxy拦截器handler方法

    一共有13个拦截方法(对应Reflect的13个方法),可以大体分为两个部分。

    1. 新的方法名

    返回值是布尔值的方法有:

    1. has(target, propKey)

    作用:  拦截判断target对象是否含有属性propKey的操作

    拦截操作: propKey in proxy;   不包含for...in循环

    对应Reflect: Reflect.has(target, propKey)

    语法:

    const handler = {
        has(target, propKey){
            // ...
            //默认操作
            return propKey in target;
        }
    }
    const proxy = new Proxy(target, handler)

    示例: 过滤某些私有属性

      const handler = {
        has(target, propKey) {
          if (propKey[0] === '_') {
            return false;
          }
          return propKey in target;
        }
      }
      const target = {_myprop: 1, a: 2, c:3};
      const proxy = new Proxy(target, handler);
      '_myprop' in proxy; // false

    特殊情况: 目标target是不可扩展或者某个属性不可配置,只能返回默认行为结果;否则报错

    var obj = { a: 10 };
    Object.preventExtensions(obj);
    
    var p = new Proxy(obj, {
      has: function(target, prop) {
        return false; //只能是return prop in target;
      }
    });
    'a' in p; //Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible

    2. deleteProperty(target, propKey)

    返回:严格模式下只有返回true, 否则报错

    作用: 拦截删除target对象的propKey属性的操作

    拦截操作: delete proxy[propKey]

    语法: 

    const handler = {
        deleteProperty(target, propKey){
            // ...
            //默认操作; 操作成功并返回true;操作失败报错
            const bool = Reflect.deleteProperty(...arguments);
            if (bool) {
                return bool;
            } else {
                throw new Error("delete failed");
            }
        }
    }
    const proxy = new Proxy(target, handler)

    示例:

    var obj = { a: 10 };
    var p = new Proxy(obj, {
      deleteProperty(target, prop) {
        console.log('delete propName ',prop); 
        return delete target[prop]; // 严格模式下操作成功必须返回true;否则报错
      }
    });
    delete p.a;
    console.log(obj);
    // 运行结果如下:
    'delete propName a'
    {}

    特殊情况: 属性是不可配置属性时,不能删除; 但是对象不可扩展的时候,可以删除属性。

    var obj = { a: 10 };
    Object.defineProperty(obj, 'b', {
      value: 2, configurable: false
    });
    var p = new Proxy(obj, {
      deleteProperty(target, prop) {
        return delete target[prop];
      }
    });
    delete p.b; // Uncaught TypeError: Cannot delete property 'b' of #<Object>
    console.log(obj);

    3. ownKeys(target)

    返回: 数组(数组元素必须是字符或者Symbol,其他类型报错)

    作用: 拦截获取键值的操作

    拦截操作: Object.getOwnPropertyNames(proxy)

                    Object.getOwnPropertySymbols(proxy)

                    Object.keys(proxy)

                    for...in...循环

    语法: 

    最后取到的结果不是return的值,而是会自动过滤

    const handler = {
       ownKeys(target) {
    // 所有的keys;也可以是其他的数组
    return Reflect.ownKeys(target); } }

    示例: 

    var obj = { a: 10, [Symbol.for('foo')]: 2 };
    Object.defineProperty(obj, 'c', {
      value: 3, enumerable: false
    })
    var p = new Proxy(obj, {
      ownKeys(target) {
        return [...Reflect.ownKeys(target), 'b', Symbol.for('bar')];
      }
    });
    const keys = Object.keys(p);  // ['a']
    // 自动过滤掉Symbol/非自身/不可遍历的属性
    
    for(let prop in p) { // 和Object.keys()过滤性质一样,只返回target本身的可遍历属性
      console.log('prop-',prop); // prop-a
    }
    const ownNames = Object.getOwnPropertyNames(p);// ['a', 'c', 'b']
    // 只返回拦截器返回的非Symbol的属性,不管是不是target上的属性
    
    const ownSymbols = Object.getOwnPropertySymbols(p);// [Symbol(foo), Symbol(bar)]
    // 只返回拦截器返回的Symbol的属性,不管是不是target上的属性
    
    const ownKeys = Reflect.ownKeys(p);// ['a','c',Symbol(foo),'b',Symbol(bar)]
    // 返回拦截器返回的所有值

    特殊情况:

    1)如果某个属性是不可配置的,那么该属性在拦截器中必须被返回,否则报错

    2)如果target对象是不可扩展的,那么拦截器返回的数组必须是操作的默认返回结果。

    即必须是被拦截的操作的默认行为,如getOwnPropertyNames()

    4. apply(target, thisArg, args)--target是函数

    返回:函数执行结果

    作用: 拦截proxy作为函数调用时的操作

    拦截操作:proxy()

                   proxy.call(obj, ...args)

                   proxy.apply(obj, [...args])

                   Reflect.apply(proxy, thisArg, args)

    语法:

    const handler = {
      apply(target, contextThis/*上下文对象this*/, args/*参数数组*/) {
        return Reflect.apply(...arguments);
      }
    }

    示例:

    const handler = {
      apply(target, contextThis/*上下文对象this*/, args/*参数数组*/) {
        console.log('---apply拦截器---',contextThis,'-',args);
        return Reflect.apply(...arguments)+'end';
      }
    }
    let target = function(a,b) {
      return a+b;
    }
    const proxy = new Proxy(target, handler);
    console.log('proxy-->',proxy(5,6));
    console.log('proxy.call-->',proxy.call(null, 5, 6));
    console.log('proxy.apply-->',proxy.apply(null, [5,6]));
    console.log('Reflect-->',Reflect.apply(proxy, null, [5,6]));
    
    // 运行结果如下:
    /*
    ---apply拦截器---undefined-[5,6]
    proxy-->11end
    ---apply拦截器---null-[5,6]
    proxy.call-->11end
    ---apply拦截器---null-[5,6]
    proxy.apply-->11end
    ---apply拦截器---null-[5,6]
    Reflect-->11end
    */

    5. construct(target, args, newTarget)--target是构造函数

    返回: 实例对象, 不是对象会报错 

    作用: 拦截new命令

    拦截操作: new proxy(...args)

    语法:

    const handler = {
      construct(target, args, newTarget){// args是参数数组, newTarget是生成的proxy实例
        console.log('----拦截new----',args,'-', newTarget === proxy);
        return Reflect.construct(target, args);// 默认行为,也可以return {a: b},只要是对象就可以
      }
    }
    const target = function(a,b) {};
    var proxy = new Proxy(target, handler);
    console.log('proxy type-->', typeof proxy);
    const result = new proxy(1,2); // 触发拦截器
    console.log('result type-->',typeof result)
    // 运行结果如下: proxy type-->function ----拦截new----[1,2]-true result type-->object

    2. 方法名和对象原有方法名一样

    6. get(target, propKey, receiver)

    返回: 返回读取的属性

    作用:拦截对象属性的读取

    拦截操作:proxy[propKey]或者点运算符

    语法:

    const handler = {
      get(target, propKey, receiver) {// receiver是proxy实例
        return Reflect.get(target, propKey);
      }
    }

    示例: 实现函数的链式操作

    const funsObj = {
      double(n) {return n*2},
      square(n) {return n**2}
    }
    var pipe = (value) => {
      const callStack=[];
      return new Proxy({}, {
        get(target, propKey, receiver) {
          console.log(propKey,callStack);
          if (propKey === 'get') {
            return callStack.reduce((val, fn) => {
              return fn(val);
            }, value)
          } else {
            callStack.push(funsObj[propKey]);
          }
          return receiver; //返回proxy实例才能实现链式调用
        }
      })
    }
    pipe(3).double.square.get; //36

    get方法可以继承,但是receiver的值会是直接触发的那个对象。

    const proxy= new Proxy({}, {
      get(target, propKey, receiver) {
        return receiver;
      }
    })
    const p = Object.create(proxy);
    console.log(p.a === p); // true p.a返回receiver

    特殊情况:如果对象的属性writable和configurable同时为false, 则拦截器只能返回默认行为

    const target = {};
    Object.defineProperty(target, 'a', {
      value: 1, 
      writable: false,
      configurable: false
    })
    const proxy = new Proxy(target, {
      get(target,propKey) {
        return 2;
        //应该return 1;不能返回其他值,否则报错
      }
    })
    proxy.a; // Uncaught TypeError

    7.set(target,propKey, value,receiver)

    返回:严格模式下返回true操作成功;否则失败,报错

    作用: 拦截对象的属性赋值操作

    拦截操作: proxy[propkey] = value

    语法:

    const proxy = new Proxy({}, {
      set(target, propKey, value,receiver) {// receiver时proxy实例
        const bool = Reflect.set(...arguments); 
        if (bool){
          return //!!!严格模式下操作成功必须返回true,否则报错;非严格模式下可以省略
    //当Reflect.set传入的参数有receiver且属性writable=true时,会在receiver在定义属性,会触发defineProperty拦截
        } else {
            throw new Error("操作失败")
        }
        
      },
      defineProperty(target, propKey, propDesc) {
        console.log('defineProperty')
      }
    })

    set方法也可以继承,receiver也是最终调用的那个实例,和get方法一样。参照get的方法。

    设置 target[propKey] = receiver;

    当对象的属性writable为false时,该属性不能在拦截器中被修改;

    const obj = {};
    Object.defineProperty(obj, 'foo', {
      value: 'bar',
      writable: false,
      configurable: true,
    });
    
    const handler = {
      set: function(obj, prop, value, receiver) {
        return Reflect.set(...arguments);
      },
    };
    const proxy = new Proxy(obj, handler);
    proxy.foo = 'baz';
    console.log(obj); // {foo: bar} 说明修改失败

    8. defineProperty(target, propKey,PropDesc)

    返回: 严格模式下操作成功必须返回true;否则报错

    作用:拦截定义属性或者重新定义属性操作

    拦截操作: Object.defineProperty(proxy, propKey,propDesc)

                    Reflect.defineProperty(proxy, propKey,propDesc)

    语法兼示例:

    const proxy = new Proxy({}, {
      defineProperty(target, propKey, propDescriptor) {// 最后一个参数是属性描述对象
        const bool = Reflect.defineProperty(...arguments);
        if (bool) {
           return bool;// !!严格模式下操作成功必须返回true,否则报错
        } else {
           throw new Error("定义属性失败")
        }
      }
    })
    Object.defineProperty(proxy, 'a', {value:5})

    特殊情况:

    如果对象是不可扩展的(preventExtensible(obj)),则不能添加属性;

    如果对象的某属性writable和configurable同时为false, 则不能重新定义该属性的值;

    如果上面的属性有其中一个是true,可以重新定义该属性的值。

    9. getPrototypeOf(target)

    返回: 返回对象或者null,否则报错

    作用:拦截获取原型对象的操作

    拦截操作:Object.getPrototypeOf(proxy)

                   proxy.__proto__

                  Object.isPrototypeOf(proxy)

                  Reflect.getPrototypeOf(proxy)

                  instanceof

    语法:

    var p = new Proxy({}, {
      getPrototypeOf(target) {
        return Reflect.getPrototypeOf(target);
      }
    });

    示例:

    var proxy = new Proxy({}, {
      getPrototypeOf(target) {
        return {a:1}
      }
    });
    Object.getPrototypeOf(proxy); // {a:1} 

    特殊情况:

    如果目标对象是不可扩展的,那么只能返回默认的原型对象。

    10.setPrototypeOf(target, proto)

    返回:严格模式下返回true,否则报错;只能是布尔值

    作用:拦截设置原型对象的操作

    拦截操作:Object.setPrototypeOf(proxy, proto)

                   proxy.__proto__

                   Reflect.setPropertyOf(proxy,proto)

    语法:

    var proxy = new Proxy({}, {
      setPrototypeOf(target, proto) {
        const bool = Reflect.setPrototypeOf(...arguments);
        if (bool) {
           return bool; // !!!严格模式下操作成功必须返回true;否则报错
        } else {
           throw new Error("设置原型对象失败");
        }
      }
    })

    特殊情况:

    如果对象不可扩展,只能进行默认行为。不能修改。

    11. isExtensible(target)---只能返回默认行为结果

    返回:布尔值

     作用: 拦截是否可扩展方法的调用

    拦截操作: Object.isExtensible(proxy)

                    Reflect.isExtensible(proxy)

    语法:

    const proxy = new Proxy({}, {
      isExtensible(target) {
        return Reflect.isExtensible(target); //!!!返回结果只能是这个;即proxy和target的可扩展性必须是一致的
      }
    })

    12. preventExtensible(target)--遵循默认行为

    返回:严格模式下返回true,否则报错;

    拦截操作:Object.preventExtensible(proxy)

                   Reflect.preventExtentsible(proxy)

    语法:

    const proxy = new Proxy({}, {
      preventExtensions(target) {
        return Reflect.preventExtensions(target); //严格模式下,必须存在
      }
    })

    13. getOwnPropertyDescriptor(target, propKey)

    返回: 对象或者undefined

    拦截操作: Object.getOwnPropertyDescriptor(proxy)

                    Reflect.getOwnPropertyDescriptor(proxy)

    语法:

    const proxy = new Proxy({}, {
      getOwnPropertyDescriptor(target, propKey) {
        return Reflect.getOwnPropertyDescriptor(target,propKey);
      }
    })

    三. 静态方法-Proxy.revocable()

    作用: 返回一个可取消的proxy实例

    语法:

    const {proxy, revoke} = Proxy.revocable(target,handler);
    // proxy是实例
    // revoke是个方法;直接调用后取消proxy实例

    示例:

    const {proxy, revoke} = Proxy.revocable({},{});
    proxy.a=5;
    revoke();
    proxy.a // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked

    应用: 

    用于一个只能访问一次的代理器

    四. this指向

    拦截器方法中的this指向proxy实例

    五.Proxy应用

    观察者模式:通过拦截对象的赋值操作,实时调用观察函数。

    如: mobx中的observable, observe的实现;通过函数观察对象,并实时作出反应

    const obj = {name: 'lyra', age: 18};//被观察者--使用Proxy
    const fn1 = () => {
        obj.age = 16;
        console.log(`Hello, ${obj.name}, ${obj.age}`);
    }
    const fn2 = () => console.log('there must be sth changed'); 
    const observeFuncs = new Set();
    const observe = (fn) => observeFuncs.add(fn);
    const observable = function(obj) {
        return new Proxy(obj, {
            set(target, prop, value) {
                observeFuncs.forEach(observer => observer()); //被观察者属性被赋值时,触发观察者函数调用
                return Reflect.set(...arguments); //默认行为
            }
        })
    }
    const objProxy = observable(obj);//指定被观察者对象
    observe(fn1); //指定观察者函数
    observe(fn2); 
    objProxy.name = 'lyraLee';
    // 运行结果如下:
    Hello, lyraLee, 16
    there must be sth changed
  • 相关阅读:
    使用HandyJSON导致的内存泄漏问题相关解决方法
    iOS开发中获取视图在屏幕上显示的位置
    颜色框架Hue使用方法
    网络库Alamofire使用方法
    iOS开发中使用文字图标iconfont
    UISearchBar的扩展使用
    cocoapods导入第三方库提示RPC failed curl 18 transfer
    APP在模拟器崩溃,提示__gcd_queue_item_enqueue_hook_block_invoke
    APP崩溃提示:This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
    crm
  • 原文地址:https://www.cnblogs.com/lyraLee/p/11774482.html
Copyright © 2020-2023  润新知