1. 程式人生 > 其它 >Vue資料雙向繫結原理 ( Vue.version = 2.6.14 )

Vue資料雙向繫結原理 ( Vue.version = 2.6.14 )

Vue資料雙向繫結原理;Vue如何監測Object和Array的變化,其中的不足以及Vue的解決方案

Vue資料雙向繫結原理 ( Vue.version = 2.6.14 )

分析主要程式碼

1. 監測Object的變化

vue.commone.dev.js檔案中,Vue在defineReactive$$1方法中監聽物件的屬性變化。

/**
 * defineReactive$$1
 */
function defineReactive$$1 (
  obj,
  key,
  val,監測
  customSetter,
  shallow
) {

  // 建立Dep例項
  const dep = new Dep();

  // 獲取屬性預設的 getter/setter ,防止重新定義getter/setter 的時候覆蓋預設行為
  const descriptor = Object.getOwnPropertyDescriptor(obj, key);
  const getter = descriptor && descriptor.get;
  const setter = descriptor && descriptor.set;

  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function() {
      const value = getter ? getter.call(obj) : val;
      if (Dep.target) {

        /**
         * 1. 呼叫Dep.prototype.depend
         * 2. 在Dep.prototype.depend中呼叫Watcher.prototype.addDep
         * 3. Watcher.prototype.addDep中將Dep例項儲存到Watcher例項的newDepIds、newDeps屬性上,再將Watcher例項儲存到Dep例項的subs屬性上
         */
        dep.depend();
      }
      return value;
    },
    set: function(newVal) {
      const value = getter ? getter.call(obj) : val;

      // 設定新的值
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }

      /**
       * 1. 呼叫Dep.prototype.notify
       * 2. 在Dep.prototype.notify中遍歷Dep例項上的subs屬性 ( getter中儲存的Watcher例項 )
       * 3. 呼叫Watcher.prototype.update更新檢視 ( 具體update中做了什麼,下次單獨分析 )
       */
      dep.notify(); 
    }
  })
}

整體流程:

① 通過observer,使用Object.definePropertydata的獲取、變化能夠被監測

② 獲取data時,觸發getter, 將依賴新增到watcher中,將wacher新增到依賴subs ( 訂閱? )

③ 更新data時,觸發setter,通知依賴遍歷subs,並呼叫watcherupdate方法更新檢視 ( 其實最終呼叫的是Vue例項_render方法 )

疑問:

watcher是在何時建立的?

initComputed階段建立watcher,並且watcher的vm屬性指向Vue例項,將watcher儲存到Vue例項的_watchers

不足:

  • 向物件中新增或刪除屬性時,無法監測到變化
  • 無法監測陣列的變化
2. 監測Array的變化

定義攔截器,在呼叫修改陣列的方法時,觸發攔截器。

const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);

// 能夠改變陣列自身的7個方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];

// 遍歷methodsToPatch並修改arrayMethods上對應名稱的方法
methodsToPatch.forEach(method => {
  Object.defineProperty(
    arrayMethods,
    method,
    {
      value: function() {

        /**
         * clone引數
         */
        const args = [];
        let len = arguments.length;
        while (len--) {
          args[len] = arguments[len];
        }

        // 呼叫原生的方法
        const result = arrayProto[method].apply(this, args);

        /**
         * 呼叫 push/unshift/splice時若新增/更新 了新元素
         * 則呼叫observe觀察該元素
         */
        const ob = this.__ob__;
        let inserted;
        switch (method) {
          case 'push':
          case 'unshift':
            inserted = args;
            break;
          case 'splice':
            inserted = args.slice(2);
            break;
          default:
            break;
        }
        if (inserted) {
          ob.observeArray(inserted);
        }

        // 通知依賴更新
        ob.dep.notify();

        return result;
      }
    }
  )
})

整體流程:

① 給Array.prototype上能改變陣列本身的方法新增攔截器

② 在Observer的構造器中Array例項的__proto__指向arrayMethods

③ 呼叫這些方法時,通知依賴更新

不足:

  • 通過索引修改陣列時,無法監測到陣列的變化
3. Vue對以上不足的解決方案

增加了兩個全域性API:Vue.setVue.delete