Vue 源碼解析(實例化前) - 初始化全局API(三)

前言

這兩天的把 1w 多行的 vue 過了一遍,看了一下把剩下在實例化 vue 構造函數前作的全部事情,作了一個總結,寫下了這個最終章,文中有哪些問題,還但願你們能夠指出。javascript

正文

爲VNode原型添加 child 屬性監聽

var VNode = function VNode( tag, data, children, text, elm, context, componentOptions, asyncFactory ) {
  this.tag = tag;
  this.data = data;
  this.children = children;
  this.text = text;
  this.elm = elm;
  this.ns = undefined;
  this.context = context;
  this.fnContext = undefined;
  this.fnOptions = undefined;
  this.fnScopeId = undefined;
  this.key = data && data.key;
  this.componentOptions = componentOptions;
  this.componentInstance = undefined;
  this.parent = undefined;
  this.raw = false;
  this.isStatic = false;
  this.isRootInsert = true;
  this.isComment = false;
  this.isCloned = false;
  this.isOnce = false;
  this.asyncFactory = asyncFactory;
  this.asyncMeta = undefined;
  this.isAsyncPlaceholder = false;
};

var prototypeAccessors = { child: { configurable: true } };

prototypeAccessors.child.get = function () {
  return this.componentInstance
};

Object.defineProperties(VNode.prototype, prototypeAccessors);
複製代碼

經過 Object.definePropertiesVNode 的原型綁定了對象 prototypeAccessorsprototypeAccessors 設置 child 是可修改的狀態。vue

初始化Mixin

initMixin(Vue);
複製代碼

在初始化的時候,給 initMixin 傳入了 Vue 構造函數:java

function initMixin(Vue) {
  Vue.prototype._init = function (options) {
    var vm = this;
    
    vm._uid = uid$3++;

    var startTag, endTag;
    
    if (config.performance && mark) {
      startTag = "vue-perf-start:" + (vm._uid);
      endTag = "vue-perf-end:" + (vm._uid);
      mark(startTag);
    }

    vm._isVue = true;
    // 合併選項
    if (options && options._isComponent) {
      initInternalComponent(vm, options);
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    
    {
      initProxy(vm);
    }
    
    vm._self = vm;
    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callHook(vm, 'beforeCreate');
    initInjections(vm); // 在data/props以前解決注入
    initState(vm);
    initProvide(vm); // 解決後提供的data/props
    callHook(vm, 'created');

    if (config.performance && mark) {
      vm._name = formatComponentName(vm, false);
      mark(endTag);
      measure(("vue " + (vm._name) + " init"), startTag, endTag);
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  };
}
複製代碼
Vue.prototype._init = function (options) {}
複製代碼

在初始化的時候,爲 Vue 在 原型上,添加了個 _init 方法,這個方法在實例化 vue 構造函數的時候會被調用:express

function Vue(options) {
    if (!(this instanceof Vue)
    {
      warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
}
複製代碼

這裏對 _init 不作太多的解釋,你們看一下 _init 都作了什麼,等到接下來說解實例化後 vue 的時候,會對這裏作詳細的解釋,包括生命週期的實現。api

state 的 mixin

stateMixin(Vue);
複製代碼

在處理完 initMixin 後,接着對 state 作了 mixin 的處理,給 stateMixin 傳入了 Vue 構造函數。數組

function stateMixin(Vue) {
  var dataDef = {};
  dataDef.get = function () { return this._data };
  var propsDef = {};
  propsDef.get = function () { return this._props };
  {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      );
    };
    propsDef.set = function () {
      warn("$props is readonly.", this);
    };
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef);
  Object.defineProperty(Vue.prototype, '$props', propsDef);

  Vue.prototype.$set = set;
  Vue.prototype.$delete = del;

  Vue.prototype.$watch = function ( expOrFn, cb, options ) {
    var vm = this;
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {};
    options.user = true;
    var watcher = new Watcher(vm, expOrFn, cb, options);
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value);
      } catch (error) {
        handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
      }
    }
    return function unwatchFn() {
      watcher.teardown();
    }
  };
}
複製代碼

官方給該方法的解釋是:緩存

