1. 程式人生 > >Vue原始碼解讀之Dep,Observer和Watcher

Vue原始碼解讀之Dep,Observer和Watcher

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

整體過程

Vue例項化一個物件的具體過程如下:

  1. 新建立一個例項後,Vue呼叫compile將el轉換成vnode。
  2. 呼叫initState, 建立props, data的鉤子以及其物件成員的Observer(新增getter和setter)。
  3. 執行mount掛載操作,在掛載時建立一個直接對應render的Watcher,並且編譯模板生成render函式,執行vm._update來更新DOM。
  4. 每當有資料改變,都將通知相應的Watcher執行回撥函式,更新檢視。

    • 當給這個物件的某個屬性賦值時,就會觸發set方法。
    • set函式呼叫,觸發Dep的notify()向對應的Watcher通知變化。
    • Watcher呼叫update方法。

在這裡插入圖片描述

在這個過程中:

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

在這裡插入圖片描述

接下來我們來分析一下物件具體的程式碼實現。

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()實現掛載。


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函式、空函式這三個引數。


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...
十分感謝

來源:https://segmentfault.com/a/1190000016208088