• 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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    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
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    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
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    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
    		
    	}
    }	
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    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]
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    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函数。
  • 相关阅读:
    大数据之hadoop hive hbase 的区别是什么?有什么应用场景?
    软件测试面试必备,一文带你彻底掌握接口测试
    二叉树结构以及堆结构基础
    手工测试如何进阶自动化测试?8年美团测试工程师浅谈一下...
    MySQL锁:全局锁、表级锁和行锁
    java计算机毕业设计大学生学籍管理系统源码+系统+lw文档+mysql数据库+部署
    视频讲解|1033含sop的配电网重构(含风光可多时段拓展)
    甬矽电子在科创板上市:市值达到122亿元,王顺波为实际控制人
    C++类的函数运算操作
    11.19 - 每日一题 - 408
  • 原文地址:https://blog.csdn.net/weixin_44374938/article/details/127898422