深入理解以下全局API的实现原理
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)
}
/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
}
}
/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
}
}
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
}
/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
}
}
}
}
/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]
}
}
}
/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()
}
/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()
}
/src/core/global-api/index.js
Vue.nextTick = nextTick
/src/core/util/next-tick.js
Vue.use(plugin)做了什么?
负责安装plugin插件,其实就是执行插件提供的install方法
Vue.mixin(options)做了什么?
负责在Vue的全局配置上合并options配置,然后在每个组件生成vnode时会将全局撇值合并到组件自身的配置上来。
Vue.component(compName,Comp)做了什么?
负责注册全局组件。其实就是将组件配置注册到全局配置的compoents选项上(options.components),然后各个子组件在生成vnode时会将全局的components选项合并到局部的components配置项上。
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选项中。原理是:
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数据;
其内部执行过程是: