vue源碼解析之插件入侵機制

先說一點概念性的東西哈~大笑

插件:聰明的程序員往往希望能更高(tou)效(lan)的完成指定的工作,插件就是按照一定的封裝方式,暴露接口。讓我們利用這些接口更快捷的實現功能。升職加薪。每個框架都提供了插件的擴展機制。這是框架可擴展性必不可少的一個部分。插件機制越簡單。對於框架的生態的發展大有好處。jquery提供了$.fn.extend,angular有對應的依賴注入,module機制。既然vue那麼精美,能迅速火起來。插件這部分的可擴展性必須頂級。這裏接下來我們看看vue插件的入侵機制。

說到插件。我們最多使用的一個方法。無非就是 Vue.use(MyPlugin, { someOption: true });

這麼說的話,這個方法應該是所有插件入侵vue的起點。沒錯。

那麼我們來看看這個方法:

Vue.use = function (plugin) {
    /* istanbul ignore if */
    if (plugin.installed) {
      return  //假如插件已經初始化過就不再繼續。避免插件重複入侵
    }
    // additional parameters
    var args = toArray(arguments, 1);//獲取插件的配置參數
    args.unshift(this);
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args); //調用的是插件的install方法;
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args);//若插件本省就是一個函數。則直接調用該函數
    }
    plugin.installed = true;
    return this
  };

Vue.use這個方法讓我們知道 插件入侵的起點是調用插件自身的install函數。那麼不同的插件入侵的機制有些時候很不一樣。我們可以知道。這個不一樣肯定發生在install函數中。我們來看看官方的install函數中的一些方式:

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或屬性
  Vue.myGlobalMethod = function () {
    // 邏輯...
  }
  // 2. 添加全局資源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 邏輯...
    }
    ...
  })
  // 3. 注入組件
  Vue.mixin({
    created: function () {
      // 邏輯...
    }
    ...
  })
  // 4. 添加實例方法
  Vue.prototype.$myMethod = function (options) {
    // 邏輯...
  }
}
我們按官網推薦的四種例子。來看看每種方法對應的源碼:

1:Vue.myGlobalMethod = function () {
    // 邏輯...
  }
類似jquery中的jquery.myGlobalMethod或則$.myGlobalMethod簡單來說就是給Vue這個全局對象添加一些工具方法。可以供全局快捷調用。我們這裏就略過了。

2: // 2. 添加全局資源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 邏輯...
    }
    ...
  })
Vue.directive,Vue.filter,Vue.component等價。當全局使用這些api時。會在vue上把這些指令過濾器組件等放在相應的屬性數組裏。形如:

Vue.options = {
    components: {
      
    },
    directives: {},
    filters: {},
    _base: Vue
}

因爲他掛在全局的vue中。在vue初始化。調用init方法時。會執行:
 vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),//策略合併核心函數。可以仔細去看看
        options || {},
        vm
      );

vue在創建實例時。會把vue對象上的options的對象中的屬性提取出來和傳入的options做合併。這裏涉及到合併策略。以後會專門講一下。這裏只要知道。vue每個配置相都有自己的合併規則。mergeOptions會根據合併的類目去選擇對應的合併規則。這裏的component.directive.filter根據合併規則。Vue對象上的全局的這些屬性會被放在實例的__proto__上。
同樣的。相應的子組件。可以回過頭去看一下組件那一章。在render創建子組件的時候。代碼如下:

function createComponent (
  Ctor,
  data,
  context,
  children,
  tag
) {
  if (isUndef(Ctor)) {
    return
  }

  var baseCtor = context.$options._base;

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor);
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    {
      warn(("Invalid Component definition: " + (String(Ctor))), context);
    }
    return
  }

  // async component
  if (isUndef(Ctor.cid)) {
    Ctor = resolveAsyncComponent(Ctor, baseCtor, context);
    if (Ctor === undefined) {
      // return nothing if this is indeed an async component
      // wait for the callback to trigger parent update.
      return
    }
  }

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor);//核心:這裏會再次合併一下vue上的全局的一些指令或則組件或則過濾器到組件的構造函數上

  data = data || {};

  // transform component v-model data into props & events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data);
  }

  // extract props
  var propsData = extractPropsFromVNodeData(data, Ctor, tag);

  // functional component
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  var listeners = data.on;
  // replace with listeners with .native modifier
  data.on = data.nativeOn;

  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners
    data = {};
  }

  // merge component management hooks onto the placeholder node
  mergeHooks(data);

  // return a placeholder vnode
  var name = Ctor.options.name || tag;
  var vnode = new VNode(
    ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
    data, undefined, undefined, undefined, context,
    { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }
  );
  return vnode
}

最後在內部組件初始化時。vue會調用:

function initInternalComponent (vm, options) {
  var opts = vm.$options = Object.create(vm.constructor.options);//這裏還是把構造函數的options放在了$options上供後續使用
  // doing this because it's faster than dynamic enumeration.
  opts.parent = options.parent;
  opts.propsData = options.propsData;
  opts._parentVnode = options._parentVnode;
  opts._parentListeners = options._parentListeners;
  opts._renderChildren = options._renderChildren;
  opts._componentTag = options._componentTag;
  opts._parentElm = options._parentElm;
  opts._refElm = options._refElm;
  if (options.render) {
    opts.render = options.render;
    opts.staticRenderFns = options.staticRenderFns;
  }
}

總的來說。如果是全局的指令過濾器時。vue統一把他放在根構造方法上。根實例初始化時。通過策略合併合併到$options中。而子組件稍微繞了一下。最終也是放在$options的原型上。很連貫啊。這樣只要是全局的組件。指令過濾器。每個子組件都可以繼承使用。達到了插件的效果。


3、下面來看看mixin方法:

Vue.mixin({
    created: function () {
      // 邏輯...
    }
    ...
  })


Vue.mixin = function (mixin) {
    this.options = mergeOptions(this.options, mixin);
  };


//這裏還是不可避免要看看mergeOptions函數:
function mergeOptions (
  parent,
  child,
  vm
) {
  {
    checkComponents(child);
  }

  if (typeof child === 'function') {
    child = child.options;
  }

  normalizeProps(child);
  normalizeDirectives(child);
  var extendsFrom = child.extends;
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm);
  }
  if (child.mixins) {
    for (var i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm);
    }
  }
  var options = {};
  var key;
  for (key in parent) {
    mergeField(key);
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key);
    }
  }
  function mergeField (key) {
    var strat = strats[key] || defaultStrat;
    options[key] = strat(parent[key], child[key], vm, key);
  }
  return options
}

分兩種情況:
a:全局註冊時。

即vue.mixin時。直接調用合併。直接便利mixin中的項目。分別調用相應合併策略。合併到構造函數的options中。影響後面所有的子組件

b:局部註冊時。

if (child.mixins) {
    for (var i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm);
    }
  }

則會去遞歸的調用合併策略把該合併的項目合併結束爲止;
vue.mixin就相當於是一個傳入的額外的配置項目,會讓vue重新按照規則合併一次,成功入侵vue。

4、添加實例方法

這個方法就很明顯了。在vue的原型上掛載方法。vue的實例自然而然就能繼承。子組件在創建的時候。

//  添加實例方法
  Vue.prototype.$myMethod = function (options) {
    // 邏輯...
  }

還有種就是將原型指向根構造函數Vue的prototype;自然而然就會有Vue的原型上的所有屬性和方法。

Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;

以上就是vue比較常用的插件侵入方法.....


好好學習 好好工作 天天向上  lalala~ 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章