图解vueVue响应式原理

358 阅读3分钟

本文是受当面试官问你Vue响应式原理,你可以这么回答他
启发自行绘制的vue动态响应原理图解 画风诡异,仅代表个人理解如果有不当的地方,望请斧正。

官方大图

图片alt

我的见解
图片alt

JserWang的原理代码,为了看懂我自行添加了好多注释与console。

//3.
const Observer = function(data) {
  // 循环修改为每个属性添加get set
  for (let key in data) {
    console.log("给value的没一个key设置一个收集器");
    defineReactive(data, key);
  }
}
//4.
const defineReactive = function(obj, key) {
  // 局部变量dep,用于get set内部调用
  console.log("创建收集器***********");
  const dep = new Dep();
  // 获取当前值
  let val = obj[key];
  console.log("observe  开始重写data每个属性的get/set");
  Object.defineProperty(obj, key, {
    // 设置当前描述属性为可被循环
    enumerable: true,
    // 设置当前描述属性可被修改
    configurable: true,
    get() {
      console.log('observe get触发');
      // 调用依赖收集器中的addSub,用于收集当前属性与Watcher中的依赖关系
      console.log("observe dep.depend收集当前属性和watcher的依赖关系");
      console.log("当^^^^^^^^^^^^^^^^^^^^^^^^前属性:", key);
      dep.depend();
      return val;
    },
    set(newVal) {
      if (newVal === val) {
        console.log("observe set 设置属性值未变化");
        return;
      }
      val = newVal;
      // 当值发生变更时,通知依赖收集器,更新每个需要更新的Watcher,
      // 这里每个需要更新通过什么断定?dep.subs
      console.log("observe 设置属性值变化通知依赖收集器更新watcher");
      dep.notify();
    }
  });
}
const observe = function(data) {
  return new Observer(data);
}

//1.
const Vue = function(options) {
  const self = this;
  self.a = 1;
  self.b = 1;
  // 将data赋值给this._data,源码这部分用的Proxy所以我们用最简单的方式临时实现
  if (options && typeof options.data === 'function') {
    console.log("Vue 将传入配绑定给新建的vue对象:", this);
    this._data = options.data.apply(this);
  }
  // 挂载函数
  this.mount = function() {
    let tmp = self.a;
    self.a += 1;
    console.log("Vue 挂载watcher实例到当前vue对象",  tmp);
    new Watcher(self, self.render);
  }
  // 渲染函数
  this.render = function() {
    let tmp =  self.b;
    self.b += 1;
    console.log("Vue 触发渲染vue对象", tmp);
    console.log('Vue 看该属性是否被组件引用,引用则重新渲染');
    // with(self) {
    //   _data.text;//获取当前的data中的属性
    //   _data.a;
    //   _data.b;
    // }
    console.log(self._data.text);
    console.log(self._data.text1);
    console.log(self._data.text2)
  }
  // 监听this._data
  //2.
  console.log("Vue 设置观察对象, 重写set、get");
  observe(this._data);  
}
const Watcher = function(vm, fn) {
  const self = this;
  //保存传入的data对象
  this.vm = vm;
  // 将当前Dep.target指向当前watcher
  console.log("Watcher 将当前Dep.target指向当前watcher");
  console.log("-----------------------临时关联watcher----------------------------");
  Dep.target = this;
  // 向Dep方法添加当前Wathcer
  this.addDep = function(dep) {
    console.log("watcher向当前收集器dep添加当前Wathcer");
    dep.addSub(self);
  }
  // 更新方法,用于触发vm._render
  this.update = function() {
    console.log("watcher  触发render");
    fn();//vue.render
  }
  // 这里会首次调用vm._render,从而触发text的get
  // 从而将当前的Wathcer与Dep关联起来

  //vue.render  首次渲染
  console.log("-----------------------首次渲染----------------------------");
  this.value = fn();
  // 这里清空了Dep.target,为了防止notify触发时,不停的绑定Watcher与Dep,
  // 造成代码死循环
  console.log("————————————————————清空Dep.target");
  Dep.target = null;
}
//5.
const Dep = function() {
  console.log("Dep新建收集器");
  const self = this;
  // 收集目标
  this.target = null;
  // 存储收集器中需要通知的Watcher
  this.subs = [];
  // 当有目标时,绑定Dep与Wathcer的关系
  this.depend = function() {
    //确认实例是否绑定watcher
    if (Dep.target) {
      // 这里其实可以直接写self.addSub(Dep.target),
      // 没有这么写因为想还原源码的过程。
      console.log("Dep向当前收集器dep添加当前Wathcer");
      Dep.target.addDep(self);
    }
  }
  // 为当前收集器添加Watcher
  this.addSub = function(watcher) {
    console.log("Dep addSub为当前收集器添加watcher");
    self.subs.push(watcher);
  }
  // 通知收集器中所的所有Wathcer,调用其update方法
  this.notify = function() {
    console.log("Dep 通知所有的watcher 触发更新");
    for (let i = 0; i < self.subs.length; i += 1) {
      self.subs[i].update();
      console.log(`第${i}个watcher`, self.subs[i]);
    }
  }
}
const vue = new Vue({
  data() {
    return {
      text: 'hello world',
      text1: "aaa",
      text2: "333"
    };
  }
})
console.log("触发挂载-----------------------");
vue.mount(); // in get
console.log(vue._data.text);
console.log(vue._data.text1);
console.log(vue._data.text2);
console.log('data set调用------------------------------------------------------');
vue._data.text = '123'; // in watcher update /n in get
console.log('data set调用-----------------------------------------------------------------');
vue._data.text = 'aaaaa';
console.log(vue._data.text);
console.log(vue._data.text1);
console.log(vue._data.text2);