Vue源碼解讀之Dep,Observer和Watcher

在解讀Dep,Observer和Watcher以前,首先我去了解了一下Vue的數據雙向綁定,即MVVM,學習於:https://blog.csdn.net/u013321...
以及關於Observer和watcher的學習來自於:https://www.jb51.net/article/...javascript

總體過程

Vue實例化一個對象的具體過程以下:vue

  1. 新建立一個實例後,Vue調用compile將el轉換成vnode。
  2. 調用initState, 建立props, data的鉤子以及其對象成員的Observer(添加getter和setter)。
  3. 執行mount掛載操做,在掛載時創建一個直接對應render的Watcher,而且編譯模板生成render函數,執行vm._update來更新DOM。
  4. 每當有數據改變,都將通知相應的Watcher執行回調函數,更新視圖。java

    • 當給這個對象的某個屬性賦值時,就會觸發set方法。
    • set函數調用,觸發Dep的notify()向對應的Watcher通知變化。
    • Watcher調用update方法。

clipboard.png

在這個過程當中:node

  1. Observer是用來給數據添加Dep依賴。
  2. Dep是data每一個對象包括子對象都擁有一個該對象, 當所綁定的數據有變動時, 經過dep.notify()通知Watcher。
  3. Compile是HTML指令解析器,對每一個元素節點的指令進行掃描和解析,根據指令模板替換數據,以及綁定相應的更新函數。
  4. Watcher是鏈接Observer和Compile的橋樑,Compile解析指令時會建立一個對應的Watcher並綁定update方法 , 添加到Dep對象上。

clipboard.png

接下來咱們來分析一下對象具體的代碼實現。react

Observer

var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  // 給value添加__ob__屬性,值就是本Observer對象,value.__ob__ = this;
  // Vue.$data 中每一個對象都 __ob__ 屬性,包括 Vue.$data對象自己
  def(value, '__ob__', this);
  //判斷是否爲數組,不是的話調用walk()添加getter和setter
  //若是是數組,調用observeArray()遍歷數組,爲數組內每一個對象添加getter和setter
  if (Array.isArray(value)) {
    var augment = hasProto
      ? protoAugment
      : copyAugment;
    augment(value, arrayMethods, arrayKeys);
    this.observeArray(value);
  } else {
    this.walk(value);
  }
};

walk和defineReactive

// 遍歷每一個屬性並將它們轉換爲getter/setter。只有當值類型爲對象時才調用此方法。
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i]);
  }
};

function defineReactive (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  var dep = new Dep();
  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }
  //獲取已經實現的 getter /setter 方法
  var getter = property && property.get;
  var setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  var childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      //Dep.target 全局變量指向的就是當前正在解析指令的Complie生成的 Watcher
      // 會執行到 dep.addSub(Dep.target), 將 Watcher 添加到 Dep 對象的 Watcher 列表中
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if ("development" !== 'production' && customSetter) {
        customSetter();
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();//若是數據被從新賦值了, 調用 Dep 的 notify 方法, 通知全部的 Watcher
    }
  });
}

observeArray和observe

Observer.prototype.observeArray = function observeArray (items) {
  for (var i = 0, l = items.length; i < l; i++) {
    // 若是是數組繼續執行 observe 方法, 其中會繼續新建 Observer 對象, 直到窮舉完畢執行 walk 方法
    observe(items[i]);
  }
};

function observe (value, asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}

Dep

// Dep是訂閱者Watcher對應的數據依賴
var Dep = function Dep () {
  //每一個Dep都有惟一的ID
  this.id = uid++;
  //subs用於存放依賴
  this.subs = [];
};

//向subs數組添加依賴
Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};
//移除依賴
Dep.prototype.removeSub = function removeSub (sub) {
  remove(this.subs, sub);
};
//設置某個Watcher的依賴
//這裏添加了Dep.target是否存在的判斷,目的是判斷是否是Watcher的構造函數調用
//也就是說判斷他是Watcher的this.get調用的,而不是普通調用
Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};

Dep.prototype.notify = function notify () {
  var subs = this.subs.slice();
  //通知全部綁定 Watcher。調用watcher的update()
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};

Watcher

在initMixin()初始化完成Vue實例全部的配置以後,在最後根據el是否存在,調用$mount()實現掛載。express

if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }

$mount

//這是供外部使用的公共的方法
Vue.prototype.$mount = function (
  el,
  hydrating
) {
  el = el && inBrowser ? query(el) : undefined;
  return mountComponent(this, el, hydrating)
};

mountComponent

function mountComponent (
  vm,
  el,
  hydrating
) {
  vm.$el = el;
  //這個if判斷目的在檢測vm.$options.render
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode;
    {
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        );
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        );
      }
    }
  }
  // 調用鉤子函數
  callHook(vm, 'beforeMount');

  // 定義updateComponent,將做爲Watcher對象的參數傳入。
  var updateComponent;
  /* istanbul ignore if */
  if ("development" !== 'production' && config.performance && mark) {
    updateComponent = function () {
      var name = vm._name;
      var id = vm._uid;
      var startTag = "vue-perf-start:" + id;
      var endTag = "vue-perf-end:" + id;

      mark(startTag);
      var vnode = vm._render();
      mark(endTag);
      measure(("vue " + name + " render"), startTag, endTag);

      mark(startTag);
      vm._update(vnode, hydrating);
      mark(endTag);
      measure(("vue " + name + " patch"), startTag, endTag);
    };
  } else {
    updateComponent = function () {
      vm._update(vm._render(), hydrating);
    };
  }


  new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);
  hydrating = false;

  // 調用鉤子
  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }
  return vm
}

