【Vue源码探究】Vue.use的原理和设计

131 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情

前言

这段时间打算回顾一下Vue的全局方法,脑海里第一个跳出来的方法就是Vue.use,之所以会首先想到它,我觉得和我平时看的面试题相关~~~

Vue.use的原理是面试中常问的点,因为相对于其他全局方法,Vue.use源代码逻辑清晰,如果了解它,也就代表这个人是看过Vue源码的!!!

基本使用

在Vue官网中是这样说明的:通过全局方法 Vue.use(plugin) 使用插件

首先要知道什么是插件,插件通常用来为 Vue 添加全局功能(过滤器、指令、组件),平时我们使用的UI组件AxiosVuex都是插件

Vue.use装载插件的方式也很简单

  import Vue from "vue"
  import Element from 'element-ui'
  Vue.use(Element)

并且在Vue官网还介绍了我们如何去开发插件,只需要暴露一个install方法就可以,在如果Vue.use方法使用插件时,会主动调用install方法

install方法第一个参数是Vue构造函数,剩下的参数则是调用Vue.use方法时,除第一个参数以外的剩余参数

  import Vue from "vue"
  
  let MyPlugin = {
      install(_Vue) {
          console.log(_Vue === Vue)       // true
      }
  }
  Vue.install(MyPlugin)

源码解析

废话不说,直接上源码~~~

  import type { GlobalAPI } from 'types/global-api'
  import { toArray, isFunction } from '../util/index'
  
  export function initUse(Vue: GlobalAPI) {
      // plugin:类型是 Function|any
    Vue.use = function (plugin: Function | any) { 
      // this是Vue构造函数
      // _installedPlugins保存了已经被过使用插件
      const installedPlugins = this._installedPlugins || (this._installedPlugins = []) 
      
      // 判断当前插件是否已经被使用过
      if (installedPlugins.indexOf(plugin) > -1) {
        // 如果已经被使用,就直接返回Vue构造函数
        return this
      }
  
      // 获取排除第一个参数之后所剩余的参数
      const args = toArray(arguments, 1)
      // 向头部添加Vue构造函数
      args.unshift(this)
      // 如果 plugin.install 是方法
      if (isFunction(plugin.install)) {
        // 执行plugin.install方法
        plugin.install.apply(plugin, args)
      } 
      // 如果 plugin 是函数
      else if (isFunction(plugin)) {
        // 执行plugin.install函数
        plugin.apply(null, args)
      }
      
      // plugin已经被使用过了,添加到已使用缓存中
      installedPlugins.push(plugin)
      return this
    }
  }
  

源码不一定都很很难,像Vue.use源码是不是就很简单

我们从源码中得到以下信息:

  • Vue.use会防止重复加载同一个插件
  • Vue.use可以链式调用
  • plugin只有类型是函数或者对象的时候才有用,为函数时,直接运行这个函数;当为对象时,会判断对象中是否存在install方法,如果存在,就执行这个方法

控制反转

例子说话:现在你手中有一份导弹设计图,震惊中外,并且各大军工厂对这份导弹设计图都提出了不同类型的具有建设性的方案

你该怎么处理喃?是把这份图纸分别寄个各大军工厂去优化,让每个工厂自己优化吗?

 class Missile { }
 ​
 // A工厂:添加gps定位系统
 Missile.prototype.gps = "gps"
 Missile.prototype.getWz = () => { }
 ​
 // B工厂:给图纸添加颜色
 Missile.color = "red"

如果是这样的话,再有一个C工厂,还要再寄给C工厂去完善~~~

这样的缺点很明显:

  • 没有做工厂方案排重,图纸可能被通同一个工厂的同一个方案修改多次(因为你并没有记录那个工厂在图纸上做了修改)
  • 后期如果有其他工厂,你还要在把图纸寄出去,不能在自己的实验室解决

为了解决这些问题,张三提了一个想法,就是说让每个工厂都提交自己的修改方案,然后你自己在实验室修改~~~

 class Missile {
     static use(scheme) {
         // 方案排重
         
         // 执行修改方案
         scheme.run(Missile)
     }
 }
 ​
 // A工厂:添加gps定位系统
 let A_scheme = {
     run(_Missile) {
         _Missile.prototype.gps = "gps"
         _Missile.prototype.getWz = () => { }
     }
 }
 Missile.use(A_scheme)
 ​
 // B工厂:给图纸添加颜色
 let B_scheme = {
     run(Missile) {
         Missile.color = "red"
     }
 }
 Missile.use(B_scheme)

这样是不是两个问题都解决了,你只需要接受意见接口,剩下的逻辑就可以自己处理了

其实图纸的修改的权利从工厂到你的转变,就叫做控制反转

控制反转的目的就是,让开发者中尽量少触碰逻辑,只需要按照第三方制定的规则去传输数据可以,第三方自己去完成业务对接

回到Vue.use的设计,是不是发现有异曲同工之妙喃?

对于不同的Vue插件,Vue规定他们必须暴露一个install方法,提供Vue.use去接受插件,然后在Vue内部去执行插件的挂载

原本需要开发者去实现的业务,交给了Vue本身,开发者只需要提供插件,这种设计叫做控制反转