Vue2源码阅读——global-api

303 阅读7分钟

全局API,文件定位: vue/src/core/global-api

assets.js

定义全局 component, directive, filter(在Vue3已被弃用,尽量减少使用)

  1. Vue.component(id, Function | Object): 注册或获取全局组件,注册还会自动使用给定的 id 设置组件的名称
  2. Vue.directive(id, Function | Object): 注册或获取全局指令
  3. Vue.filter(id, Function | Object): 注册或获取全局过滤器
export const ASSET_TYPES = [ // 资源类型
  'component',
  'directive',
  'filter'
]
// 验证组件名是否合法
export function validateComponentName (name: string) {
  if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
    warn(
      'Invalid component name: "' + name + '". Component names ' +
      'should conform to valid custom element name in html5 specification.'
    )
  }
  if (isBuiltInTag(name) || config.isReservedTag(name)) {
    warn(
      'Do not use built-in or reserved HTML elements as component ' +
      'id: ' + name
    )
  }
}

import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && 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
      }
    }
  })
}
  1. 如果 type 是 组件component, 并且 definition 是一个对象的时候, 会自动调用 Vue.extend
if (type === 'component' && isPlainObject(definition)) {
  definition.name = definition.name || id
  definition = this.options._base.extend(definition)
}

所以在定义组件的时候,以下几种形式都是可以的:

// 函数
Vue.component('CustomComponent', function () {
  return new Promise(resolve => {
    resolve({
      data () {
        return {
          name: 'CustomComponent'
        }
      },
      template: '<div>{{name}}</div>'
    })
  })
})
// 对象 会自动调用  Vue.extend
Vue.component('CustomComponent', {
  data () {
    return {
      name: 'CustomComponent'
    }
  },
  template: '<div>{{name}}</div>'
})
// 或者 Vue.extend
Vue.component('CustomComponent', Vue.extend({
  data () {
    return {
      name: 'CustomComponent From Vue.extend'
    }
  },
  template: '<div>{{name}}</div>'
}))
    
  1. 如果 type 是 指令directive, 并且 definition 是一个函数的时候, 返回 { bind: definition, update: definition } 也就是, 在 bindupdate的时候 都会触发该函数
if (type === 'directive' && typeof definition === 'function') {
  definition = { bind: definition, update: definition }
}
  1. 其余的都采用 this.options[type + 's'][id] = definition

extend.js

使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

注意: data 必须是一个函数

  1. JS的继承,参考 知识拓展

源码


import { ASSET_TYPES } from 'shared/constants'
import { defineComputed, proxy } from '../instance/state'
import { extend, mergeOptions, validateComponentName } from '../util/index'

export function initExtend (Vue: GlobalAPI) {
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  Vue.cid = 0
  let cid = 1

  /**
   * Class inheritance
   */
  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]
    }

    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

    // 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(Sub)
    }
    if (Sub.options.computed) {
      initComputed(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
  }
}

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])
  }
}

逐行解析

  1. 基本结构
Vue.extend = function (extendOptions) {}
  1. extendOptions 如果不传值, 则默认为: {}
extendOptions = extendOptions || {}
  1. 获取 父类 以及 父类cid
const Super = this
const SuperId = Super.cid 
  1. 缓存策略, 如果 传入的extendOptions 已经被 构造过了, 那么直接返回
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
  return cachedCtors[SuperId]
}

例如:

const ctor = {
  name: 'Child'
}
const Child = Vue.extend(ctor)
// 因为 ctor之前被构造过, 所以 直接返回 之前构造的, 不需要再构造
const Sub = Vue.extend(ctor) 
console.log(Child === Sub) // true
  1. 验证名称的有效性
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
  validateComponentName(name)
}
  1. JS的寄生组合继承
const Sub = function VueComponent (options) {
  this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
  1. 增加cid, 合并 父类的 options, 新增 子类的 super属性, 指向父类
 Sub.cid = cid++;
 Sub.options = mergeOptions(
  Super.options,
  extendOptions
);
Sub['super'] = Super;
  1. 初始化 propscomputed
if (Sub.options.props) {
  initProps$1(Sub);
}
if (Sub.options.computed) {
  initComputed$1(Sub);
}
  1. 拷贝父类的 extend, mixin, use, component, filter, directive
Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;
ASSET_TYPES.forEach(function (type) {
  Sub[type] = Super[type];
});
  1. 启用递归自查找
if (name) {
  Sub.options.components[name] = Sub;
}
  1. 新增额外属性 superOptions, extendOptions, sealedOptions
Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({}, Sub.options);
  1. 缓存 构造函数
cachedCtors[SuperId] = Sub;

mixin.js

Vue.mixin() 源码如下, 还是很容易理解的, 就是将 传入的 mixin 对象 和 this.options进行合并

import { mergeOptions } from '../util/index'

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

use.js

Vue.use 主要用于 注册插件

import { toArray } from '../util/index'

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    const 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
  }
}
  1. 判断 插件是否已经注册过
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
  return this
}
  1. 获取额外参数,并且转为数组, 并且将 当前的 this 追加到数组的 头部, 如果你调用Vue.use 那么 this 就是 Vue
