Vue源码探索(一):Vue.use() 具体做了什么?

758 阅读3分钟

我正在参加「掘金·启航计划」

前言

当我们使用 vuex、vue-router 等插件时,需要通过Vue.use('xxx')方法才起作用。在调用自定义的全局组件时,比如之前的文章还不会自定义一个全局 vue2 插件?,也需要使用Vue.use()。那么Vue.use()具体做了那些事情呢?让我们一探究竟。

语法

查看官方文档,可以看到参数支持对象或函数类型,且前者必须提供 install 方法。

Vue.use( plugin )
参数:
{Object | Function} plugin
用法:
安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。
该方法需要在调用 new Vue() 之前被调用。
当 install 方法被同一个插件多次调用,插件将只会被安装一次。

下一步,我们深入源码学习一下。

源码

本文使用的 vue 是最新版本 vue@2.7.14

Vue.use() 是在 initUse() 函数中声明的,该函数会在Vue初始化时传参 Vue 并调用。Vue.use() 的源码如下:

// src\core\global-api\use.tsimport type { GlobalAPI } from 'types/global-api'
import { toArray, isFunction } from '../util/index'
export function initUse(Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | any) {
    const installedPlugins =
      this._installedPlugins || (this._installedPlugins = [])
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }
    // additional parameters
    const args = toArray(arguments, 1)
    args.unshift(this)
    if (isFunction(plugin.install)) {
      plugin.install.apply(plugin, args)
    } else if (isFunction(plugin)) {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

本来以为Vue.use()中做了很多处理,其实并没有... 代码挺简练的,只有区区21行!!

首先,定义变量 installedPlugins 为已安装插件列表,如果多次注册插件,也就是已经包含了要引入的插件,则直接返回 this。

接着,判断 调用 Vue.use()时是否传入了参数:

// 这里是获取传入的参数,并插入this对象作为args的第一个元素
const args = toArray(arguments, 1)
args.unshift(this)

Vue.use()传入的参数可带参数也可以不带参数,比如:

const pluginStub = {
  install: (Vue, opts) => {}
}
Vue.use(pluginStub)
Vue.use(pluginStub, {})
Vue.use(pluginStub, 'hello', 'world')

再者,判断传参是对象或者数组:

  • 若是对象(带有 install 方法),则执行plugin.install.apply(plugin, args),此处指定了install方法的 this 指向了plugin。
  • 若是函数,则执行该函数:plugin.apply(null, args)
if (isFunction(plugin.install)) {
  plugin.install.apply(plugin, args)
} else if (isFunction(plugin)) {
  plugin.apply(null, args)
}
installedPlugins.push(plugin)

最后,返回 this,使得 Vue.use()支持链式调用

Vue.use(VueRouter).use(Vuex)

接下来,继续了解代码中的 isFunction 和 toArray 函数的用法。

isFunction()

顾名思义,该函数是判断 value 是否是一个函数。

export function isFunction(value: any): value is (...args: any[]) => any {
  return typeof value === 'function'
}

更常用的判断方法是:

Object.prototype.toString.call(Math.max) === '[object Function]'
​
// 此外,还有其他的很多方法:
// Object.prototype.toString.call(null) // '[object Null]'
// Object.prototype.toString.call({}) // '[object Object]'
// ...

toArray()

顾名思义,这里是将一个类数组对象转化为一个数组,支持从索引 start 开始获取数组元素。

/**
 * Convert an Array-like object to a real Array.
 */
export function toArray(list: any, start?: number): Array<any> {
  start = start || 0
  let i = list.length - start
  const ret: Array<any> = new Array(i)
  while (i--) {
    ret[i] = list[i + start]
  }
  return ret
}

什么是类数组对象呢?类数组对象是指含有 length 属性的对象,通常来说还会有0 ~ length-1的属性,结构上非常像一个数组。常见的类数组有 arguments 对象 和 DOM方法的返回结果,如document.getElementsByTagName()

对于arguments对象,我们有很多方法可以将其转换为一个真正的Array

var args1 = Array.prototype.slice.call(arguments);
var args2 = [].slice.call(arguments);
const args3 = Array.from(arguments);
const args4 = [...arguments];

toArray()方法中,返回值的长度是 list.length - start,返回值中每个元素是 list 中对应的元素。该方法是用于获取Vue.use()传入的参数,并插入 this 对象作为args的第一个元素。

后记

在本文中,我们深入了解了Vue.use()相关的源码,并学习了如何判断参数的类型、类数组对象 arguments、apply方法等等。在Vue项目中,Vue.use()在注册插件时都会用到,其代码很简单,只有 21 行,但值得一看并仔细品味!