Vue源码学习- 全局API
Posted ~往无前
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue源码学习- 全局API相关的知识,希望对你有一定的参考价值。
目标
深入理解以下全局API的实现原理
- Vue.use
- Vue.mixin
- Vue.component
- Vue.filter
- Vue.directive
- Vue.extend
- Vue.set
- Vue.delete
- Vue.nextTick
Vue 的众多全局API的实现大部分都放在 /src/core/global-api 目录下。这些全局API源码阅读的入口则在/src/core/global-api/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'
import type GlobalAPI from 'types/global-api'
/**
* 初始化 Vue的众多全局API,比如:
* 默认配置:Vue.config
* 工具方法:Vue.util.xx
*/
export function initGlobalAPI(Vue: GlobalAPI)fine
// config
const configDef =
//Vue的众多默认配置项
configDef.get = ()=>config
if(__DEV__)
configDef.set = ()=>
warn('Do not replace the Vue.config object, set individual fiedlds instead')
//Vue.config
Object.defineProperty(Vue,'config',configDef)
/**
* 暴露一些工具方法,轻易不要使用这些工具方法,除非你很清楚这些工具方法,以及知道它的使用风险
*/
Vue.uil =
//警告日志
warn,
//类似选项合并
extend,
//合并选项
mergeOptions,
//设置响应式
defineReactive
//Vue.set / Vue.delete / Vue.nextTick
Vue.set = set
Vue.delete = delete
Vue.nextTick = nextTick
//响应式方法
Vue.observable = <T>(obj:T): T=>
observe(obj)
return obj
Vue.options = Object.create(null)
AEEET_TYPES.forEach(type =>
Vue.options[type + 's'] = Object.create(null)
)
//将Vue.构造函数挂载到Vue.options._base上
Vue.options._base = Vue
//在Vue.options.component 中添加内置组件,比如keep-alive
extend(Vue.options.components,builtInComponents)
//Vue.use
initUse(Vue)
//Vue.mixin
initMixin(Vue)
//Vue.extend
initExtend(Vue)
//Vue.component/directive/filter
initAssetRegisters(Vue)
Vue.use
/src/core/global-api/use.js
/**
* 定义Vue.use 负责为Vue安装插件,做了以下两件事
* 1.判断插件是否已经被安装,如果安装则直接结束
* 2.安装插件,执行插件的install方法
*
*/
export function initUse(vue: GlobalAPI)
Vue.use = function(plugin: Function | any)
//已经安装过的插件列表
const installedPlugins =(this._installedPlugins || (this._installedPlugins = [])
//判断plugin是否已经安装过,保证不重复安装
if(installedPlugins.indexOf(plugin)>-1)
return this
//将Vue构造函数放到第一个参数位置,然后将这些参数传递给install方法
const args = toArray(arguments,1)
args.unshift(this)
if(typeof plugin.install === 'function')
// plugin 是一个对象,则执行其install方法安装插件
plugin.install.apply(plugin,args)
else if(typeof plugin === 'function')
//执行直接plugin方法安装插件
plugin.apply(null,args)
// 在插件列表中添加新安装的插件
installedPlugins.push(plugin)
return this
Vue.mixin
/src/core/global-api/mixin.js
/**
* 定义Vue.mixin,负责全局混入选项,影响之后所有创建的Vue实例,这些实例会合并全局混入的选项
*/
export function initMixin(Vue: GlobalAPI)
Vue.mixin = function (mixin: Object)
this.options = megeOptions(this.options,mixin)
return this
mergeOptions
src/core/utils/options.js
/**
* 合并两个选项,出现相同配置时,子选项会覆盖父选项的配置
*/
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、inject、directive选项,方便后续程序的处理
normalizeProps(child,vm)
normalizeProps(child,vm)
normalizeDirectives(child)
//处理原始child对象上的extends 和 mixins,分别执行mergeOptions,将这些继承而来的选项合并到parent
if(!child._base)
if(child.extends)
parent = mergeOptions(parent, child.extends,vm)
if(child.mixins)
for(let i = 0,l=child.mixins.length;i<l;i++)
parent = mergeOptions(parent,child.mixins[i],vm)
const options: ComponentOptions = as any
let key
for(key in parent)
mergeField(key)
for(key in child)
if( !hasOwn(parent,key))
mergeField(key)
function mergeField(key:any)
const strat = strats[key] || defaultStrat
//在遍历parent的时候,就会将父子相同的key做处理,用的是子的值,所以在上面的child遍历中,遇到相同的key就不做处理了。
options[key] = strat(parent[key],child[key],vm,key)
return options
Vue.component、Vue.filter、Vue.directive
/src/core/global-api/assets.js
import ASSET_TYPES from 'shared/constants'
/**
* 定义Vue.component、Vue.filter、
*/
export function initAssetRegisters(Vue:GlobalAPI)
ASSET_TYPES.forEach(type =>
/**
* 比如:Vue.component(name,definition)
* @param * id nam
* @param * definition 组件构造函数或者配置对象
* @returns 返回组件构造函数
*/
Vue[type] = function(
id: string,
definition: Function | Object
): Function | Object | void
if(!definition)
// 如果只传id,则返回从options中对应id的值
return this.options[type + 's'][id]
else
if(type === 'component' && isPlainObject(definition))
//如果组件配置中存在name,则使用,否则直接使用id
definition.name = definition.name || id
// extend 就是 Vue.extend,所以这时的definition就变成了组件构造函数,使用时可直接new Definition()
definition = this.options._base.extend(definition)
if(type === 'directive' && typeof definition === 'function')
definition = bind: definition,update:definition
//this.options.components[id] = definition this.options.directives[id] = definition this.options.filters[id] = definition
//在实例化时通过mergeOptions 将全局注册的组件合并到每个组件的配置对象的components中
this.options[type + 's'][id] = definition
return definition
Vue.extend
/src/core/global-api/extend.js
Vue.cid = 0
let cid = 1
/**
* 基于Vue去扩展子类,该子类同样支持
*/
Vue.extend = function(extendOptions:Object):Function
extendOptions = extendOptions ||
const Super = this
const SuperId = Super.cid
/**
* 利用缓存,如果存在则直接返回缓存中的构造函数
* 什么情况下可以利用这个缓存?
* 如果你在多次调用Vue.extend 时使用了同一个配置项(extendOptions),这时就会启用该缓存
*/
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = )
if(cachedCtors[SuperId])
return cachedCtors[SuperId]
const name = cachedOptions.name || Super.options.name
if(process.env.NODE_ENV !== 'productio' && name)
validateComponentName(name)
//定义Sub构造函数和Vue构造函数一样
const Sub =function VueComponent(this:any,options:any)
//初始化
this._init(options)
//通过原型继承的方式继承Vue
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
//合并Vue的配置项到自己的配置项上来
Sub.options=mergeOptions(Super.options,extendOptions)
//记录自己的基类
Sub['super'] = Super
//初始化props,将props配置代理到Sub.prototype._props对象上
//在组件内通过this._props方式可以访问
if(Sub.options.props)
initProps(Sub)
// 定义extend、mixin、use这三个静态方法,允许在Sub基础上再进一步构造子类
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
//定义compoent、filter、directive三个静态方法
ASSET_TYPES.forEach(function(type)
Sub[type] = Super[type]
)
// 递归组件的原理,如果组件设置了name属性,则将自己注册到自己的compoents选项中
if(name)
Sub.options.components[name] = Sub
// 在扩展时保留对基类选项的引用
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend(,Sub.options)
//缓存cachedCtors[SupperId] = Sub
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]
Vue.set
/src/core/observer/index.js
/**
* 通过Vue.set 或者this.$set方法给target的指定key设置值val
* 如果target是对象,并且key原本不存在,则为新key设置响应式,然后执行依赖通知
*/
Vue.set = set
export function set(target: Array<any> | Object, key: any, val: any):any
if(process.env.NODE_ENV !== 'prodcution' && (isUndef(target) ||isPrimitive(target))
warn(`Cannot set reactive property on undefined, null, or primitive value: $target: any`)
// 更新数组指定下标的元素,Vue.set(array,idx,val),通过splice方法实现响应式更新
if(Array.isArray(target) && isValidArrayIndex(key))
target.length = Math.max(target.length,key)
target.splice(key,1,val)
return val
//更新对象已有属性,Vue.set(obj,key,val),执行更新即可
if( key in target && !(key in Object.prototype)
target[key]=val
return val
const ob = taret:any.__ob__
//不能向Vue实例或者$data添加动态响应式属性,vmCount的用处之一
//this.$data 的ob.vmCount = 1,表示根组件,其它子组件的vm.vmCount都是0
if(target._isVue || (ob && ob.vmCount))
process.env.NODE_ENV !== 'production' && warn(`
Avoid adding reactive properties to a Vue instance or its root $data
at runtime - declare it upfront in the data option
`)
return val
// target 不是响应式对象,新属性会被设置,但是不会做响应式处理
if(!ob)
target[key] = val
return val
// 给对象定义新属性,通过defineReactive方法设置响应式,并触发依赖更新
defineReactive(ob.value,key,val)
ob.dep.notify()
Vue.delete
/src/core/global-api/index.js
/**
* 通过Vue.delete 或者 vm.$delete 删除target对象的指定key
* 数组通过splice方法实现,对象则通过delete运算符删除制定key,并执行依赖通知
*/
export function del(target: Array<any> | Object, key:any)
if(process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
)
warn(`Cannot delete reactive property on undefined, null, or primitive value: $(target: any)`)
// target 为数组,则通过splice方法删除指定下标 的元素
if(Array.isArray(target) && isValidArrayIndex(key))
target.splice(key,1)
return
const ob = (target: any).__ob__
//避免删除Vue实例的属性或者$data的数据
if(target._isVue || (ob && ob.vmCount))
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
//如果属性不存在直接结束
if(!hasOwn(target,key))
return
//通过delete运算符删除对象的属性
delete target[key]
if(!ob)
return
//执行依赖通知
ob.dep.notify()
Vue.nextTick
/src/core/global-api/index.js
Vue.nextTick = nextTick
nextTick
/src/core/util/next-tick.js
总结:
-
Vue.use(plugin)做了什么?
负责安装plugin插件,其实就是执行插件提供的install方法- 首先判断该插件是否已经安装过了
- 如果没有,则执行插件提供的install方法安装插件,具体做什么由插件自己决定
-
Vue.mixin(options)做了什么?
负责在Vue的全局配置上合并options配置,然后在每个组件生成vnode时会将全局撇值合并到组件自身的配置上来。- 标准化options对象上的props,inject,directive选项的格式
- 处理options上的extends和mixins,分别将它们合并到全局配置上
- 然后将options配置和全局配置进行合并,选项冲突时options配置会覆盖全局配置
-
Vue.component(compName,Comp)做了什么?
负责注册全局组件。其实就是将组件配置注册到全局配置的compoents选项上(options.components),然后各个子组件在生成vnode时会将全局的components选项合并到局部的components配置项上。- 如果第二个参数为空,则表示获取compName的组件构造函数
- 如果Comp是组件配置对象,则使用Vue.extend方法得到组件构造函数,否则直接进行下一步。
- 在全局配置上设置组件信息,this.options.components.compName = compConstructor
-
Vue.directive(‘my-directive’,xx)做了什么?
在全局注册my-directive指令,然后每个子组件在生成vnode时会将全局的directives选项合并到局部的directive选项中。原理同Vue.component方法: -
如果第二个参数为空,则获取指定指令的配置对象
-
如果不为空,如果第二个参数时一个函数的话,则生成配置对象(bind:第二个参数,update:第二个参数)
-
然后将指令配置对象设置到全局配置上,this.options.directives[‘my-directive’] = xx
-
Vue.filter(‘my-filter’,function(val)xx)做了什么?
负责在全局注册过滤器my-filter,然后每个子组件在生成vnode时会将全局的filters选项合并到局部的filters选项中。原理是:- 如果没有提供第二个参数,则获取my-filter过滤器的回掉函数
- 如果提供了第二个参数,则是设置this.options.filters[‘my-filter’]=function(val)xx.
-
Vue.extend(options)做了什么?
Vue.extend基于Vue创建了一个子类,参数options会作为该子类的默认全局配置,就像Vue的默认全局配置一样。所以通过Vue.extend扩展一个子类,一大用处就是内置一些公共配置,供子类使用。 -
定义子类构造函数,这里和Vue一样,也是调用_init(options)
-
合并Vue的配置和options,如果选项冲突,则options的选项会覆盖Vue的配置项
-
给子类定义全局API,值为Vue的全局API,比如 Sub.extend = Super.extend,这样子类同样可以扩展出其它子类
-
返回子类Sub
-
Vue.set(target,key,val) 做了什么?
由于Vue无法探测普通的新增property(比如this.myObject.netProperty = ‘hi’),所以通过Vue.set为响应式对象中添加一个property,可以确保这个新property同样是响应式的,且触发视图更新。 -
Vue.delete(target,key)做了什么?
删除对象的property。如果对象是响应式的,确保删除能触发更新视图。这个方法主要是用于避开Vue 不能检测到property被删除的限制,同样不能删除根级别的响应式属性。 -
Vue.nextTick(cb)做了什么?
Vue.nextTick(cb)方法的作用是延迟回掉函数cb的执行,一般用于this.key=newVal更新数据后,想立即获取更改过后的DOM数据;
其内部执行过程是:
- this.key = ‘new val’ ,触发依赖通知更新,将负责更新的watcher放入watcher队列
- 将刷新的watcher队列的函数放到callbacks数组中
- 在浏览器的异步队列中放入一个刷新callbacks数组的函数
- Vue.nextTick(cb)来插队,将cb函数放入callbacks数组
- 待将来的某个时刻执行刷新callbacks数组的函数
- 然后执行callbacks数组中的众多函数,触发watcher.run的执行,更新DOM
- 由于cb函数是在后面放入到callbacks数组的,所以这就保证先完成的DOM更新,再执行cb函数。
以上是关于Vue源码学习- 全局API的主要内容,如果未能解决你的问题,请参考以下文章