记录高级js学习(十六)Proxy的使用及简单模拟vue3响应式

74 阅读2分钟

监听对象属性的改变可以用Object.defineProperty(vue2响应式用法),但是也有很大弊端,就是新设置或者删除一个属性的时候无法监听到,但是vue2内部通过响应式系统进行优化,我们也可以通过$set和$delete设置或者删除一个响应式属性

ES6新增一个Proxy用于监听对象的属性(功能比属性描述符更全)(vue3响应式用法)

      const obj = {name:"李斯",age:18}
      const proxy = new Proxy(obj,{   //第二个参数为捕获器 里面有13种方法如下图
        get(target,key){
          console.log(`${key}被获取`);
          return target[key]
        },
        set(target,key,newVal){
          console.log(`${key}被设置值了`);
          target[key] = newVal
        },
        // 监听进行in操作符查找对象属性
        has(target,key){
          console.log(`${key}被查找了`);
          return key in target
        },
        // 监听删除对象时
        deleteProperty(target,key){
          console.log(`${key}被删除了`);
          delete target[key]
        }
      })
      console.log("name" in proxy);
      delete proxy.age

image.png

相比于上面捕获器的用法,实际上更多个是与Reflect连用,return一个布尔值

      // 参数receiver的作用,只有get和set有传,代表代理对象本身
      const obj = {
        _name : "李斯",
        get name(){
          return this._name  
        },
        set name(newVal){
          this._name = newVal
        }
      }
      const objProxy = new Proxy(obj,{
        get(target,key,receiver){
          console.log("捕获到get操作----",key);  //此时监听到原对象get中获取_name属性的方法了
          return Reflect.get(target,key,receiver)
        },
        set(target,key,newVal,receiver){
          console.log("捕获到set操作----",key);
          return Reflect.set(target,key,newVal,receiver)
        }
      })
      console.log(objProxy.name);
      objProxy.name = "王五" 

针对于Reflect用法的好处主要体现在receiver参数

    const parent = {
      _a: 1,
      get a(){
        console.log(this === child);
        return this._a
      }
    }
    const handler = {
      // 此时receiver为直接调用的对象child,传给代理对象get中的this就变为child了
      get(target,key,receiver){
        // 使用这种的话代理对象get中的this就指向parent,此时打印 false 和 1
        // return target[key] 
        // 使用这种的话代理对象get中的this就指向child,此时打印 true 和 2
        return Reflect.get(target,key,receiver)
      },
      set(target,key,value,receiver){
        return Reflect.set(target,key,value,receiver)
      }
    }
    const proxyObj = new Proxy(parent,handler)
    // 实现继承,child继承于代理对象proxyObj
    const child = Object.setPrototypeOf({_a:2},proxyObj)
    console.log(child.a);

Reflect.construct(target,args,newTarget)的使用,借用target的构造方法new newTarget

      function Student(name,age){
        this.name = name
        this.age = age
      }
      function Teacher(){

      }
      // 此时new Teacher执行的是Student的构造方法
      const obj = Reflect.construct(Student,["李斯",18],Teacher)
      console.log('obj: ', obj);  // Teacher {name: '李斯', age: 18}
      console.log(obj.__proto__ === Teacher.prototype); //true

自己乱七八糟写了个vue3的响应式,随便看看就好

<body>
    姓名:<p></p>
    年龄:<span></span>
    <script type="text/javascript">
      const obj = {
        name:"李斯",  //depend
        age:16        //depend
      }
      class Depend {
        constructor(){
          // 存放依赖
          this.reactiveFns = []
        }
        addDepend(objProxy,key){
          // 这里获取属性所对应的依赖
          this.reactiveFns = dependWeakMap.get(objProxy).get(key)
        }
        notify(){
          // 遍历执行所有依赖的方法
          this.reactiveFns.forEach(item=>item())
        }
      }
      const objProxy = new Proxy(obj,{
        get(target,key,receiver){
          return Reflect.get(target,key,receiver)
        },
        set(target,key,newVal,receiver){
          const setVal = Reflect.set(target,key,newVal,receiver)
          // 响应式查找并执行依赖
          depend.addDepend(receiver,key)
          depend.notify()
          return setVal
        }
      })
      document.getElementsByTagName("p")[0].innerHTML = objProxy.name
      document.getElementsByTagName("span")[0].innerHTML = objProxy.age
    
      // 这里通过Map设定响应对象每个属性对应的依赖
      const dependMap = new Map([["name",[nameFoo]],["age",[ageFoo]]])
      // 这里通过WeakMap绑定响应对象以及他所对应所有的依赖
      const dependWeakMap = new WeakMap([[objProxy,dependMap]])
      const depend = new Depend()

      function nameFoo(){
        document.getElementsByTagName("p")[0].innerHTML = objProxy.name
        console.log("触发了name事件-------");
      }
      function ageFoo(){
        document.getElementsByTagName("span")[0].innerHTML = objProxy.age
        console.log("触发了age事件-------");
      }

      setTimeout(()=>{objProxy.name = "王五"},3000)
      setTimeout(()=>{objProxy.age = 18},5000)
    </script>
  </body>

---有不理解的地方欢迎留言讨论哦