在使用object.defineproperty時,流在某種程度上存在直接聲明的定義對象的問題,所以咱們必須在此處按程序構建該對象。
複製代碼
var dataDef = {};
dataDef.get = function () { return this._data };
var propsDef = {};
propsDef.get = function () { return this._props };
{
dataDef.set = function () {
  warn(
    'Avoid replacing instance root $data. ' +
    'Use nested data properties instead.',
    this
  );
};
propsDef.set = function () {
  warn("$props is readonly.", this);
};
}
Object.defineProperty(Vue.prototype, '$data', dataDef);
Object.defineProperty(Vue.prototype, '$props', propsDef);
複製代碼

一開始,聲明瞭兩個對象 dataDefpropsDef ,並分別添加了 setget , 在咱們操做 vm.$data 的時候,返回的就是 this._data$props 也是如此;bash

Vue.prototype.$set = set;
Vue.prototype.$delete = del;
複製代碼

這裏就是給 vue 實例綁定了 setdel 方法,這兩個方法在以前的章節將結果,連接在 Vue 源碼解析(實例化前) - 初始化全局API(二)async

Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
    var vm = this;
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {};
    options.user = true;
    var watcher = new Watcher(vm, expOrFn, cb, options);
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value);
      } catch (error) {
        handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
      }
    }
    return function unwatchFn() {
      watcher.teardown();
    }
};
複製代碼

這裏,就是咱們在作 vue 項目當中,常用的 api 之一,這裏就是它的具體實現。ide

具體的用法,和參數的意思,能夠看官方的 api 文檔,那裏對參數的解釋和用法寫的很是清楚,我這裏就講一下怎麼實現的,具體意思,你們看 api 文檔就好。

官方api文檔路徑:vm.$watch( expOrFn, callback, [options] )

在一開始,把當前的 this 指針存儲在一個 vm 當變量當中;

檢測若是當前的 cb 是對象的話,返回一個 createWatcher 方法:

function createWatcher( vm, expOrFn, handler, options ) {
  if (isPlainObject(handler)) {
    options = handler;
    handler = handler.handler;
  }
  if (typeof handler === 'string') {
    handler = vm[handler];
  }
  return vm.$watch(expOrFn, handler, options)
}
複製代碼

這是 createWatcher 的實現。

其實就是對在調用 vm.$watch 時接受到參數,包括當前的 vue 實例,而後仍是第一步要檢查 handler 是否是對象,這裏的 handler 就是以前的 cb

若是是對象的話,就用 handler 覆蓋 optionshandler.handler 去看成當前的 handler 去使用;

若是 handler 是字符串的話,就去把當前 vue 實例的該屬性,看成 handler 去使用;

最後,返回一個新的 vm.$watch

options = options || {};
options.user = true;
複製代碼

檢查接收的參數 options 是否存在,不存在就設置一個空對象;

設置的 options.usertrue

var watcher = new Watcher(vm, expOrFn, cb, options);
複製代碼

在設置完 options.user 後,就實例化了 Watcher 這個構造函數,這裏是很是核心的一塊內容,但願你們能夠仔細看看,這一塊看成一個大分類來說,先把下面的兩行給講了:

if (options.immediate) {
  try {
    cb.call(vm, watcher.value);
  } catch (error) {
    handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
  }
}
return function unwatchFn() {
  watcher.teardown();
}
複製代碼

若是設置了 options.immediatetrue ,那麼把當前 cbthis 指向 vue 的實例化構造函數,並把 watcher.value 傳給 cb

最後返回一個函數,調用 watcher.teardown

Watcher 實現

Watcher 構造函數

var Watcher = function Watcher(
  vm,
  expOrFn,
  cb,
  options,
  isRenderWatcher
) {
  this.vm = vm;
  if (isRenderWatcher) {
    vm._watcher = this;
  }
  vm._watchers.push(this);
  if (options) {
    this.deep = !!options.deep;
    this.user = !!options.user;
    this.lazy = !!options.lazy;
    this.sync = !!options.sync;
    this.before = options.before;
  } else {
    this.deep = this.user = this.lazy = this.sync = false;
  }
  this.cb = cb;
  this.id = ++uid$1;
  this.active = true;
  this.dirty = this.lazy;
  this.deps = [];
  this.newDeps = [];
  this.depIds = new _Set();
  this.newDepIds = new _Set();
  this.expression = expOrFn.toString();
  if (typeof expOrFn === 'function') {
    this.getter = expOrFn;
  } else {
    this.getter = parsePath(expOrFn);
    if (!this.getter) {
      this.getter = noop;
      warn(
        "Failed watching path: \"" + expOrFn + "\" " +
        'Watcher only accepts simple dot-delimited paths. ' +
        'For full control, use a function instead.',
        vm
      );
    }
  }
  this.value = this.lazy
    ? undefined
    : this.get();
};

