瞭解一下Vue - [Vue是怎麼實現響應式的(三)]

寫在前面:本文爲個人在日常工作和學習中的一些總結,便於後來查漏補缺,非權威性資料,請帶着自己的思考^-^。
前文鏈接:瞭解一下Vue - [Vue是怎麼實現響應式的(一)]
瞭解一下Vue - [Vue是怎麼實現響應式的(二)]
前面對於響應式有了一些些瞭解,這裏嘗試自己寫一遍(抄一遍)
設對象data爲要被定義的響應式對象,key爲data中的屬性

  1. 通過defineProperty爲data中的每一個屬性key定義getter/setter,這樣在data[key]被引用和被賦值時將觸發相應的操作,這個是響應式實現的基礎;
  2. dep對象,作爲依賴實體,其包含依賴添加、依賴者(watcher)添加、變更通知(notify)等方法,用於依賴收集、變更分發,本身包含觀察者列表;
  3. watcher對象,觀察者對象,包含依賴收集、更新方法;用於依賴收集、更新,包含依賴列表;

Dep構造函數

  let uid = 0;

  class Dep {
    constructor() {
      this.id = ++uid;
      this.subs = [];
    }
    addSub(target) { // 將target添加進觀察者列表
      this.subs.push(target);
    }
    depend() { // 
      if (Dep.target) {
        Dep.target.addDep(this);
      }
    }
    removeSub(target) {
      this.subs.splice(this.subs.findIndex(_ => _.id == target.id), 1);
    }
    notify() {
      const subs = this.subs.slice();
      for (let i = 0; i < subs.length; i++) {
        subs[i].update();
      }
    }
  }

  Dep.target = null; // 當前處於激活狀態的watcher
  const targetStack = []; // 存放watcher的棧

  const pushTarget = target => { // 更新當前激活的Dep.target
    if (Dep.target) {
      targetStack.push(target); // 將target(watcher)壓入棧中
    }
    Dep.target = target;
  };

  const popTarget = () => { // 從watcher棧中彈出頂部watcher
    Dep.target = targetStack.pop();
  };

watcher構造函數

  class Watcher {
    constructor(getter, options = {}) {
      this.deps = []; // 依賴列表
      this.newDeps = []; // 最近一次添加的依賴列表
      this.depIds = new Set(); // 依賴ids列表
      this.newDepIds = new Set(); // 最近一次添加的依賴ids列表
      this.getter = getter; // 
      this.lazy = !!options.lazy; // 懶依賴,在首次實例化的時候不執行getter
      this.dirty = this.lazy; // 髒值標識,主要用在computed計算時;
      this.lazy ? undefined : this.get();
    }
    get() {
      pushTarget(this); // 將當前watcher作爲激活的watcher對象,並推入targetStack棧中
      const value = this.getter();
      popTarget(); // 將當前watcher置爲棧中上一個watcher
      this.cleanupDeps(); // 依賴整理,主要用來整理this.deps、this.depIds
      return value;
    }
    addDep(dep) {
      if (!this.newDepIds.has(dep.id)) {
        this.newDepIds.add(dep.id);
        this.newDeps.push(dep);
        if (!this.depIds.has(dep.id)) {
          dep.addSub(this);
        }
      }
    }
    cleanupDeps() {
      let i = this.deps.length;
      while (i--) {
        const dep = this.deps[i];
        if (!this.newDepIds.has(dep.id)) { // 如果新的依賴列表中不再包含之前的依賴項,則調用dep.removeSub方法,將當前watcher從dep.subs列表中移除
          dep.removeSub(this);
        }
      }
      [this.deps, this.newDeps] = [this.newDeps, this.deps];
      this.newDeps.length = 0;
      [this.depIds, this.newDepIds] = [this.newDepIds, this.depIds];
      this.newDepIds.clear();
    }
    evaluate() {
      this.value = this.get();
      this.dirty = false;
    }
    update() {
      if (this.lazy) {
        this.dirty = true;
      } else {
        new Promise((resolve) => {
          resolve();
        }).then(() => {
          this.get();
        });
      }
    }
    depend() { // 將當前watcher的依賴添加到當前Dep.target的依賴列表中
      const deps = this.deps;
      for (let i = 0; i < deps.length; i++) {
        deps[i].depend();
      }
    }
  }

defineReactive 方法

用來爲data[key]定義getter/setter

  const defineReactive = (target, key, val) => {
    const dep = new Dep(); // 這裏實例化dep對象,用於在getter/setter觸發的時候訪問該對象進行依賴收集等操作,本質上來說當前實例化的dep和當前的data[key]一一對應了
    Object.defineProperty(target, key, {
      enumerable: true,
      configurable: true,
      get() {
        if (Dep.target) {
          dep.depend(); // 依賴添加
        }
        return val;
      },
      set(newVal) {
        if (val === newVal) return;
        val = newVal;
        dep.notify();
      },
    });
  };

data、computed初始化

  const initData = data => {
    const keys = Object.keys(data);
    for (let i = 0; i < keys.length; i++) {
      defineReactive(vm, keys[i], data[keys[i]]);
    }
  };

  const initComputed = computed => {
    const keys = Object.keys(computed);
    for (let i = 0; i < keys.length; i++) {
      const userDef = computed[keys[i]].bind(vm);
      const watcher = new Watcher(userDef, { lazy: true });

      Object.defineProperty(vm, keys[i], {
        enumerable: true,
        configurable: true,
        get() {
          if (watcher) {
            if (watcher.dirty) {
              watcher.evaluate();
            }
            if (Dep.target) {
              watcher.depend();
            }
            return watcher.value;
          }
        },
      });
    }
  };

模擬頁面渲染:

<!-- html -->
<body>
  <div id="app"></div>
  <button id="btn">更新</button>
</body>
  const data = initData(vm.data);

  const computed = initComputed(vm.computed);

  const updateComponent = () => {
    const app = document.querySelector('#app');
    var a = vm.current; // 引用vm.current
    var b = vm.computedCurrent; // 應用computed
    app.innerHTML = `data: <i>${a}</i> computed: <strong>${b}</strong>`;
  };

  defineReactive(vm, 'current', vm.current); // 爲vm.current定義getter/setter
  const watcher = new Watcher(updateComponent);

可以看到點擊按鈕,更新vm中的current屬性,頁面成功進行了更新。。。

流程分析

初始化data屬性getter/setter --> 實例化watcher,帶有update方法、addDep方法 --> watcher.get方法執行進行依賴收集(該方法中被引用到的data屬性視爲當前watcher的依賴) --> watcher依賴收集的過程中被依賴的data屬性也會進行觀察者收集 --> data屬性更新 --> 通知watcher,update方法調用 --> 新一輪的依賴收集,新舊依賴比較,新依賴相對舊依賴缺失的從依賴列表中刪除,新增的加入依賴列表同時將觀察者watcher添加進該依賴的subs觀察者列表中 --> 執行業務代碼(視圖更新);

對於computed來說,它既是觀察者,也是依賴;視圖更新watcher依賴computed,computed依賴data;

對於watch來說,它是觀察者,依賴要watch的data或者computed,依賴更新時會notify(通知)它,執行相應的方法;

原理大抵如此,細節還有很多

THE END

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章