前言
最近阅读Vue源码,从打包的入口文件开始阅读,学到一些API的内部源码,这里分享一下自己的理解。
Vue.use
参数
{Object | Function} plugin
用法
用于安装Vue
插件,例如vue-router
、vuex
。
import Router from 'vue-router'
Vue.use(Router)
如果插件是一个对象,必须提供 install
方法。如果插件是一个函数,它会被作为 install
方法。install
方法调用时,会将 Vue
作为参数传入。
该方法需要在调用 new Vue()
之前被调用。当 install
方法被同一个插件多次调用,插件将只会被安装一次。
源码
src\core\global-api\use.js
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
// 如果Vue._installedPlugins为空,则初始化Vue._installedPlugins用于存放所有安装的插件
// 声明installedPlugins指向Vue._installedPlugins
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 如果插件已被安装,则返回vue实例
if (installedPlugins.indexOf(plugin) > -1) {
// 返回实例的目的是实现链式调用,例如Vue.use(Plugin1).filter('filter2',Filter2).component('component2',Component2)
return this
}
// use可以以"Vue.use(plugin,arg1,arg2)"传入参数,arg1、arg2会以形参形式传入到plugin.install方法或plugin方法
// 此处的toArray把数组中的第一个元素(plugin)去除
const args = toArray(arguments, 1)
// 把this(Vue)插入到第一个元素的位置
// 因为install方法默认第一个传入参数是Vue
args.unshift(this)
// 根据传进来的是object(必须有名为install的函数)还是function分开执行
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
流程总结:
Vue.filter,Vue.component,Vue.directive
因为Vue.filter
和Vue.component
以及Vue.directive
都是在源码中都是由同一个函数生成的。而且其传入参数和用法都一致。所以这里放在一起说。
参数
{string} id
{Function | Object} [definition]
用法
注册或获取全局组件。注册还会自动使用给定的 id
设置组件的名称
Vue.component( id, [definition] )
// 注册组件,传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({ /* ... */ }))
// 注册组件,传入一个选项对象 (自动调用 Vue.extend)
Vue.component('my-component', { /* ... */ })
// 获取注册的组件 (始终返回构造器)
var MyComponent = Vue.component('my-component')
Vue.filter( id, [definition] )
// 注册
Vue.filter('my-filter', function (value) {
// 返回处理后的值
})
// getter,返回已注册的过滤器
var myFilter = Vue.filter('my-filter')
Vue.directive( id, [definition] )
// 注册
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')
源码
src\core\global-api\assets.js
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* 创建静态变量注册方法
*/
// ASSET_TYPES:['component', 'directive', 'filter']
// 通过遍历给Vue添加Vue.component,Vue.directive,Vue.filter函数
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
// 无第二参数则代表要返回已注册的变量
if (!definition) {
return this.options[type + 's'][id]
} else {
if (process.env.NODE_ENV !== 'production' && type === 'component') {
// 检查组件name的格式
validateComponentName(id)
}
// isPlainObject用于判断definition是否为原始的object对象
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
// this.options._base即Vue构造函数,
// 此处调用Vue.extend把组件转换为对应的构造函数
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
// 如果注册'directive'时传入的是函数,则该指令在bind和update周期都调用该函数
definition = { bind: definition, update: definition }
}
// 传进来的'component', 'directive', 'filter'都分别放入
// 实例的options的'components', 'directives', 'filters'中
this.options[type + 's'][id] = definition
return definition
}
}
})
}
流程总结:
Vue.mixin
参数
{Object} mixin
用法
全局注册一个混入,影响注册之后所有创建的每个 Vue
实例。插件作者可以使用混入,向组件注入自定义的行为。不推荐在应用代码中使用。
源码
src\core\global-api\mixin.js
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
// 把两个option对象合并成一个,深度合并
this.options = mergeOptions(this.options, mixin)
// 返回实例的目的是实现链式调用
return this
}
}
Vue.extend
参数
{Object} options
用法
使用基础 Vue
构造器,创建一个对应传入组件的构造函数。传入参数是一个包含组件选项的对象。
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#app')
源码
src\core\global-api\extend.js
export function initExtend (Vue: GlobalAPI) {
/**
* 每个生成的构造函数(包括Vue)都有一个惟一的cid。
* 该cid用于作为键缓存生成的构造函数
*/
// 声明Vue.cid且赋值为0
Vue.cid = 0
// 声明cid变量,作为extend生成的构造函数的cid
let cid = 1
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
// Super的值取决于xx.extend中的xx
// 注意:用Vue.extend生成的类也可以调用extend
const Super = this
const SuperId = Super.cid
// 给传入的组件添加_Ctor,用于缓存不同的Super通过Super.extend生成的类
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
// 通过SuperId即Super中的cid缓存
// 当重复调用相同Super的extend方法时,可以直接从_Ctor中取出结果
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
// 检测组件的name是否合符规范
validateComponentName(name)
}
// 创建Sub构造函数
const Sub = function VueComponent (options) {
// 调用Vue.prototype._init初始化
this._init(options)
}
// Sub构造函数继承Super,此处的继承方式使用寄生式组合继承[继承原型、构造函数、静态变量]
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
if (Sub.options.props) {
// 此方法留到下面分析
// 主要用于把extendOptions的props通过Object.defineProperty到实例上
initProps(Sub)
}
if (Sub.options.computed) {
// 没看懂,反正就是处理computed吧,之后补上
initComputed(Sub)
}
// 把Super的'extend', 'mixin', 'use'属性复制到Sub
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// ASSET_TYPES:['component', 'directive', 'filter']
// 把Super的'component', 'directive', 'filter'属性复制到Sub
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub
}
// 在扩展时间保持对Super.options的引用。稍后在实例化时,我们可以检查Super.options是否被更新。
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// 以Super.cid为键存储Sub
cachedCtors[SuperId] = Sub
return Sub
}
}
initProps
源码中,在initProps
函数上有以下注释:
对于props和计算属性,我们在扩展时在扩展原型的Vue实例上定义代理getter。这样可以避免对创建的每个实例调用Object.defineProperty。
划出重点:避免对创建的每个实例调用Object.defineProperty
。我们带着这个观点分析一下源码:
// src\core\global-api\extend.js
function initProps (Comp) {
const props = Comp.options.props
// 把Sub.options.props中的每一个键都传入proxy方法中
for (const key in props) {
proxy(Comp.prototype, `_props`, key)
}
}
// src\core\instance\state.js
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
// 注意:在 get 和 set 方法中,this 指向被访问和修改属性的实例对象。
// 下面把sourceKey形参的值默认为"_props"分析
// 当读取实例的其中一个与对应组件props中同名的值,相当于读取this._props中的同名属性
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
// 在Sub实例化时,props会被设定值,从而会触发set方法,
// 继而给实例中名为_props的对象添加props中的键名一致的属性
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
上面代码中,在实例里声明一个_props
的私有对象,用于给实例调用props中的值时返回。
通过上述proxy
方法让Object.defineProperty
逻辑在Vue.extend
函数执行时就执行,利用set
和get
方法中this
指向实例从而达到构造函数实例化时,赋值到props
的值会被复制到this._props
中。从而避免了Object.defineProperty
在创建的每个实例中重复调用。
流程总结:
后记
之后还会继续更新Vue
的一些源码。