美文网首页
手摸手教你实现一个简单vue(2)上手编写observer

手摸手教你实现一个简单vue(2)上手编写observer

作者: 某时橙 | 来源:发表于2021-12-03 09:00 被阅读0次

    事前哔哔

    上一节我们讲了响应式原理,比较笼统又难以理解,所以这一节我们就直接上手开始编写,在章节末尾我把observer对象转换成了js版本供你直接上手测试,你可以先copy边调试边理解接下来的编写流程

    开始

    observer的侦测是一个对象或者数组,所以一开始我们便要传递这个参数(取名为value)进来

     class ObserverNext {
      $value: any;
      constructor(value) {
      ...
      }
    

    在前面响应式原理中我们说了我们要深度递归分析这个对象(就是data选项)内的数据所以我们还得有个walk方法

     class ObserverNext {
      $value: any;
      constructor(value) {
      ... 
      this.walk(value)
      }
      private walk(obj: Object | Array<any>) {
        for (const [key, val] of Object.entries(obj)) {
          if (typeof val == "object") {
            //同时判断数组和对象
            new ObserverNext(key,val, obj);
          }
        }
      }
    

    现在就完成了深度递归,但是还没有分析的过程,所以我们得再写一个方法,名为detect来分析,这里我们用Proxy完成对该对象的监控(分析)

    //observer中的detect私有方法
     private detect(val: any, parent: any) {
        const dep = this.dep//在constructor中定义
        const proxy = new Proxy(val, {
          get(obj, property) {
            if (!obj.hasOwnProperty(property)) {
              return;
            }
            dep.depend(property);
            return obj[property];
          },
          set(obj, property, value) {
            obj[property] = value;
            dep.notify(property);
            return true;
          },
        });
    
      }
    

    当对象被get了数据,我们就收集依赖,当对象set了数据,我们就通知依赖更新,
    并且呢,这个proxy要被外部能够获取到,这里我们的解决方案是直接在父对象上替换

       parent[key] = proxy;
    

    所以我们一开始就要传递父对象(parent)和键(key)进来。
    所以我们的constructor就变成了

     constructor(key,value, parent) {
        this.$key=key;
        this.$value = value;
    
        this.$parent = parent;
        this.dep = new Dep();
        
        this.walk(value);
        this.detect(value, parent);
      }
    

    现在我们的observer变成了

    class ObserverNext {
      $value: any;
      $parent: any;
      $key:string
      dep: any;
      constructor(key,value, parent) {
        this.$key=key;
        this.$value = value;
    
        this.$parent = parent;
    
        this.dep = new Dep();
    
        //def(value, "__ob__", this);
        this.walk(value);
        this.detect(value, parent);
      }
      private walk(obj: Object | Array<any>) {
        for (const [key, val] of Object.entries(obj)) {
          if (typeof val == "object") {
            //同时判断数组和对象
            new ObserverNext(key,val, obj);
          }
        }
      }
      private detect(val: any, parent: any) {
        const dep = this.dep
        const key=this.$key
        const proxy = new Proxy(val, {
          get(obj, property) {
            if (!obj.hasOwnProperty(property)) {
              return;
            }
            dep.depend(property);
            return obj[property];
          },
          set(obj, property, value) {
            obj[property] = value;
    
            dep.notify(property);
            //if(parent.__ob__)parent.__ob__.dep.notify(key)
    
            return true;
          },
        });
    
        parent[key] = proxy;
      }
    }
    

    但是现在有个bug,如果儿子对象更新了,它只会通知自己的依赖收集器(dep)更新,父对象不会感知到任何异常!

    所以我们得通知父对象的观察者类实例(ObserverNext)更新
    因此,在每个对象中我们都要记录一下观察者实例
    大概如下:

    constructor(key,value,parent){
    ...
    def(value, "__ob__", this); 
    //相当于value.__ob__=this;
    ...
    }
    
    

    然后在proxy的set中,我们要通知父对象更新于是

    set(){
    if(parent.__ob__)parent.__ob__.dep.notify(key)
    }
    

    最终成果

    我们原版是typescript版本的,但为了方便各位直接丢控制台里测试,我转换成了js版本以供各位调试

    class Dep {
      constructor() {}
      depend() {
        console.log("依赖收集");
      }
      notify() {
        console.log("依赖更新");
      }
    }
    function def(obj, key, val, enumerable=false) {
        Object.defineProperty(obj, key, {
          value: val,
          enumerable: !!enumerable,
          writable: true,
          configurable: true,
        });
      }
    class ObserverNext {
      constructor(key, value, parent) {
        this.$key = key;
        this.$value = value;
    
        this.$parent = parent;
    
        this.dep = new Dep();
       
        def(value, "__ob__", this);
        this.walk(value);
        this.detect(value, parent);
      }
      walk(obj) {
        for (const [key, val] of Object.entries(obj)) {
          if (typeof val == "object") {
            //同时判断数组和对象
            new ObserverNext(key, val, obj);
          }
        }
      }
      detect(val, parent) {
        const dep = this.dep;
        const key = this.$key;
        const proxy = new Proxy(val, {
          get(obj, property) {
            if (!obj.hasOwnProperty(property)) {
              return;
            }
            dep.depend(property);
            return obj[property];
          },
          set(obj, property, value) {
            obj[property] = value;
    
            dep.notify(property);
            if (parent.__ob__) parent.__ob__.dep.notify(key);
    
            return true;
          },
        });
    
        parent[key] = proxy;
      }
    }
    
    const vm = {
      data: {
        attr1: {
          a: 1,
          b: 2,
          c: 3,
        },
        array: [1, 2, 3],
      },
    };
    
    new ObserverNext('data',vm.data,vm);
    //测试
    //vm.data.attr1 ->return 依赖收集
    

    归档

    # 手摸手教你实现一个简单vue(1)响应式原理
    # 手摸手教你实现一个简单vue(2)上手编写observer

    相关文章

      网友评论

          本文标题:手摸手教你实现一个简单vue(2)上手编写observer

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