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

前言

在前面的兩章,介紹了Vue 源碼解析(實例化前) - 初始化全局API(一)Vue 源碼解析(實例化前) - 響應式數據的實現原理javascript

因爲數據雙向綁定的內容會比較多一些,並且涉及到的知識點也比較多,因此我當時就從初始化全局API裏面單獨拿了一章出來,去講解 vue 究竟是如何實現的數據雙向綁定,如今,接着把以前沒有講完的初始化全局API要作的事情,全都給講完。vue

仍是那句老話,若是以爲寫的哪裏不對了,還但願你們多多指出,歡迎評論;java

若是以爲不錯的話,點點關注,點點贊,謝謝你們,大家的支持,是我繼續寫下去的動力🙏react

正文

Vue.set = set;
Vue.delete = del;
Vue.nextTick = nextTick;

Vue.options = Object.create(null);
ASSET_TYPES.forEach(function (type) {
  Vue.options[type + 's'] = Object.create(null);
});

Vue.options._base = Vue;

extend(Vue.options.components, builtInComponents);

initUse(Vue);
initMixin$1(Vue);
initExtend(Vue);
initAssetRegisters(Vue);
複製代碼

這篇文章,講的就是 initGlobalAPI 函數剩下的這一部分。api


設置對象屬性(set)

Vue.set = set;
複製代碼

該屬性,就是用來設置對象的屬性,若是屬性不存在,則添加新屬性並觸發更改通知。數組

function set (target, key, val) {
  if (isUndef(target) || isPrimitive(target)
  ) {
    warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key);
    target.splice(key, 1, val);
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val
  }
  var ob = (target).__ob__;
  if (target._isVue || (ob && ob.vmCount)) {
    warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    );
    return val
  }
  if (!ob) {
    target[key] = val;
    return val
  }
  defineReactive(ob.value, key, val);
  ob.dep.notify();
  return val
}
複製代碼

set 方法,接收三個參數, target (目標)、 key (屬性)、val (值);app

function isUndef (v) {
    return v === undefined || v === null
}
function isPrimitive (value) {
    return (
      typeof value === 'string' ||
      typeof value === 'number' ||
      typeof value === 'symbol' ||
      typeof value === 'boolean'
    )
}
複製代碼

一進來,就先檢查,當前的目標是否是上面的這幾種類型,若是是的話,是沒有辦法去作相似根據對應的 key 去作值更新的;函數

這裏其實最主要檢查的就是當前的 target 是否是對象或者數組,但是做者在這裏,作了除對象和數組之外全部類型的判斷,不知道這樣作的目的是什麼,爲何不直接判斷數組和對象呢,那樣須要執行的代碼也會比較少,若是你們知道爲何要這麼作,歡迎評論。oop

if (Array.isArray(target) && isValidArrayIndex(key)) {
}
複製代碼

檢查當前的 target 是否是數組,而且 key 是否是有效的數組索引:post

function isValidArrayIndex (val) {
    var n = parseFloat(String(val));
    return n >= 0 && Math.floor(n) === n && isFinite(val)
}
複製代碼

這裏接收到的其實就是數組的 key ,先把 key 轉成字符串,而後經過 parseFloat 去格式化它;

而後去看 n 是否是大於0的正整數,而且不是無窮大的。

target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val
複製代碼

若是是數組,而且該索引也是合法的,那麼用接收到的 val 去替換當前索引爲 key 的索引項;

if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val
}
複製代碼

這裏要判斷的是若是當前的 target 是對象,而且存在當前 key , 而且當前 key 不是在 Object 的原型上的,那麼就用接收到新的 val 去替換當前的值;

var ob = (target).__ob__;
複製代碼

__ ob __ 屬性是咱們在打印 vue 的實例化對象時常常看到的,這個屬性其實就是在實例化 Observer 的時候,調用 def 函數去給當前目標添加的一個屬性:

// Observer部分
var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
    if (hasProto) {
      protoAugment(value, arrayMethods);
    } else {
      copyAugment(value, arrayMethods, arrayKeys);
    }
    this.observeArray(value);
  } else {
    this.walk(value);
  }
};
// def部分
function def (obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
      value: val,
      enumerable: !!enumerable,
      writable: true,
      configurable: true
    });
}
複製代碼

