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方法

    1. 首先判断该插件是否已经安装过了
    2. 如果没有,则执行插件提供的install方法安装插件,具体做什么由插件自己决定
  • Vue.mixin(options)做了什么?
    负责在Vue的全局配置上合并options配置,然后在每个组件生成vnode时会将全局撇值合并到组件自身的配置上来。

    1. 标准化options对象上的props,inject,directive选项的格式
    2. 处理options上的extends和mixins,分别将它们合并到全局配置上
    3. 然后将options配置和全局配置进行合并,选项冲突时options配置会覆盖全局配置
  • Vue.component(compName,Comp)做了什么?
    负责注册全局组件。其实就是将组件配置注册到全局配置的compoents选项上(options.components),然后各个子组件在生成vnode时会将全局的components选项合并到局部的components配置项上。

    1. 如果第二个参数为空,则表示获取compName的组件构造函数
    2. 如果Comp是组件配置对象,则使用Vue.extend方法得到组件构造函数,否则直接进行下一步。
    3. 在全局配置上设置组件信息,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选项中。原理是:

    1. 如果没有提供第二个参数,则获取my-filter过滤器的回掉函数
    2. 如果提供了第二个参数,则是设置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数据;
    其内部执行过程是:

  1. this.key = ‘new val’ ,触发依赖通知更新,将负责更新的watcher放入watcher队列
  2. 将刷新的watcher队列的函数放到callbacks数组中
  3. 在浏览器的异步队列中放入一个刷新callbacks数组的函数
  4. Vue.nextTick(cb)来插队,将cb函数放入callbacks数组
  5. 待将来的某个时刻执行刷新callbacks数组的函数
  6. 然后执行callbacks数组中的众多函数,触发watcher.run的执行,更新DOM
  7. 由于cb函数是在后面放入到callbacks数组的,所以这就保证先完成的DOM更新,再执行cb函数。

以上是关于Vue源码学习- 全局API的主要内容,如果未能解决你的问题,请参考以下文章

vue2.0学习-全局API

摸透Vue3中的13个全局Api,让你的代码更优雅!

一篇文章深度理解 13 个 Vue3 中的全局 Api(收藏!)

vue2源码-- 其他全局 api

vue2源码-- 其他全局 api

Vuejs1029- 源码浅析-Vue3中的13个全局 API