vue2源码解析之全局API实例详解

Vue
281
0
0
2023-06-09
目录
  • 前言
  • Vue.extend()
  • 基本使用
  • 整体源码
  • Vue.nextTick,Vue.set,Vue.delete
  • Vue.directive、Vue.filter、Vue.component
  • 基本使用
  • 源码分析
  • Vue.use
  • 基本使用
  • 源码预览
  • Vue.mixin
  • 基本使用
  • 源码预览
  • Vue.compile
  • 基本使用
  • 源码预览
  • Vue.observable
  • 基本用法
  • 源码预览
  • 总结

前言

实例方法是挂载在vue的原型上,而全局方法是挂载在vue上;全局方法有12个,分别为extend、nextTick、set、delete、directive、filter、component、use、mixin、version和observable;

Vue.extend()

基本使用

/*
作用:
使用基础vue构造器,创建一个子类,参数是一个包含组件选项的对象
`data` 选项是特例,需要注意 - 在 `Vue.extend()` 中它必须是函数
*/
Vue.extend(options)

// eg:
<div id="mount-point"></div>
// 创建构造器  
var Profile = Vue.extend({  
    template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',  
    data: function () {  
        return {  
            firstName: 'Walter',  
            lastName: 'White',  
            alias: 'Heisenberg'  
        }  
    }  
})  
// 创建 Profile 实例,并挂载到一个元素上。  
new Profile().$mount('#mount-point')

// 结果:
<p>Walter White aka Heisenberg</p>

整体源码

// 源码位置 src/core/global-api/extend.js
 Vue.extend = function (extendOptions: Object): Function {
    // 初始化参数
    extendOptions = extendOptions || {}
    // 存储父类
    const Super = this
    // 存储父类的唯一标识
    const SuperId = Super.cid
    // 获取到参数中的缓存数据
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    // 如果已经缓存过就会缓存中获取返回
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }
    // 获取到name
    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }
    // 创建一个子类
    const 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

    // 如果子类中有props 进行初始化
    if (Sub.options.props) {
      initProps(Sub)
    }
    // 如果子类中有计算属性,进行初始化
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // 把父类的extend,mixin,use放在子类上
    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
  }
}

// 把子类中的props存储到原型上
function initProps (Comp) {
  const props = Comp.options.props
  for (const key in props) {
    proxy(Comp.prototype, `_props`, key)
  }
}
// 把子类中的计算属性存储到原型上
function initComputed (Comp) {
  const computed = Comp.options.computed
  for (const key in computed) {
    defineComputed(Comp.prototype, key, computed[key])
  }
}

extend方法内部其实就是创建一个子类,通过原型继承的方式继承父类,然后把父类的属性和一些方法存储到子类上,并且把子类进行缓存,再一开始就先从缓存中获取子类,如果没有就进行属性和方法的继承,最后返回子类;

Vue.nextTick,Vue.set,Vue.delete

以上三种都在实例方法中解析过

Vue.directive、Vue.filter、Vue.component

基本使用

注册或获取全局指令

// 注册  
Vue.directive('my-directive', {  
    bind: function () {},  
    inserted: function () {},  
    update: function () {},  
    componentUpdated: function () {},  
    unbind: function () {}  
})  
  
// 注册 (指令函数)  
Vue.directive('my-directive', function () {  
// 这里将会被 `bind` 和 `update` 调用  
})  
  
// getter,返回已注册的指令  
var myDirective = Vue.directive('my-directive')

注册或获取过滤器

// 注册
Vue.filter('my-filter',function(){})
// 获取
Vue.filter('my-filter')

注册或获取组件

// 注册 传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({}))
// 注册 传入一个选项对象
Vue.component('my-component', {})
// 获取
Vue.component('my-component')

源码分析

// 源码位置 src/core/global-api/
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      // 不存在definition 表示获取,直接从options下的相应的typs+'s'进行获取
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        // 如果是组件 并且definition为一个{}对象,那么就通过extend继承vue
        if (type === 'component' && isPlainObject(definition)) {
          // 没有name就使用id
          definition.name = definition.name || id
          // 使definition继承vue
          definition = this.options._base.extend(definition)
        }
        // 如果是指令并且definition是一个函数,那么就把这个函数作为bind和update
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        // 设置值 保存到对应的key下
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

指令,过滤器,组件把它们放在一个数组中,遍历这个数组,分别进行处理

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]
 ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ){}
 })

如果没有传递第二个参数,表示是获取,直接进行获取返回

if (!definition) {
    return this.options[type + 's'][id]
}

否则就是传递了第二个参数,表示是注册,首先判断是否是组件,是组件并且第二个参数是对象,那么就继承Vue;

// 如果是组件
if (type === 'component' && isPlainObject(definition)) {
  // 没有name就使用id
  definition.name = definition.name || id
  // 使definition继承vue
  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

通过以上三种方法就可以自定义组件,过滤器和指令,然后就可以在模板中使用它们,最终的处理还是在模板编译节点进行的;

Vue.use

基本使用

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

源码预览

// 源码位置 src/core/global-api/use.js
export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    // 初始_installedPlugins变量
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // 如果已经存在直接返回
    if (installedPlugins.indexOf(plugin) > -) {
      return this
    }

    // 获取剩余的参数转成数组
    const args = toArray(arguments,)
    // 把this添加进去
    args.unshift(this)
    // 如果install是一个函数 直接执行
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') { // 如果插件是一个函数,直接执行
      plugin.apply(null, args)
    }
    // 添加到数组中
    installedPlugins.push(plugin)
    return this
  }
}