Watcher.prototype.get = function get() {
  pushTarget(this);
  var value;
  var vm = this.vm;
  try {
    value = this.getter.call(vm, vm);
  } catch (e) {
    if (this.user) {
      handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
    } else {
      throw e
    }
  } finally {
   
    if (this.deep) {
      traverse(value);
    }
    popTarget();
    this.cleanupDeps();
  }
  return value
};

Watcher.prototype.addDep = function addDep(dep) {
  var id = dep.id;
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id);
    this.newDeps.push(dep);
    if (!this.depIds.has(id)) {
      dep.addSub(this);
    }
  }
};

Watcher.prototype.cleanupDeps = function cleanupDeps() {
  var i = this.deps.length;
  while (i--) {
    var dep = this.deps[i];
    if (!this.newDepIds.has(dep.id)) {
      dep.removeSub(this);
    }
  }
  var tmp = this.depIds;
  this.depIds = this.newDepIds;
  this.newDepIds = tmp;
  this.newDepIds.clear();
  tmp = this.deps;
  this.deps = this.newDeps;
  this.newDeps = tmp;
  this.newDeps.length = 0;
};

Watcher.prototype.update = function update() {
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
};

Watcher.prototype.run = function run() {
  if (this.active) {
    var value = this.get();
    if (
      value !== this.value ||
      isObject(value) ||
      this.deep
    ) {
      var oldValue = this.value;
      this.value = value;
      if (this.user) {
        try {
          this.cb.call(this.vm, value, oldValue);
        } catch (e) {
          handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
        }
      } else {
        this.cb.call(this.vm, value, oldValue);
      }
    }
  }
};

Watcher.prototype.evaluate = function evaluate() {
  this.value = this.get();
  this.dirty = false;
};

Watcher.prototype.depend = function depend() {
  var i = this.deps.length;
  while (i--) {
    this.deps[i].depend();
  }
};

Watcher.prototype.teardown = function teardown() {
  if (this.active) {
    if (!this.vm._isBeingDestroyed) {
      remove(this.vm._watchers, this);
    }
    var i = this.deps.length;
    while (i--) {
      this.deps[i].removeSub(this);
    }
    this.active = false;
  }
};
複製代碼

這是有關 Watcher 構造函數全部實現的代碼。

Watcher初始化

this.vm = vm;
if (isRenderWatcher) {
    vm._watcher = this;
}
複製代碼

當前的 watcher 對象的 vm 屬性指向的是 vue 實例化對象;

若是 isRenderWatchertrue 時, vue_watcher 指向當前 this

vm._watchers.push(this);
複製代碼

vue 實例化對象的 _watchers 數組添加一個數組項,就是當前的 watcher 實例化對象;

if (options) {
    this.deep = !!options.deep;
    this.user = !!options.user;
    this.lazy = !!options.lazy;
    this.sync = !!options.sync;
    this.before = options.before;
} else {
    this.deep = this.user = this.lazy = this.sync = false;
}
this.cb = cb;
this.id = ++uid$1;
this.active = true;
this.dirty = this.lazy;
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = expOrFn.toString();
複製代碼

這裏就是 watcher 初始化的一些屬性值;

var bailRE = /[^\w.$]/;
function parsePath(path) {
    if (bailRE.test(path)) {
      return
    }
    var segments = path.split('.');
    return function (obj) {
      for (var i = 0; i < segments.length; i++) {
        if (!obj) { return }
        obj = obj[segments[i]];
      }
      return obj
    }
}
if (typeof expOrFn === 'function') {
    this.getter = expOrFn;
} else {
    this.getter = parsePath(expOrFn);
    if (!this.getter) {
      this.getter = noop;
      warn(
        "Failed watching path: \"" + expOrFn + "\" " +
        'Watcher only accepts simple dot-delimited paths. ' +
        'For full control, use a function instead.',
        vm
      );
    }
}
複製代碼

若是接收到的 expOrFn 是個函數的話,當前 thisgetter 就指向它;

不然,經過 parsePath 去格式化當前的路徑;

若是 expOrFn 並非已單詞字符結尾的,就直接返回,設置一個空的 noop 函數給當前實例的 getter 屬性;

expOrFn 進行切割,遍歷切割後的 expOrFn ,並把切割後的每一個數組項看成要返回的函數的接收到的 obj 的屬性。

this.value = this.lazy ? undefined : this.get();
複製代碼

