入口文件 /src/core/global-api/index.js
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 export function initGlobalAPI (Vue : GlobalAPI ) { const configDef = {} configDef.get = () => config if (process.env .NODE_ENV !== 'production' ) { configDef.set = () => { warn ( 'Do not replace the Vue.config object, set individual fields instead.' ) } } Object .defineProperty (Vue , 'config' , configDef) Vue .util = { warn, extend, mergeOptions, defineReactive } Vue .set = set Vue .delete = del Vue .nextTick = nextTick Vue .observable = <T>(obj : T): T => { observe (obj) return obj } Vue .options = Object .create (null ) ASSET_TYPES .forEach (type => { Vue .options [type + 's' ] = Object .create (null ) }) Vue .options ._base = Vue extend (Vue .options .components , builtInComponents) initUse (Vue ) initMixin (Vue ) initExtend (Vue ) initAssetRegisters (Vue ) }
主要就是挂载 api
Vue.use 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 Vue .use = function (plugin: Function | Object ) { const installedPlugins = (this ._installedPlugins || (this ._installedPlugins = [])) if (installedPlugins.indexOf (plugin) > -1 ) { return this } const args = toArray (arguments , 1 ) args.unshift (this ) if (typeof plugin.install === 'function' ) { plugin.install .apply (plugin, args) } else if (typeof plugin === 'function' ) { plugin.apply (null , args) } installedPlugins.push (plugin) return this }
Vue.mixin 1 2 3 4 5 6 7 8 9 10 Vue .mixin = function (mixin: Object ) { this .options = mergeOptions (this .options , mixin) return this }
mergeOptions 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 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 } normalizeProps (child, vm) normalizeInject (child, vm) normalizeDirectives (child) 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 = {} let key for (key in parent) { mergeField (key) } for (key in child) { if (!hasOwn (parent, key)) { mergeField (key) } } function mergeField (key) { const strat = strats[key] || defaultStrat options[key] = strat (parent[key], child[key], vm, key) } return options }
Vue.component/filter/directive 这三个方法放在一起,因为用法类似
1 2 3 4 5 6 7 8 Vue .filter (....)Vue .directive (....)Vue .mixin (....)
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 const ASSET_TYPES = ['component' , 'directive' , 'filter' ]ASSET_TYPES .forEach (type => { Vue [type] = function ( id: string, definition: Function | Object ): Function | Object | void { if (!definition) { return this .options [type + 's' ][id] } else { if (type === 'component' && isPlainObject (definition)) { definition.name = definition.name || id definition = this .options ._base .extend (definition) } if (type === 'directive' && typeof definition === 'function' ) { definition = { bind : definition, update : definition } } this .options [type + 's' ][id] = definition return definition } } })
Vue.extend 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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 Vue .cid = 0 let cid = 1 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) } 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 if (Sub .options .props ) { initProps (Sub ) } if (Sub .options .computed ) { initComputed (Sub ) } Sub .extend = Super .extend Sub .mixin = Super .mixin Sub .use = Super .use ASSET_TYPES .forEach (function (type ) { Sub [type] = Super [type] }) if (name) { Sub .options .components [name] = Sub } Sub .superOptions = Super .options Sub .extendOptions = extendOptions Sub .sealedOptions = extend ({}, Sub .options ) 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/global-api/index.js
/src/core/observer/index.js
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 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)} ` ) } if (Array .isArray (target) && isValidArrayIndex (key)) { target.length = Math .max (target.length , key) target.splice (key, 1 , val) return val } if (key in target && !(key in Object .prototype )) { target[key] = val return val } const ob = (target : any).__ob__ 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 } if (!ob) { target[key] = val return val } defineReactive (ob.value , key, val) ob.dep .notify () return val }
1.如果 target 是数组,直接通过 splice 方法响应式更新元素,因为数组方法已重写。
2.如果 target 是对象并且对象已有该属性(key),则直接 target[key] = val 修改即可,因为 setter 中会做响应式更新。
3.如果 target 不是响应式对象,直接 target[key] = val,但其不是响应式的,因为 target 不是响应式对象,它的 setter 中不会去 notify。
4.如果 target 是对象且没有该属性(key),则调用 defineReactive 设置响应式,并调用 dep.notify() 触发依赖更新。
Vue.delete /src/core/global-api/index.js
/src/core/observer/index.js
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 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)} ` ) } 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 } if (!hasOwn (target, key)) { return } delete target[key] if (!ob) { return } ob.dep .notify () }
1.如果 target 是数组,和 vue.set() 类似,使用 splice 删除
2.key 不存在,直接 return
3.如果 target 是对象,delete target[key] 即可,然后 dep.notify() 触发依赖更新
Vue.nextTick 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 const callbacks = []export function nextTick (cb?: Function , ctx?: Object ) { let _resolve callbacks.push (() => { if (cb) { try { cb.call (ctx) } catch (e) { handleError (e, ctx, 'nextTick' ) } } else if (_resolve) { _resolve (ctx) } }) if (!pending) { pending = true timerFunc () } if (!cb && typeof Promise !== 'undefined' ) { return new Promise (resolve => { _resolve = resolve }) } }
面试题 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) 做了什么? 负责注册全局组件。其实就是将组件配置注册到全局配置的 components 选项上(options.components),然后各个子组件在生成 vnode 时会将全局的 components 选项合并到局部的 components 配置项上。
如果第二个参数为空,则表示获取 compName 的组件构造函数
如果 Comp 是组件配置对象,则使用 Vue.extend 方法得到组件构造函数,否则直接进行下一步
在全局配置上设置组件信息,this.options.components.compName = CompConstructor
Vue.directive(‘my-directive’, {xx}) 做了什么? 在全局注册 my-directive 指令,然后每个子组件在生成 vnode 时会将全局的 directives 选项合并到局部的 directives 选项中。原理同 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.set(target, key, val) 做了什么? 由于 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = ‘hi’),所以通过 Vue.set 为向响应式对象中添加一个 property,可以确保这个新 property 同样是响应式的,且触发视图更新。
更新数组指定下标的元素:Vue.set(array, idx, val),内部通过 splice 方法实现响应式更新
更新对象已有属性:Vue.set(obj, key ,val),直接更新即可 => obj[key] = val
不能向 Vue 实例或者 $data 动态添加根级别的响应式数据
Vue.set(obj, key, val),如果 obj 不是响应式对象,会执行 obj[key] = val
,但是不会做响应式处理
Vue.set(obj, key, val),为响应式对象 obj 增加一个新的 key,则通过 defineReactive 方法设置响应式,并触发依赖更新
Vue.delete(target, key) 做了什么? 删除对象的 property。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到 property 被删除的限制,但是你应该很少会使用它。当然同样不能删除根级别的响应式属性。
Vue.delete(array, idx),删除指定下标的元素,内部是通过 splice 方法实现的
删除响应式对象上的某个属性:Vue.delete(obj, key),内部是执行 delete obj.key
,然后执行依赖更新即可
Vue.nextTick(cb) 做了什么? Vue.nextTick(cb) 方法的作用是延迟回调函数 cb 的执行,一般用于 this.key = newVal
更改数据后,想立即获取更改过后的 DOM 数据:
1 2 3 4 5 this .key = 'new val' Vue .nextTick (function ( ) { })
其内部的执行过程是:
this.key = 'new val
,触发依赖通知更新,将负责更新的 watcher 放入 watcher 队列
将刷新 watcher 队列的函数放到 callbacks 数组中
在浏览器的异步任务队列中放入一个刷新 callbacks 数组的函数
Vue.nextTick(cb) 来插队,将 cb 函数放入 callbacks 数组
待将来的某个时刻执行刷新 callbacks 数组的函数
然后执行 callbacks 数组中的众多函数,触发 watcher.run 的执行,更新 DOM
由于 cb 函数是在后面放到 callbacks 数组,所以这就保证了先完成的 DOM 更新,再执行 cb 函数
参考 https://juejin.cn/post/6952643167715852319