通过之前的源码学习,我们已经了解到了new Vue主要有两种场景,第一种就是在外部主动调用new Vue创建一个实例,第二个就是代码内部创建子组件的时候自行创建一个new Vue实例。但是无论那种new Vue方式,我们都需要进入了Vue._init,执行mergeOptions函数合并配置。为了更直观,我们整个demo调试耍耍。
// src\main.js let childComp = { template:"<div>{{msg}}</div>", data(){ return{ msg:"childComp" } }, created(){ console.log("childComp created"); }, mounted(){ console.log("childComp mounted"); } } Vue.mixin({ created(){ console.log("mixin"); } }) let app = new Vue({ el:"#app", render: h => h(childComp) })我用的时vue-cli3,这里有个小细节需要注意一下,vue-cli3开发环境默认使用的是runtime版本(node_modules\vue\dist\vue.runtime.esm.js),这个版本是不支持编译template的,需要用Compiler版本,这个在vue.config.js中配置一下即可,配置代码如下:
module.exports = { runtimeCompiler: true }准备工作搞好了,那么我们现在开始进入_init函数,看看合并配置是怎么一个说法。
// src\core\instance\init.js Vue.prototype._init = function (options?: Object) { ... if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), //vue.options options || {}, //new Vue中的options vm ) } ... } 外部调用场景上述代码中可明显看出两中合并配置的情况,我们一开始进入的肯定时非组件模式,也就是else情况。mergeOptions传入了3个入参,我们先看第一个入参的resolveConstructorOptions方法做了什么。
// src\core\instance\init.js export function resolveConstructorOptions (Ctor: Class<Component>) { let options = Ctor.options if (Ctor.super) { const superOptions = resolveConstructorOptions(Ctor.super) const cachedSuperOptions = Ctor.superOptions if (superOptions !== cachedSuperOptions) { // super option changed, // need to resolve new options. Ctor.superOptions = superOptions // check if there are any late-modified/attached options (#4976) const modifiedOptions = resolveModifiedOptions(Ctor) // update base extend options if (modifiedOptions) { extend(Ctor.extendOptions, modifiedOptions) } options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) if (options.name) { options.components[options.name] = Ctor } } } return options }入参Ctor = vm.constructor = Vue,Vue没有父级,所以不会进入到if逻辑,因此这里返回的就是Vue.options的配置。Vue.options则在初始化的时候就做了定义和配置。
// src\core\global-api\index.js Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) Vue.options._base = Vue //createComponent时用到,之前提及过。 extend(Vue.options.components, builtInComponents) //扩展一些内置组件这里ASSET_TYPES在src\shared\constants.js有定义
// src\shared\constants.js export const ASSET_TYPES = [ 'component', 'directive', 'filter' ]然后我们再返回去_init函数分析一下mergeOptions函数:
// src\core\util\options.js export function mergeOptions ( parent: Object, child: Object, vm?: Component ): Object { ... const options = {} let key 、、 for (key in parent) { mergeField(key) } for (key in child) { // key没在parent定义时 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 }简略了部分代码,我们先去关注合并的关键代码。
这边其实就是遍历了parent(Vue.options)和child(new Vue中的options),然后遍历的过程中调用了mergeField方法。而该方法先去拿到一个strat函数,这个函数首先是再strats中去找,没找到就使用defaultStrat默认函数(defaultStrat可自行查阅源码),我们主要看strats:
strats是定义在config中,所以说我们是可以随意改动strats的。然后在options.js中,strats扩展了很多属性,每个属性(key)都是一种合并策略,有兴趣的可以一个个研究,因为我们例子是生命周期的合并,所以我们先挑生命周期的合并策略来分析,后面遇到其他的再做分析。
// src\core\util\options.js LIFECYCLE_HOOKS.forEach(hook => { strats[hook] = mergeHook })LIFECYCLE_HOOKS定义在src\shared\constants.js
// src\shared\constants.js export const LIFECYCLE_HOOKS = [ 'beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'beforeDestroy', 'destroyed', 'activated', 'deactivated', 'errorCaptured' ]