若是 lazytrue 的話,this.value 的就是 undefinend ,不然就是調用 this.get 方法

Watcher 獲取值

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

function popTarget() {
    targetStack.pop();
    Dep.target = targetStack[targetStack.length - 1];
}
Watcher.prototype.get = function get() {
  pushTarget(this);
  var value;
  var vm = this.vm;
  try {
    value = this.getter.call(vm, vm);
  } catch (e) {
    if (this.user) {
      handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
    } else {
      throw e
    }
  } finally {
   
    if (this.deep) {
      traverse(value);
    }
    popTarget();
    this.cleanupDeps();
  }
  return value
};
複製代碼

Vue 源碼解析(實例化前) - 響應式數據的實現原理 講解 Dep 構造函數的時候,涉及到過,這裏開始仔細講解,具體調用的地方,能夠去以前的章節查看。

get 在一開始的時候,講調用了 pushTarget 方法,並把當前 watcher 實例化對象傳過去;

pushTarget 方法,就是給 targetStack 數組添加一個數組項,就是當前的 watcher 實例化對象;

把當前的 watcher 實例化對象 指向 Dep.target

this.getterthis 指向 vue 的實例化對象,並調用它,把當前的值去作獲取返回到 value

若是設置了 this.deeptrue ,就表明用戶想要發現對象內部值的變化,這個時候調用 traverse 函數,目的是遞歸遍歷一個對象以喚起全部轉換的getter,以便將對象內的每一個嵌套屬性收集爲「深度」依賴項,把最後的結果更新到 value

popTarget 把在 targetStack 數組中的最後一個刪除,並把 Dep.target 指向刪除後的數組的最後一個數組項。

在這裏,其實就是給在獲取當前數據時的 watcher 在一開始作了存儲 (targetStack),在全部值的展現和處理作完之後,在清空了存儲 (targetStack

this.cleanupDeps();
複製代碼

清除依賴項集合,接下來說。

return value
複製代碼

最後返回 value

Watcher 添加隊列

Watcher.prototype.addDep = function addDep(dep) {
  var id = dep.id;
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id);
    this.newDeps.push(dep);
    if (!this.depIds.has(id)) {
      dep.addSub(this);
    }
  }
};
複製代碼

檢查隊列是否存在當前的 id ,該 id 其實就是 Dep 的實例化對象的 id,把它添加到對應的隊列裏面去;

這裏其實比較簡單明瞭,就不作太複雜的解釋了,你們看一眼就明白了。

Watcher 清空隊列

Watcher.prototype.cleanupDeps = function cleanupDeps() {
  var i = this.deps.length;
  while (i--) {
    var dep = this.deps[i];
    if (!this.newDepIds.has(dep.id)) {
      dep.removeSub(this);
    }
  }
  var tmp = this.depIds;
  this.depIds = this.newDepIds;
  this.newDepIds = tmp;
  this.newDepIds.clear();
  tmp = this.deps;
  this.deps = this.newDeps;
  this.newDeps = tmp;
  this.newDeps.length = 0;
};
複製代碼

把當前 Watcher 監聽隊列裏的 Watcher 對象從後往前清空,在把一些屬性初始化。

Watcher 更新

Watcher.prototype.update = function update() {
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
};

複製代碼

若是是懶更新的話,設置 dirtytrue

若是是同步更新的話,直接調用 run 方法;

不然,調用 queueWatcher 方法。

// 將觀察者推入觀察者隊列。
// 具備重複ID的工做將被跳過,除非在刷新隊列時將其推送。
function queueWatcher(watcher) {
  var id = watcher.id;
  if (has[id] == null) {
    has[id] = true;
    if (!flushing) {
      queue.push(watcher);
    } else {
      // 若是已經刷新,則根據其ID拼接觀察程序
      // 若是已經超過了它的ID,它將當即運行。
      var i = queue.length - 1;
      while (i > index && queue[i].id > watcher.id) {
        i--;
      }
      queue.splice(i + 1, 0, watcher);
    }
    // 刷新隊列
    if (!waiting) {
      waiting = true;

      if (!config.async) {
        flushSchedulerQueue();
        return
      }
      nextTick(flushSchedulerQueue);
    }
  }
}
複製代碼

若是設置的 config.async 是同步的,那麼就刷新兩個隊列並運行 Watcher 結束當前方法;

不然的話,執行 nextTick 後執行 flushSchedulerQueue

var MAX_UPDATE_COUNT = 100;

