前言
在使用 Vue 框架在进行开发时,不免会使用到一些 global-api 或者是实例上的 api(本质上也是调用了全局 api ,相当于是别名),用于解决在项目遇到的一些问题,比如:this.$set()、this.$nextTick()
等等。还有一些全局 api 很可能只是因为不了解这个 api 的作用,所以一直没有找到合适的场景去使用它,那现在我们就通过源码层面看看这些全局 api 的作用是什么,以及了解其对应的原理。
下面是【官方文档目录】和【源码目录】的对比:
深入源码
initGlobalAPI() 方法
文件位置:src\core\global-api\index.js
初始化全局 api,比如:Vue.util = {...}、Vue.options = {...}、Vue.[set | del | nextTick | observable | use | mixin | extend | component | directive | filter]
,详细内容请看下面代码的注释.
// 初始化全局 api 的入口
export function initGlobalAPI (Vue: GlobalAPI) {
// Vue 全局默认配置 config
const configDef = {}
configDef.get = () => config
// 不允许通过 Vue.config = {} 的方式进行覆盖
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
// 将配置项 config 代理到 Vue 上,支持通过 Vue.config 的方式去访问
Object.defineProperty(Vue, 'config', configDef)
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
// 向外暴露了一些工具方法
Vue.util = {
// 警告日志
warn,
// extend (to: Object, _from: ?Object), 将 _from 对象上的属性复制到 to 对象
extend,
// 合并配置项
mergeOptions,
// 设置 getter 和 setter,分别进行 依赖收集 和 依赖更新通知
defineReactive
}
// 全局 set 方法,处理数组元素或对象属性的新增或修改
Vue.set = set
// 全局 delete 方法,删除数组元素或对象属性
Vue.delete = del
// 全局 nextTick 方法,主要依赖于浏览的异步任务队列
Vue.nextTick = nextTick
// 2.6 explicit observable API
// 全局 observable 方法,本质就是 observe 方法,将接收对象转换为响应式对象
Vue.observable = <>(obj: T): T => {
observe(obj)
return obj
}
{/* 为全局 options 设置指定的配置项 Vue.options = { components:{}, directive: {}, filters:{} } */}
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
{/* 将 Vue 赋值给 Vue.options._base,向外进行暴露 */}
Vue.options._base = Vue
{/*
builtInComponents 实际上就是 KeepAlive 组件
将 KeepAlive 注册到 components 全局组件配置当中,即可以直接在全局使用 <keep-alive></keep-alive>
*/}
extend(Vue.options.components, builtInComponents)
{/* 初始化 Vue.use 方法 */}
initUse(Vue)
{/* 初始化 Vue.mixin 方法 */}
initMixin(Vue)
{/* 初始化 Vue.extend 方法 */}
initExtend(Vue)
{/* 初始化 Vue.component、Vue.directive、Vue.filter 方法 */}
initAssetRegisters(Vue)
}
Vue.set() 方法
文件位置:src\core\observer\index.js
实例上的 vm.$set
是全局 Vue.set
的 别名,目的是向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,如果 key
已存在就将新值替换旧值,且触发视图更新.
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 处理数组:Vue.set(arr, index, value),实现响应式
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
// 本质上是通过重写后的 splice 方法进行实现元素替换
target.splice(key, 1, val)
return val
}
/*
处理对象: Vue 无法探测普通的新增 property,如果需要可以通过 Vue.set(this, newKey, value) 进行设置动态的、具有响应式的属性
*/
// 当前对象上存在 key 属性且不属于原型对象上时,直接更新旧值
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// 判断当前对象是否经过 observer 处理,__ob__ 存在即已处理,否则表明是普通对象
const ob = (target: any).__ob__
// 避免在运行时给 Vue 实例或者根组件的 $data 上直接添加属性或修改属性值
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
}
// 给普通对象进行 Vue.set(obj, key, value) 是可以设置成功的,但是不具备响应式
if (!ob) {
target[key] = val
return val
}
// 对新属性设置 getter 和 setter,读取时进行依赖收集,设置时进行依赖更新通知
defineReactive(ob.value, key, val)
// 直接进行依赖更新通知
ob.dep.notify()
return val
}
Vue.delete() 方法
文件位置:src\core\observer\index.js
实例上的 vm.$delete
是全局 Vue.delete
方法的 别名,目的是在删除对象的 property 时,若对象是响应式的,确保删除能触发更新视图.
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)}`)
}
// 删除数组元素:利用重写后的 splice 方法删除元素
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
// 删除对象属性
const ob = (target: any).__ob__
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
}
// 当前 key 不存在当前对象上,直接终止
if (!hasOwn(target, key)) {
return
}
// 通过 delete 操作符删除对象属性
delete target[key]
// 如果是普通对象,删除属性后不进行依赖更新通知
if (!ob) {
return
}
// 如果是响应式对象,删除属性后进行依赖更新通知
ob.dep.notify()
}
Vue.nextTick() 方法
文件位置:src\core\util\next-tick.js
将回调延迟到下次 DOM 更新循环之后执行,在修改数据之后立即使用它,然后等待 DOM 更新。
与全局方法 Vue.nextTick
一样,不同的是回调的 this
自动绑定到调用它的实例上。
在前面的文章中有对 nextTick
的详细介绍,可以通过 nextTick 更详细介绍 进行查看.
export function nextTick(cb?: Function, ctx?: Object) {
let _resolve
// 将 cb 通过匿名函数包裹一层,然后存入到 callbacks 中
callbacks.push(() => {
// cb 可能是 Vue 内部传递的 flushSchedulerQueue 函数,也可能是用户在外部传入的自定义函数,因此这里需要对 cb 进行 try catch,方便捕获异常
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
// cb 不存在,默认执行在下面 _resolve = resolve 方法
_resolve(ctx)
}
})
// pending = false 时,需要执行 timerFunc()
if (!pending) {
pending = true
// 利用浏览器的异步任务执行 flushCallbacks() 函数
timerFunc()
}
// 当 cb 函数不存在且支持使用 Promise 时,需要提供一个默认函数,即 Promise 中的 resolve 方法
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
Vue.observable() 方法
文件位置:src\core\observer\index.js
实际上是调用的 observe()
方法,目的是让一个对象具有响应式,Vue
内部通常会用它来处理 data
函数返回的对象.
返回的对象可以直接用于 渲染函数 和 计算属性 内,并且会在发生变更时触发相应的更新.
在前面的文章中有对 observe
的详细介绍,可以通过 observer 更详细介绍 查看.
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// value 已经通过 Observer 处理,直接返回上一次的 __ob__ 实例
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 为 value 实例化一个 Observer 实例
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
initUse() 方法 —— 初始化 Vue.use
文件位置:src\core\global-api\use.js
如果插件是一个对象,必须提供 install
方法.
如果插件是一个函数,且这个函数对象上没有 install
属性的函数,那么它会被作为 install
方法,如果这个函数对象上有一个 install
属性的函数,那么会优先使用 install
属性.
install
方法调用时,会将 Vue
作为参数传入,该方法需要在调用 new Vue()
之前被调用.
export function initUse (Vue: GlobalAPI) {
// 一般用于注册插件,plugin 可以是 Function,也可以是对象 { install: (Vue)=>{} }
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 避免重复注册插件
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(arguments, 1)
// 将 Vue 作为插件参数中的第一个参数
args.unshift(this)
// plugin 对象上存在 install 属性,且值为函数,通过 apply 绑定 this 并进行调用
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
// plugin 本身是函数,通过 apply 进行调用
plugin.apply(null, args)
}
// 保存插件到 Vue.installedPlugins 数组中
installedPlugins.push(plugin)
return this
}
}
initMixin() 方法 —— 初始化 Vue.mixin
文件位置:src\core\global-api\mixin.js
全局注册一个混入,影响注册之后所有创建的每个 Vue
实例,不推荐使用. 使用最多的还是组件配置项上的 mixins 选项.
本质就是合并配配置项,即 mergeOptions()
方法.
export function initMixin (Vue: GlobalAPI) {
// 向组件配置中进行混入配置项
Vue.mixin = function (mixin: Object) {
// 本质就是合并配配置项
this.options = mergeOptions(this.options, mixin)
return this
}
}
mergeOptions() 方法
文件位置:src\core\util\options.js
主要就是将两个选项对象合并为一个新对象,也是实例化和继承的核心.
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
// 如果 child 是函数,就送函数上获取配置项
if (typeof child === 'function') {
child = child.options
}
// 对选项配置进行标准化
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
// 在子选项上进行 extends (扩展) 和 mixins (混合)
// 并且必须是原始配置对象,也就没有经过合并的配置对象
// 而每个被合并过的配置对象上都存在 _base 属性
if (!child._base) {
// 组件的 extents 和 Vue.extend 是一样的,extents 为了便于扩展单文件组件
// var CompA = { ... } , var CompB = { extends: CompA, ... }, B 继承了 A
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 = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
// 选项合并策略
// 默认策略为:如果 child 有值,就会覆盖 parent 值,否则使用 parent 值
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
Vue.extend() 方法
文件位置:src\core\global-api\extend.js
使用基础 Vue 构造器,创建一个 子类,参数是一个包含组件选项的对象,data
选项是特例,因为在 Vue.extend()
中它必须是函数.
export function initExtend (Vue: GlobalAPI) {
/**
* Each instance constructor, including Vue, has a unique
* cid. This enables us to create wrapped "child
* constructors" for prototypal inheritance and cache them.
*/
Vue.cid = 0
let cid = 1
/**
* Class inheritance
* 使用 Vue.extend,创建一个子类,参数是一个包含组件选项的对象
*/
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
// 缓存:如果使用同一个混入配置项,如果缓存中存在已存在,就直接使用缓存的值
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
// 校验组件名称
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
/*
核心:定义一个 Vue 子类,本质上和 Vue 构造函数一样
function Vue (options) {
this._init(options)
}
*/
const Sub = function VueComponent (options) {
this._init(options)
}
// 设置子类原型为基类的原型
Sub.prototype = Object.create(Super.prototype)
// 指定子类构造函数为自己
Sub.prototype.constructor = Sub
Sub.cid = cid++
// 合并基类选项和传入进来的配置项到子类配置项中,相当于进行预设
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
// 将 props 代理到子类上,在子类中可以直接通过 this.props 形式访问
if (Sub.options.props) {
initProps(Sub)
}
// 将 computed 代理到子类上,在子类中可以直接通过 this.computed 形式访问
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
// 将基类的扩展方法赋值给子类,使子类拥有自己的扩展能力
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
// 组件递归自调用的原理
// { name:'comp', components: { Comp } }
if (name) {
Sub.options.components[name] = Sub
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor
// 缓存子类
cachedCtors[SuperId] = Sub
return Sub
}
}
initAssetRegisters() 方法
文件位置:src\core\global-api\assets.js
这个方法就是负责初始化 Vue.component()
、Vue.directive()
、Vue.filter()
方法,因为这三个 api
实现上比较特殊,但是原理又很相似,所以就统一放在 initAssetRegisters
中进行初始化.
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
* 统一初始化 Vue.component, Vue.directive, Vue.filter
*/
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
// 如果对应的 definition 没有传递,直接返回
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
// 校验组件名字
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
// 如果是组件
if (type === 'component' && isPlainObject(definition)) {
// 设置组件的 name,如果是配置对象就先取 options.name,不存在就取传入的第一个值
definition.name = definition.name || id
// 通过 Vue.extend 方法,基于 definition 扩展一个新的组件子类,直接 new definition() 实例化一个组件
definition = this.options._base.extend(definition)
}
// 如果是指令
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
// 将每个配置项都放到根组件的对应配置项中
// 如:{components:{ id: comonent },directives:{id: directive} ,filters:{ id: filter },... }
this.options[type + 's'][id] = definition
return definition
}
}
})
}
总结
Vue.use(plugin) 的作用是什么?
- Vue.use 是用来安装 Vue 插件的
- 如果插件是一个 对象,必须提供 install 方法
- 如果插件是一个 函数,它会被作为 install 方法
- 当 install 方法调用时,会将 Vue 作为参数传入
- 该方法需要在调用 new Vue() 之前被调用
- 当 install 方法被同一个插件 多次调用,插件将只会被安装一次
Vue.mixin(options) 的作用是什么?
- Vue.mixin 本质上就是调用 mergeOptions( parent: Object, child: Object, vm?: Component),作用就是将两个选项对象合并为一个新对象
- 首先对 props、inject、directives 选项进行标准化处理
- 处理 options 上的 extends 和 mixins,最终将它们合并到 全局配置 上
- options 配置和 全局配置 进行合并发生选项冲突时,options 配置会覆盖 全局配置
PS:当使用组件配置项
mixins
进行混入配置项时
- 没有冲突就正常进行合并选项
- 除了 生命周期 钩子发生冲突时,会在组件对应生命周期钩子前被调用,其他如 methods、components 和 directives 等选项中发生冲突时以 组件 数据 优先
Vue.component(compName, Comp) 的作用是什么?
Vue.component 用于注册全局组件.
本质上就是将 当前组件配置 注册到全局配置的 components 选项上($root.options.components),然后各个子组件在生成 vnode 时会将全局的 components 选项合并到局部的 components 配置项上,自动实现局部注册组件.
- Vue.component(compName) 表示获取 compName 的组件构造函数
- 若 Comp 是组件配置对象,则使用 Vue.extend 方法得到组件构造函数,否则直接进行下一步
- 在全局配置上添加当前组件信息,
this.options.components = { compName:CompConstructor, xxx }
Vue.directive('my-directive', definition) 的作用是什么?
Vue.directive 用于注册或获取全局指令,在每个子组件在生成 vnode 时会将全局的 directives 选项合并到局部的 directives 选项中,如 this.options.directives = { directive:{xxx} }
- 如果 definition 为空,则会获取指定指令的配置对象
- 如果 definition 是一个函数,则会在 bind 和 update 时调用这个函数,相当于配置对象
{ bind: definition, update: definition }
Vue.filter('my-filter', definition) 的作用是什么?
Vue.filter 用于注册或获取全局过滤器,在每个子组件在生成 vnode 时会将全局的 filters 选项合并到局部的 filters 选项中,如 this.options.filters = { filters:{xxx} }
- 如果 definition 为空,则获取 my-filter 过滤器的回调函数
- 如果 definition 为存在,则注册
this.options.filters['my-filter'] = definition
Vue.extend(extendOptions) 的作用是什么?
Vue.extend 基于 Vue 创建一个子类,通过 mergeOptions(Super.options, extendOptions) 生成新的配置项作为该子类默认的全局配置,因此子类也可以通过 extend() 方法得到自己的子类。
- 定义子类构造函数,和基类 Vue 一样也是调用 this._init(options)
- 将 Vue.options 和 extendOptions 合并,如果选项冲突,则 extendOptions 会覆盖 Vue.options 中对应的内容,相当于进行预设
- 给子类定义和 Vue 一样的全局 API,比如
Sub.extend = Super.extend
- 向外返回子类 Sub
Vue.set(target, key, val) 和 Vue.delete(target, key) 的作用是什么?
Vue.set(target, key, val) —— 新增或更新对应属性或元素值
- 向响应式对象中添加一个新的 property,对新属性设置 getter 和 setter,读取时进行依赖收集,设置时进行依赖更新通知
- 如果 target 是数组,则 key 需要为数组中对应的 index,本质上是通过重写的 splice 方法进行更新数组元素
- 如果 target 是对象,则直接更新对应属性数据
- 如果 target 不是响应式对象,对应操作会成功,但是不会具备响应式
- 不能向 Vue 实例或者 $data 动态添加根级别的响应式数据 Vue.delete(target, key) —— 删除对象的 property
- 如果 target 是响应式对象,确保删除对应属性后能触发更新视图
- 如果 target 是数组,还是通过重写的 splice 方法进行删除元素
- 如果 target 是对象,则通过 delete target[key] + ob.dep.notify() 实现删除并更新视图