watcher

mountComponent在構造新的Watcher對象傳了當前vue實例、updateComponent函數、空函數這三個參數。segmentfault

var Watcher = function Watcher (
  vm,
  expOrFn,
  cb,
  options,
  isRenderWatcher
) {
  this.vm = vm;
  if (isRenderWatcher) {
    vm._watcher = this;
  }
  // 當前Watcher添加到vue實例上
  vm._watchers.push(this);
  // 參數配置,options默認false
  if (options) {
    this.deep = !!options.deep;
    this.user = !!options.user;
    this.computed = !!options.computed;
    this.sync = !!options.sync;
    this.before = options.before;
  } else {
    this.deep = this.user = this.computed = this.sync = false;
  }
  this.cb = cb;
  this.id = ++uid$1; // uid for batching
  this.active = true;
  this.dirty = this.computed; //用於計算屬性
  this.deps = [];
  this.newDeps = [];
  //內容不可重複的數組對象
  this.depIds = new _Set();
  this.newDepIds = new _Set();
  this.expression = expOrFn.toString();
  //將watcher對象的getter設爲updateComponent方法
  if (typeof expOrFn === 'function') {
    this.getter = expOrFn;
  } else {
    this.getter = parsePath(expOrFn);
    if (!this.getter) {
      this.getter = function () {};
      "development" !== 'production' && warn(
        "Failed watching path: \"" + expOrFn + "\" " +
        'Watcher only accepts simple dot-delimited paths. ' +
        'For full control, use a function instead.',
        vm
      );
    }
  }
  //若是是計算屬性,就建立Dep數據依賴,不然經過get獲取value
  if (this.computed) {
    this.value = undefined;
    this.dep = new Dep();
  } else {
    this.value = this.get();
  }
};
};

get

Watcher.prototype.get = function get () {
  pushTarget(this);//將Dep的target添加到targetStack,同時Dep的target賦值爲當前watcher對象
  var value;
  var vm = this.vm;
  try {
   // 調用updateComponent方法
    value = this.getter.call(vm, vm);
  } catch (e) {
    if (this.user) {
      handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
    } else {
      throw e
    }
  } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value);
    }
    popTarget();//update執行完成後,又將Dep.target從targetStack彈出。
    this.cleanupDeps();
  }
  return value
};

//這是全局惟一的,由於任什麼時候候均可能只有一個watcher正在評估。
Dep.target = null;
var targetStack = [];

function pushTarget (_target) {
  if (Dep.target) { targetStack.push(Dep.target); }
  Dep.target = _target;
}

function popTarget () {
  Dep.target = targetStack.pop();
}

Watcher的get方法實際上就是調用了updateComponent方法,updateComponent就是數組

updateComponent = function() {
        vm._update(vm._render(), hydrating);
    };

調用這個函數會接着調用_update函數更新dom,這個是掛載到vue原型的方法,而_render方法從新渲染了vnode。緩存

Vue.prototype._render = function () {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var _parentVnode = ref._parentVnode;
    // reset _rendered flag on slots for duplicate slot check
    {
      for (var key in vm.$slots) {
        // $flow-disable-line
        vm.$slots[key]._rendered = false;
      }
    }
    if (_parentVnode) {
      vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject;
    }
    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode;
    // render self
    var vnode;
    try {
      vnode = render.call(vm._renderProxy, vm.$createElement);
    } catch (e) {
      handleError(e, vm, "render");
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      {
        if (vm.$options.renderError) {
          try {
            vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
          } catch (e) {
            handleError(e, vm, "renderError");
            vnode = vm._vnode;
          }
        } else {
          vnode = vm._vnode;
        }
      }
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if ("development" !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        );
      }
      vnode = createEmptyVNode();
    }
    // set parent
    vnode.parent = _parentVnode;
    return vnode
  };

update

//當依賴項改變時調用。前面有提到。
Watcher.prototype.update = function update () {
    var this$1 = this;

  /* istanbul ignore else */
  //是否計算屬性
  if (this.computed) {
    if (this.dep.subs.length === 0) {
      this.dirty = true;
    } else {
      this.getAndInvoke(function () {
        this$1.dep.notify();
      });
    }
    //是否緩存
  } else if (this.sync) {
   //調用run方法執行回調函數
    this.run();
  } else {
    queueWatcher(this);
  }
};

Watcher.prototype.run = function run () {
  if (this.active) {
    //這裏的cb就是指watcher的回調函數
    this.getAndInvoke(this.cb);
  }
};

Watcher.prototype.getAndInvoke = function getAndInvoke (cb) {
  var value = this.get();
  if (value !== this.value ||isObject(value) ||this.deep) {
    //設置新的值
    var oldValue = this.value;
    this.value = value;
    this.dirty = false;
    if (this.user) {
      try {
        cb.call(this.vm, value, oldValue);
      } catch (e) {
        handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
      }
    } else {
      //執行回調函數
      cb.call(this.vm, value, oldValue);
    }
  }
};

後記

關於Vue數據雙向綁定的文章不少,查看越多資料越以爲本身只是淺薄,要更努力才行啊。
學習到比較系統思路的來自於:https://segmentfault.com/a/11...
十分感謝dom

相關文章
相關標籤/搜索