var queue = [];
var activatedChildren = [];
var has = {};
var circular = {};
var waiting = false;
var flushing = false;
var index = 0;

// 重置計劃程序的狀態
function resetSchedulerState() {
  index = queue.length = activatedChildren.length = 0;
  has = {};
  {
    circular = {};
  }
  waiting = flushing = false;
}

function flushSchedulerQueue() {
  flushing = true;
  var watcher, id;
  //在刷新以前排隊隊列。
  //這能夠確保:
  // 1.組件從父級更新爲子級。 (由於父母老是在孩子面前建立)
  // 2.組件的用戶觀察者在其渲染觀察者以前運行(由於在渲染觀察者以前建立用戶觀察者)
  // 3.若是在父組件的觀察程序運行期間銷燬了組件,能夠跳過其觀察者。
  queue.sort(function (a, b) { return a.id - b.id; });

  //不要緩存長度,由於可能會推送更多的觀察程序
  //當咱們運行現有的觀察程序時
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index];
    if (watcher.before) {
      watcher.before();
    }
    id = watcher.id;
    has[id] = null;
    watcher.run();
    //在開發構建中,檢查並中止循環更新。
    if (has[id] != null) {
      circular[id] = (circular[id] || 0) + 1;
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? ("in watcher with expression \"" + (watcher.expression) + "\"")
              : "in a component render function."
          ),
          watcher.vm
        );
        break
      }
    }
  }

  // 重置狀態前保留髮布隊列的副本
  var activatedQueue = activatedChildren.slice();
  var updatedQueue = queue.slice();

  resetSchedulerState();

  // 調用組件更新和激活的鉤子
  callActivatedHooks(activatedQueue);
  callUpdatedHooks(updatedQueue);

  if (devtools && config.devtools) {
    devtools.emit('flush');
  }
}
function callUpdatedHooks(queue) {
    var i = queue.length;
    while (i--) {
      var watcher = queue[i];
      var vm = watcher.vm;
      if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'updated');
      }
    }
}
function callActivatedHooks(queue) {
  for (var i = 0; i < queue.length; i++) {
    queue[i]._inactive = true;
    activateChildComponent(queue[i], true /* true */);
  }
}
複製代碼

Watcher 運行

Watcher.prototype.run = function run() {
  if (this.active) {
    var value = this.get();
    if (value !== this.value || isObject(value) || this.deep) {
      var oldValue = this.value;
      this.value = value;
      if (this.user) {
        try {
          this.cb.call(this.vm, value, oldValue);
        } catch (e) {
          handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
        }
      } else {
        this.cb.call(this.vm, value, oldValue);
      }
    }
  }
};
複製代碼

只有 activetrue 的時候,執行 run 纔有效果,由於 active 在調用 teardown 方法的時候會變成 false

檢查新值和舊值是否相同,若是不相同的話,把新值和舊值傳遞給 cb 回調,並把 this 指向 vue 實例。

這樣可能你們理解的會更容易點。

Watcher 評估

Watcher.prototype.evaluate = function evaluate() {
  this.value = this.get();
  this.dirty = false;
};
複製代碼

評估觀察者的價值,這隻適用於懶惰的觀察者。

Watcher 依賴

Watcher.prototype.depend = function depend() {
  var i = this.deps.length;
  while (i--) {
    this.deps[i].depend();
  }
};
複製代碼

這裏就是真正的實現通知依賴的部分。

Watcher 卸載

Watcher.prototype.teardown = function teardown() {
  if (this.active) {
    if (!this.vm._isBeingDestroyed) {
      remove(this.vm._watchers, this);
    }
    var i = this.deps.length;
    while (i--) {
      this.deps[i].removeSub(this);
    }
    this.active = false;
  }
};
複製代碼

清空全部依賴,從後往前。

結束語

原本是準備這一章把 Vue 構造函數實例化前要作的全部事情都寫完,發如今 statemixin 時候,涉及到了 watcher ,可是發現了,就先講解了,以後仍是有一樣量的內容,因此仍是準備單拿出來一講,篇幅太長了對你們學習和吸取並不友好。

接下來的一章,會講到:

eventsminxin$on$once$off$emit

lifecycleminxinupdated$forceUpdate$destroy

renderminxin$nextTickrender

下一章,就是 vue 源碼解析(實例化前) - 初始化全局 API(最終章)了,文中有寫的不對的,還但願你們能夠積極指出。

相關文章
相關標籤/搜索