在這裏對 Observer 不作太多的講解,由於如今主要講的是 set 部分,在往下講,可能會比較亂,你們先本身看一下,看不懂的話,在接下來說實例化 vue 部分的時候在仔細對 vue 的每個重要的 api 作詳細的講解。

_isVue 和 vmCount 在 Vue 源碼解析(實例化前) - 數據雙向綁定的實現原理 這裏有作過講解,你們不瞭解這兩個值是幹什麼的,能夠來這裏看一下。

if (!ob) {
    target[key] = val;
    return val
}
複製代碼

若是當前的目標沒有 __ ob __屬性,那麼就直接作賦值,而且返回;

defineReactive(ob.value, key, val);
ob.dep.notify();
複製代碼

這裏在上面的連接裏面,就能夠看到具體作了哪些事情,這裏就不浪費篇幅去寫了;

最後直接返回當前值。

這裏總結一下,其實就是若是是存在值的話,直接更新值,若是不存在的話,就經過 defineReactive 去綁定一下
複製代碼

刪除對象屬性(del)

Vue.delete = del;
複製代碼

這個方法就是在刪除屬性並在必要時觸發更改。

function del (target, key) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target))));
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1);
    return
  }
  var ob = (target).__ob__;
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    );
    return
  }
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key];
  if (!ob) {
    return
  }
  ob.dep.notify();
}
複製代碼

這個其實和 set 作的事情差很少,只是修改變成了刪除,這裏就很少講了。

nextTick

function nextTick(cb, ctx) {
    var _resolve;
    callbacks.push(function () {
      if (cb) {
        try {
          cb.call(ctx);
        } catch (e) {
          handleError(e, ctx, 'nextTick');
        }
      } else if (_resolve) {
        _resolve(ctx);
      }
    });
    if (!pending) {
      pending = true;
      if (useMacroTask) {
        macroTimerFunc();
      } else {
        microTimerFunc();
      }
    }
    if (!cb && typeof Promise !== 'undefined') {
      return new Promise(function (resolve) {
        _resolve = resolve;
      })
    }
}
複製代碼

callbacks 是一個全局的消息頻道,在每次調用 nextTick的時候,去添加一個 function ,這個 function 裏面最主要作的就是一個判斷 cb 是否存在,存在就把 cb 的 this 指向 ctx,若是 _resolve 存在的話,就直接調用它,並把 ctx 傳給它。

if (!pending) {
  pending = true;
  if (useMacroTask) {
    macroTimerFunc();
  } else {
    microTimerFunc();
  }
}
複製代碼

pending 是一個全局的狀態,初始化是 false ,在第一次調用它的時候,直接改成 true ,而且要檢查當前執行的是宏任務仍是微任務,在執行到任務棧後,pending 變爲 false,具體什麼是宏任務,什麼是微任務,看這裏:js事件循環機制(event loop)

if (!cb && typeof Promise !== 'undefined') {
  return new Promise(function (resolve) {
    _resolve = resolve;
  })
}
複製代碼

這裏代碼很簡單,這裏就不作太多解釋了,你們不懂了評論把。😂


Vue.options = Object.create(null);
複製代碼

給 Vue 的 options 添加一個原子。

ASSET_TYPES.forEach(function (type) {
  Vue.options[type + 's'] = Object.create(null);
});
複製代碼

給 options 添加 n 個屬性,每一個屬性都是一個原子:

var ASSET_TYPES = [
  'component',
  'directive',
  'filter'
];
複製代碼

這是 ASSET_TYPES 全部的數組項;

Vue.options._base = Vue;

複製代碼

這用於標識「基礎」構造函數以擴展全部普通對象;

extend(Vue.options.components, builtInComponents);
複製代碼

extend 在第一章已經講過了,在這裏:Vue 源碼解析(實例化前) - 初始化全局API(一)


使用插件 initUse

initUse(Vue);
複製代碼

這個方法,其實實現的就是一個 use 的方法,用來添加插件的:

function initUse(Vue) {
  Vue.use = function (plugin) {
    var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }
    var args = toArray(arguments, 1);
    args.unshift(this);
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args);
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args);
    }
    installedPlugins.push(plugin);
    return this
  };
}
複製代碼

在這裏,就是給 Vue 構造函數,在實例化前綁定哪些插件,全部的插件,都在 installedPlugins 裏面。


初始化合並 initMixin$1

