最简单的proxy讲解,一遍就懂

5,877 阅读5分钟

Day 9

代理与反射

代理基础:

  • 代理是目标对象的抽象,可以当做是目标对象的替身,但完全独立于目标对象,目标对象可以被直接操作,也可以通过代理来操作

创建空代理:

  • 最简单的代理是空代理,就是说除了作为一个抽象的目标对象,什么也不做,在代理对象是执行的所有操作都会无障碍的传播到目标对象,因此,任何使用目标对象的地方,都可以通过同样的方式使用与之关联的代理对象

    例子1:

const target = { id: 'target' };
const handler = {};
const proxy = new Proxy(target, handler);
// id属性会访问同一个值
console.log(target.id); // target 
console.log(proxy.id); // target 
// 给目标属性赋值会反映在两个对象上, 因为两个对象访问的是同一个值
target.id = 'foo'; 
console.log(target.id); // foo
console.log(proxy.id); // foo 
// 给代理属性赋值会反映在两个对象上,因为这个赋值会转移到目标对象
proxy.id = 'bar';
console.log(target.id); // bar
console.log(proxy.id); // bar 
// hasOwnProperty()方法在两个地方 都会应用到目标对象
console.log(target.hasOwnProperty('id')); // true 
console.log(proxy.hasOwnProperty('id')); // true

小结:这是最简单的一个空代理的案例,初看可能会不明所以,我当初看也很懵,但是不要着急,你现在只需要看懂,知道代理做了什么事情,在这个例子里,target是代理的目标,proxy是代理,代理的目标和代理属性共享,操作共享,包括方法也是共享的,但是代理并不等于代理目标,更倾向于操作代理时,代理把操作转发给了代理的目标,然后进行操作,目标拥有的,代理会拥有,代理拥有的,也会相应的传递给目标;

代码捕获器与反射方法:

  • 使用代理的目的 :是可以定义捕获器(trap),捕获器就是可以直接或间接在代理对象上调用,每次在代理对象上调用这些基本操作时, 代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦 截并修改相应的行为。

    • 通俗讲就是,捕获器在代理对象被调用时,先执行,从而拦截并且修改相应的行为,就是类似加了一层if判断,或者说是弹框确认
  • handler :代理的处理对象,例子1中是一个空对象,多数情况下并不是空对象,而是定义了一个或者多个捕获器(trap)去处理代理,如果没有定义,则和上例中的空对象一样,使用默认行为。

  • set trap :常用的trap,触发条件是在设置属性值的时候触发,

  • Reflect.set: 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true

    首先set trap接受4个参数,

    • trapTarget - 接收的属性的对象,就是**代理的目标 **

    • key - 要写入的属性的

    • value- 写入属性的

    • receiver- 操作的对象,通常是代理

      例子2:用set trap验证一个属性的值是否为number

let target = {
       name: "target"
     };
     
     let proxy = new Proxy(target, {
       //target,name,target,proxy;
       set(trapTarget, key, value, receiver) {
         console.log(`trapTarget is ${trapTarget}, key is     ${key}, value is ${value}, receiver is ${receiver}`)
       // 忽视存在的属性,以免产生影响,不存在的属性才会进入判断
       if (!trapTarget.hasOwnProperty(key)) {
           //判断值是否为number
       if (isNaN(value)) {
      throw new TypeError("Property must be a number.");
       }
     }
     // 添加到属性
     return Reflect.set(trapTarget, key, value, receiver);
     }
     });
     // 添加一个新的属性
     proxy.count = 1;
     console.log(proxy.count);       // 1
     console.log(target.count);      // 1
     // 赋值给存在target上的属性
     proxy.name = "proxy";
     console.log(proxy.name);        // "proxy"
     console.log(target.name);       // "proxy"
     // 新的属性值不是数字会抛出异常
     proxy.anotherName = "proxy";
 小结:这个例子打印出了4个参数的时刻变化,可以清晰的看到参数对应的值,可以发现,每次设置属性值的时候都会进行拦截判断,所以在获取时候,可以用get进行拦截判断,
  • get trap:

    • get 是读取对象属性的时候用到的trap,他接受3个参数

    • trapTarget: 从哪个对象读取的属性,就是target

    • key - 读取的key

    • receiver - 操作的对象,通常是代理(proxy)

      例子1:这是一个普通的get代理的按理

     const handler = {
         get: function(obj, prop) {
             return prop in obj ? obj[prop] : 37;
         }
     };
     
     const 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
 小结:p.a的时候,获取代理里面a的值经过get,返回了 a的值,p.b一样,然后c in p,因为p没有c这个属性,所以会返回false,p.cp获取c属性,进入get,赋值为37,
  • 再来看一个set 和get的综合例子

    例子3;

   var p = {
     name:"chen",
     work:function() {
       console.log("wording...");
     },
     _age:18,
     //监听获取name属性的事件
     get name(){
       return this._age;
     },
     //监听设置age属性的事件
     set age(val) {
       if (val<0 || val> 100) {//如果年龄大于100就抛出错误
         throw new Error("invalid value")
       }else{
         this._age = val;
       }
     }
   };
   console.log(p.name);//输出18,因为这里触发了监听name属性的拦截,所以返回的age,
   console.log(p._age);//这里的age没有获取拦截,所以输出原值
   console.log(p._age =20);//输出20,因为这里进入设置拦截,满足条件,完成赋值
   console.log(p._age =200);//报错,设置拦截进入,不满足条件,报错
   console.log(p.name = 'yu')//输出 yu,因为name没有设置拦截,所以可以成功

小结:看完这个,代理的基本知识及其原理相信大家都明白了,其实捕获器还有很多,列如has等等,但是基本原理都是这样,只要把握住参数分别代表什么,捕获器的触发条件,问题都可以迎刃而解了,希望可以给大家带来帮助

本文使用 mdnice 排版