最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • ES6(8)Proxy(拦截器、代理器)

    正文概述 掘金(甘草倚半夏)   2021-05-22   456

    概述:改变默认行为,对外界的访问进行过滤和改写

    var proxy = 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,去读写它的属性,就会得到下面的结果。

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

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

    语法,var proxy = new Proxy(target, handler);

    var proxy = new Proxy(target, handler);
    

    target:参数表示所要拦截的目标对象

    handler:参数也是一个对象,用来定制拦截行为

    var target = {};
    var handler = {};
    var proxy = new Proxy(target, handler);
    proxy.a = 'b';
    target.a // "b"
    

    Proxy 对象,设置到object.proxy属性,从而可以当做object`对象的属性调用

    var object = { proxy: new Proxy(target, handler) };
    

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

    var proxy = new Proxy({}, {
      get: function(target, property) {
        return 35;
      }
    });
    
    let obj = Object.create(proxy);
    obj.time // 35
    
    //obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。
    

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

    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" // true
    

    Proxy 支持的拦截操作

    • 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)
    方法 描述
    handler.apply() 拦截 Proxy 实例作为函数调用的操作 handler.construct() 拦截 Proxy 实例作为构造函数调用的操作 handler.defineProperty() 拦截 Object.defineProperty() 的操作 handler.deleteProperty() 拦截 Proxy 实例删除属性操作 handler.get() 拦截 读取属性的操作 handler.set() 拦截 属性赋值的操作 handler.getOwnPropertyDescriptor() 拦截 Object.getOwnPropertyDescriptor() 的操作 handler.getPrototypeOf() 拦截 获取原型对象的操作 handler.has() 拦截 属性检索操作 handler.isExtensible() 拦截 Object.isExtensible()操作 handler.ownKeys() 拦截 Object.getOwnPropertyDescriptor() 的操作 handler.preventExtension() 拦截 Object().preventExtension() 操作 handler.setPrototypeOf() 拦截Object.setPrototypeOf()操作 Proxy.revocable() 创建一个可取消的 Proxy 实例

    get(target,key,receiver)

    依次三个参数:目标对象、被读取的属性名、proxy 实例本身

    需要return

    应用实例

    1、访问目标对象不存在的属性,抛出一个错误而不是返回undefined
    var person = {
      name: "张三"
    };
    
    var proxy = new Proxy(person, {
      get: function(target, property) {
        if (property in target) {
          return target[property];
        } else {
          throw new ReferenceError("Property \"" + property + "\" does not exist.");
        }
      }
    });
    
    proxy.name // "张三"
    proxy.age // 抛出一个错误
    
    2、get方法可以继承(定义在Prototype对象上,拦截实例通过原型链获取继承的方法的操作)
    let proto = new Proxy({}, {
      get(target, propertyKey, receiver) {
        console.log('GET ' + propertyKey);
        return target[propertyKey];
      }
    });
    
    let obj = Object.create(proto);  
    obj.foo // "GET foo"
    

    上面代码中,拦截操作定义在Prototype对象上面,所以如果读取obj对象继承的属性时(本身没有,通过原型链查找的属性),拦截会生效。

    3、使用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
    
    //简略版
    var arr=new Proxy([0,1,2,3,4],{
        get(target, p, receiver) {
            if(p<0){
                var n=eval(target.length-1+p);
                return target[n];
            }
            return target[p];
        }
    })
    
    console.log(arr[-1]);   //3
    
    4、将读取属性的操作(get),转变为执行某个函数【vue3.0】有点像发布订阅
    var pipe = (function () {
      return function (value) {
        var funcStack = [];
        var oproxy = new Proxy({} , {
          get : function (pipeObject, fnName) {
            if (fnName === 'get') {
    	        //如果获取的是get 对数组funcStack中的函数一次调用
              return funcStack.reduce(function (val, fn) {
                return fn(val);
              },value);
            }
            //如果不是get  向数组funcStack中添加函数
            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
    
    //有点像发布订阅
    
    5、利用get拦截,实现一个生成各种 DOM 节点的通用函数dom【vue3.0】
    const dom=new Proxy({},{
        get(target, p, receiver) {
            //(attrs={},...children)   第一个参数为attrs 剩余的都是children
            return function (attrs={},...children) {
                //get属性名  就是要创建的 元素名称
                const el=document.createElement(p);
                //循环 attrs设置元素属性
                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.append(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')
        )
    );
    
    //相对与div :{}为 attrs ,剩余的都是子级
    console.log(el);
    document.body.appendChild(el);
    

    ES6(8)Proxy(拦截器、代理器)

    第三个参数的例子,一般情况下就是 Proxy 实例
    const proxy = new Proxy({}, {
      get: function(target, property, receiver) {
        return receiver;
      }
    });
    proxy.getReceiver === proxy // true
    
    
    const proxy = new Proxy({}, {
      get: function(target, property, receiver) {
        return receiver;
      }
    });
    
    const d = Object.create(proxy);
    d.a === d // true
    

    上面代码中,d对象本身没有a属性,所以读取d.a的时候,会去d的原型proxy对象找。这时,receiver就指向d,代表原始的读操作所在的那个对象。

    set(obj, prop, value,receiver)

    依次四个参数:目标对象,属性名,属性值,Proxy 实例本身

    在赋值操作发生时进行自己想要的操作,还可以进行数据绑定,即每当对象发生变化时,会自动更新 DOM(vue3.0)

    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 // 报错
    
    

    设置一些内部属性不被外部读写(假定开头是_的为内部属性)

    const handler = {
      get (target, key) {
        invariant(key, 'get');
        return target[key];
      },
      set (target, key, value) {
        invariant(key, 'set');
        target[key] = value;
        return true;
      }
    };
    function invariant (key, action) {
      if (key[0] === '_') {
        throw new Error(`Invalid attempt to ${action} private "${key}" property`);
      }
    }
    const target = {};
    const proxy = new Proxy(target, handler);
    proxy._prop
    // Error: Invalid attempt to get private "_prop" property
    proxy._prop = 'c'
    // Error: Invalid attempt to set private "_prop" property
    

    第四个参数的例子

    set方法的第四个参数receiver,指的是原始的操作行为所在的那个对象,一般情况下是proxy实例本身,跟get的第三个参数的运用相同

    const handler = {
      set: function(obj, prop, value, receiver) {
        obj[prop] = receiver;
      }
    };
    const proxy = new Proxy({}, handler);
    proxy.foo = 'bar';
    proxy.foo === proxy // true
    

    apply(target, context, args),拦截函数的调用、call和apply操作

    依次三个形参:目标对象,上下文对象(this),参数数组

    设置apply拦截后 函数自身内部的代码不再执行

    var handler = {
      apply (target, ctx, args) {
        return Reflect.apply(...arguments);
      }
    };
    

    示例

    var fn=function () {
        //不执行
        console.log(this,'fn')
        return 'I am fn'
    }
    
    var p=new Proxy(fn,{
        apply(target, thisArg, argArray) {
            console.log(thisArg,'p')   //undefined 'p'
            return 'I am p'
        }
    })
    
    console.log(p());   //I am p
    
    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
    
    //直接调用Reflect.apply方法,也会被拦截。
    
    Reflect.apply(proxy, null, [9, 10]) // 38
    

    construct(target, args, newTarget)

    依次三个形参:目标对象,参数对象,new 后面的函数

    construct方法返回的必须是一个对象

    var handler = {
      construct (target, args, newTarget) {
        return new target(...args);
      }
    };
    
    var p = new Proxy(function () {}, {
      construct: function(target, args) {
        console.log('called: ' + args.join(', '));
        return { value: args[0] * 10 };
      }
    });
    
    (new p(1)).value
    // "called: 1"
    // 10
    

    has(target, key),拦截HasProperty操作,即判断对象是否具有某个属性(in运算符)

    依次两个形参

    下面的例子使用has方法隐藏某些属性,不被in运算符发现。

    var handler = {
      has (target, key) {
        if (key[0] === '_') {
          return false;
        }
        return key in target;
      }
    };
    var target = { _prop: 'foo', prop: 'foo' };
    var proxy = new Proxy(target, handler);
    '_prop' in proxy // false
    

    deleteProperty(target, key),用于拦截delete操作

    依次两个形参:目标对象、需查删除的属性名

    var handler = {
      deleteProperty (target, key) {
        invariant(key, 'delete');
        delete target[key];
        return true;
      }
    };
    function invariant (key, action) {
      if (key[0] === '_') {
        throw new Error(`Invalid attempt to ${action} private "${key}" property`);
      }
    }
    
    var target = { _prop: 'foo' };
    var proxy = new Proxy(target, handler);
    delete proxy._prop
    // Error: Invalid attempt to delete private "_prop" property
    

    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
    

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

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

    原生对象的内部属性,只有通过正确的this才能拿到

    const target = new Date();
    const handler = {};
    const proxy = new Proxy(target, handler);
    
    proxy.getDate();
    // TypeError: this is not a Date object.
    

    用bind将this绑定到原始处理对象,就可以解决这个问题

    const target = new Date('2015-01-01');
    const handler = {
      get(target, prop) {
        if (prop === 'getDate') {
          return target.getDate.bind(target);
        }
        return Reflect.get(target, prop);
      }
    };
    const proxy = new Proxy(target, handler);
    proxy.getDate() // 1
    

    起源地下载网 » ES6(8)Proxy(拦截器、代理器)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元