圖解vueVue響應式原理

本文是受當面試官問你Vue響應式原理,你能夠這麼回答他
啓發自行繪製的vue動態響應原理圖解 畫風詭異,僅表明我的理解若是有不當的地方,望請斧正。vue

官方大圖
面試

圖片alt

個人看法
圖片alt

JserWang的原理代碼,爲了看懂我自行添加了好多註釋與console。bash

//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);
複製代碼
相關文章
相關標籤/搜索