首先获取到用来缓存的属性,如果不存在初始化为一个数组,再判断传递进来的插件是否已经存在,存在表示重复操作,直接返回;(实现:当 install 方法被同一个插件多次调用,插件将只会被安装一次)

// 初始_installedPlugins变量
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 如果已经存在直接返回
if (installedPlugins.indexOf(plugin) > -) {
  return this
}

接着获取到剩余的参数,并且把当前实例也作为参数(实现:install 方法调用时,会将 Vue 作为参数传入。)

// 获取剩余的参数转成数组
const args = toArray(arguments,)
// 把this添加进去
args.unshift(this)

判断plugin.install是否是一个函数,如果是函数就通过plugin执行(实现:如果插件是一个对象,必须提供 install 方法)

// 如果install是一个函数 直接执行
if (typeof plugin.install === 'function') {
  plugin.install.apply(plugin, args)
}

否则plugin是一个函数,那么直接执行(实现:如果插件是一个函数,它会被作为 install 方法)

else if (typeof plugin === 'function') { // 如果插件是一个函数,直接执行
  plugin.apply(null, args)
}

最后添加到缓存中并返回

// 添加到数组中
installedPlugins.push(plugin)
return this

Vue.mixin

基本使用

/*
作用:
全局注册一个混入,影响注册之后所有创建的每个vue实例,插件作者可以使用混入,
向组件注入自定义的行为。**不推荐在应用代码中使用**。
*/
Vue.mixin()

源码预览

// 源码位置 src/core/global-api/mixin.js
export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

混入很简单,就是把传递进来的数据和实例上已有的数据进行合并替换,最后重新赋值给实例上的options;

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }
  // 设置props,把带有-的属性名改成驼峰,存到一个新的对象中进行返回
  normalizeProps(child, vm)
  // 设置注入的属性 把inject中的数据都转成 {from:val}形式的对象返回
  normalizeInject(child, vm)
  // 设置指令 返回指令的值为{ bind: def, update: def }的形式
  normalizeDirectives(child)

  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  // 递归处理extends和mixins
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i =, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }
  // 创建一个新的对象
  const options = {}
  let key
  for (key in parent) { // 遍历父级进行设置属性
    mergeField(key)
  }
  for (key in child) { // 遍历子级进行设置属性
    if (!hasOwn(parent, key)) { // 父级不存在此属性就进行设置
      mergeField(key)
    }
  }
  // 通过策略模式给不同的属性设置对应的执行函数,
  function mergeField (key) {
    // 获取到不同属性的函数
    const strat = strats[key] || defaultStrat
    // 执行对应的函数重新设置属性
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

把子级的属性进行单独处理,处理完成之后和父级的属性进行合并,合并之后进行返回;

Vue.compile

基本使用

/*
作用: 将一个模板字符串编译成 render 函数。**只在完整版时可用**。
template: 字符串模板
*/
Vue.compile(template)

源码预览

Vue.compile = compileToFunctions

compileToFunctions函数在模板编译节点已经解析完

Vue.observable

基本用法

/*
作用:
让一个对象可响应,Vue内部会用它来处理data函数返回的对象
返回的对象可以直接用于[渲染函数]和[计算属性]内,并且会在发生变更时触发相应的更新。
也可以作为最小化的跨组件状态存储器,用于简单的场景:
*/
const state = Vue.observable({ count: })  
const Demo = {  
    render(h) {  
        return h('button', {  
            on: { click: () => { state.count++ }}  
        }, `count is: ${state.count}`)  
    }  
}

源码预览

// 源码位置 src/core/global-api/
//.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
}

总结

extend: 内部实现一个子类,通过原型继承的方式继承自当前调用者(父类),并且把父类中的属性进行合并,存储到自己的options中,父类的方法添加到自己构造器上;把子类进行缓存;一进入到函数内部首先从缓存中获取,没有才进行创建子类;

directive,filter,component: 这三种方式通过一个函数实现,首先判断有没有传递第二个参数,如果没传递表示是获取,直接获取返回;如果有传递表示注册,如果是指令并且第二个参数是函数,那么把函数作为bind和update的值;如果是组件并且第二个参数是函数,那么通过extend方法进行继承;

use: 如果缓存中有此插件就直接返回,否则就判断传递进来的参数是对象还是函数,如果是对象那么就执行对象上的install方法,如果是函数直接执行;并且把当前实例作为参数传递进去;最后把插件进行缓存;

mixin: 首先把props,inject和指令的属性进行统一格式化操作;然后把当前实例的options和传递进来的参数进行合并,相同属性传递进来的参数会覆盖options中的属性;

observable: 通过调用observe给当前传递进来的对象添加响应式,并且返回该对象;