const args = toArray(arguments, 1)
args.unshift(this)
  1. 如果 传入的 plugin 含有 install 并且 install是一个函数, 调用 install函数, 并且将 plugin 作为 上下文
plugin.install.apply(plugin, args)

例如:

Vue.use({
  config: {
    name: '请叫我张先森'
  },
  install (vm, ...params) {
    console.log(this)
  }
})

image.png 4. 否则 plugin 是函数, 直接调用 该函数

plugin.apply(null, args)

例如:

Vue.use(function (vm, ...params) {
  console.log(vm, params)
})
  1. 缓存策略
installedPlugins.push(plugin)

index.js

import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import { observe } from 'core/observer/index'

import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

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

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

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

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}
  1. 引入 所需文件
import config from '../config' // 配置文件

// 就上面讲到的 Vue.use, Vue.mixin, Vue.extend
// Vue.component, Vue.directive, Vue.filter
import { initUse } from './use'
import { initMixin } from './mixin' 
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'

import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import { observe } from 'core/observer/index'

import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'
  1. 设置config只能获取, 不能设置
const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
Object.defineProperty(Vue, 'config', configDef)

关于 Object.defineProperty可以 阅读我之前写的文章, Vue源码阅读——Shared

  1. 暴露一些工具函数
Vue.util = {
  warn,
  extend,
  mergeOptions,
  defineReactive
}
  1. setdelete, nextTickobservable 添加到全局, 并且设置 Vue.options为空对象, 并且增加 components, directives, filters属性, 以及 _base = Vue;

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

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

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

  // this is used to identify the "base" constructor
  // to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue
  1. 将内置组件keep-alive 增加到 Vue.options.components
extend(Vue.options.components, builtInComponents)

关于keep-alive的源码解析可以阅读 Vue2源码阅读——keep-alive

  1. 添加 use, mixin, extend, component, filter, directive
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)

知识拓展

js继承

假设我们有一个 父类ParentClass, 构造函数如下:

function ParentClass() {
  this.names = ['Demo1', 'Demo2', 'Demo3']
}
ParentClass.prototype.say = function () {
  console.log(`我的名字叫: ${this.names}`)
}

原型链继承

function ChildClass () {}
ChildClass.prototype = new ParentClass()
ChildClass.prototype.constructor = ChildClass

缺点

  1. 将父类初始化之后, 引用类型的属性被所有的实例共享
const aChild = new ChildClass()
const bChild = new ChildClass()
aChild.say() // 我的名字叫: Demo1,Demo2,Demo3
bChild.say() // 我的名字叫: Demo1,Demo2,Demo3
aChild.names.push('Demo4')
aChild.say() // 我的名字叫: Demo1,Demo2,Demo3,Demo4
bChild.say() // 我的名字叫: Demo1,Demo2,Demo3,Demo4
  1. 在创建 子类的时候, 不能向父类传参
  2. 无法实现多继承
  3. 要想 ChildClass.prototype 上新增 属性或者方法, 需要在 ChildClass.prototype = new ParentClass() 之后

借用构造函数

function ChildClass () {
  ParentClass.call(this)
  this.say = function () {
    console.log(`我的名字叫: ${this.names}`)
  }
}

优点

  1. 解决了 原型链继承中的,引用对象的共享
  2. 在创建 子类的时候, 可以向父类传参
  3. 可以实现多继承(call多个父类函数) 也就是解决了, 原型链继承的缺点

缺点

  1. 实例并不是 父类的实例,只是子类的实例
  2. 只能继承父类的实例的属性和方法, 不能继承原型属性和方法
  3. 不能实现实例中函数的复用,比如实例代码的say函数, 每一次创建新的实例,都会创建一次

组合式继承

原型链继承 + 经典继承 = 组合式继承

function ChildClass () {
  ParentClass.call(this)
}
ChildClass.prototype = new ParentClass()
ChildClass.prototype.constructor = ChildClass

优点

  1. 融合原型链继承和构造函数的优点

缺点

  1. 初始化的时候调用了一次父类, 原型上也初始化了一次父类,

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

function createObj (o) {
  var clone = Object.create(o);
  clone.sayName = function () {
    console.log('hi');
  }
  return clone;
}

缺点

  1. 跟借用构造函数模式一样,每次创建对象都会创建一遍方

寄生组合式继承

function ChildClass () {
  ParentClass.call(this)
}
ChildClass.prototype = Object.create(ParentClass.prototype)
ChildClass.constructor = ChildClass

ES6 继承

Vue2源码阅读系列