function initMixin$1(Vue) {
    Vue.mixin = function (mixin) {
      this.options = mergeOptions(this.options, mixin);
      return this
    };
}
複製代碼

mergeOptions 方法的實如今這裏:Vue 源碼解析(實例化前) - 初始化全局API(一)


初始化繼承 initExtend

Vue.extend = function (extendOptions) {
    extendOptions = extendOptions || {};
    var Super = this;
    var SuperId = Super.cid;
    var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    var name = extendOptions.name || Super.options.name;
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name);
    }

    var Sub = function VueComponent (options) {
      this._init(options);
    };
    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;
    Sub.cid = cid++;
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    );
    Sub['super'] = Super;

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      initProps$1(Sub);
    }
    if (Sub.options.computed) {
      initComputed$1(Sub);
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend;
    Sub.mixin = Super.mixin;
    Sub.use = Super.use;

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type];
    });
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub;
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options;
    Sub.extendOptions = extendOptions;
    Sub.sealedOptions = extend({}, Sub.options);

    // cache constructor
    cachedCtors[SuperId] = Sub;
    return Sub
  };
}
複製代碼

這裏,就是 extend 的方法的實現,其實最主要就是輸出了一個函數,繼承了 Vue 構造函數的全部屬性和方法

extendOptions = extendOptions || {};
var Super = this;
var SuperId = Super.cid;
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
if (cachedCtors[SuperId]) {
  return cachedCtors[SuperId]
}
複製代碼

檢查 extendOptions 是否存在,若是存在 extendOptions 上是否存在 _Ctor 而且存在 SuperId ,若是存在直接就返回 cachedCtors[SuperId];

var name = extendOptions.name || Super.options.name;
if (process.env.NODE_ENV !== 'production' && name) {
  validateComponentName(name);
}
複製代碼

這裏檢查的就是 name 的格式,是不是 xxx-xxx 這種格式的,在第一章裏面有詳細解釋;

if (Sub.options.props) {
  initProps$1(Sub);
}
function initProps$1 (Comp) {
  var props = Comp.options.props;
  for (var key in props) {
    proxy(Comp.prototype, "_props", key);
  }
}
複製代碼

這裏作了初始化屬性的遍歷,給全部屬性都綁定了 Object.defineProperty;

var sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
};

function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}
複製代碼

這是 proxy 函數的實現過程。

if (Sub.options.computed) {
  initComputed$1(Sub);
}
function initComputed$1 (Comp) {
  var computed = Comp.options.computed;
  for (var key in computed) {
    defineComputed(Comp.prototype, key, computed[key]);
  }
}
function defineComputed ( target, key, userDef ) {
  var shouldCache = !isServerRendering();
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef;
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop;
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop;
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        ("Computed property \"" + key + "\" was assigned to but it has no setter."),
        this
      );
    };
  }
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

複製代碼

這段代碼是對 computed 作的處理,也是綁定了 Object.defineProperty,這種相似的代碼處理條件,在前兩章已經講了不少了,你們這裏看一下其實就差很少了,若是不明白,就去看看前兩章把。

Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;

ASSET_TYPES.forEach(function (type) {
  Sub[type] = Super[type];
});

if (name) {
  Sub.options.components[name] = Sub;
}

Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({}, Sub.options);

// cache constructor
cachedCtors[SuperId] = Sub;
return Sub
複製代碼

這裏其實就是讓繼承 Vue 的子級方法,能夠作 Vue 能夠作的事情。


初始化資源註冊 initAssetRegisters

function initAssetRegisters(Vue) {
  ASSET_TYPES.forEach(function (type) {
    Vue[type] = function ( id, definition ) {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        if (type === 'component') {
          validateComponentName(id);
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id;
          definition = this.options._base.extend(definition);
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition };
        }
        this.options[type + 's'][id] = definition;
        return definition
      }
    };
  });
}
複製代碼

這個方法,就是把 ASSET_TYPES 全部的數組項看成 Vue 的方法名來定義,這裏就只有註冊,沒有作太多的事情,最後返回處理後的 definition。

結束語

到這裏,在實例化 Vue 前要作的事情,其實已經講解了70%左右了,下一章,我會把剩下全部初始化對 Vue 構造函數作處理的地方作逐一講解(我是看了一萬多行源碼找的)。

哪裏不對,還但願你們多多指點,🙏

相關文章
相關標籤/搜索