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

事前哔哔

上一节我们讲了响应式原理,比较笼统又难以理解,所以这一节我们就直接上手开始编写,在章节末尾我把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(3)上手编写dep和watcher

上一篇:【CF547E】Mike and Friends(AC自动机)


下一篇:获取免费代理IP