源码笔记(三):beforeCreate 阶段
根据 demo 配置,将 demo 跑起来,然后忽略掉分支剧情,只分析 Vue 运行的主流程。
引入 Vue 和 App
执行 index.js, 先执行:
import Vue from 'vue';
import App from './app.vue';- 变量
Vue为Vue的构造函数,在执行Vue文件的过程中,会初始化设置Vue上的原型变量方法,Watch类,Dep类等等。 - 变量
App为经过webpack编译,通过vue-loader,vue-template-compiler,VueLoaderPlugin作用后的包含render和staticRenderFns的组件选项对象:
{
name: "app"
data: ƒ data()
components: {Child: {…}}
props: {num: ƒ, name: ƒ}
beforeCreate: ƒ beforeCreate()
created: ƒ created()
beforeMount: ƒ beforeMount()
mounted: ƒ mounted()
beforeUpdate: ƒ beforeUpdate()
updated: ƒ updated()
beforeDestroy: ƒ beforeDestroy()
destroyed: ƒ destroyed()
render: ƒ ()
staticRenderFns: []
_compiled: true
__file: "src/app.vue"
}其中 staticRenderFns 包含标记为静态节点的 vnode 单独生成的 render 函数的数组。
处理 vm.$options
执行 new Vue(),即执行了 Vue 原型链上 _init 方法,内部执行:
if (options && options._isComponent) {
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);
}根据传入的 options 里是否包含组件标识而执行不同的方法。
initInternalComponent
如果是组件调用,则执行 initInternalComponent 设置 vm.$options。
以第一个 App 组件为例,方法中将组件的 options 赋给 vm.$options 的原型上。最后经过一系列的设置得到 vm.$options:
{
parent: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …} //父组件实例
_parentVnode: VNode {tag: "vue-component-1-app", data: {…}, children: undefined, text: undefined, elm: undefined, …} // 组件vnode
propsData: {title: 1, name: "one"} //组件vnode上的props
_parentListeners: {welcome: ƒ} //组件vnode上的事件
_renderChildren: undefined //插槽vnode
_componentTag: "app" //组件vnode对应的组件实例名称
}其原型为挂载在子组件构造函数下的 options 属性(Vue.extend 里定义,为 mergeOptions 后的组件选项对象)。
resolveConstructorOptions
如果不是组件,则执行 resolveConstructorOptions 和 mergeOptions 设置 vm.$options。
参数 Ctor 即为传入的 vm.constructor 即 Vue 构造函数。Ctor.options 即为 Vue.options,该 options 在方法 initGlobalAPI 中定义。本 demo 的 Ctor.options 为:
{
components: {KeepAlive: {…}, Transition: {…}, TransitionGroup: {…}}
directives: {model: {…}, show: {…}}
filters: {}
_base: ƒ Vue(options)
}判断 Ctor.super 是否存在,不存在即为普通构造器,方法直接返回 Ctor.options;若存在,说明这是通过 Vue.extend 构造的子类,那么就递归合并其父级构造器的 options 和本身的 Ctor.extendOptions,最后返回合并后的 options 详解。
mergeOptions
mergeOptions 里先对 child(即参数 options) 执行 checkComponents 方法检查 options.components 命名是否合法。然后根据 options 的配置,接着检查并格式化了 Props,Inject,Directives。然后根据是否有 extends 和 mixins 递归调用 mergeOptions 合并 extends 和 mixins。
调用 mergeField 对 parent(resolveConstructorOptions(vm.constructor) 得到) 和 child(options) 进行了 strats 处理。 strats 用于合并处理 options 详解。合并后,其中 data 为函数 mergedInstanceDataFn。最后将 options 赋给实例 $options 属性上。得到 vm.$options:
{
components: {App: {…}, Bpp: ƒ}
directives: {}
filters: {}
_base: ƒ Vue(options)
el: "#main"
data: ƒ mergedInstanceDataFn()
computed: {compute: ƒ}
methods: {plus: ƒ, hide: ƒ}
watch: {b: {…}}
beforeCreate: [ƒ]
created: [ƒ]
beforeMount: [ƒ]
mounted: [ƒ]
beforeUpdate: [ƒ]
updated: [ƒ]
}初始化
然后执行:
initProxy(vm);
//...
initLifecycle(vm);
initEvents(vm);
initRender(vm);下面分别分析各初始化内容:
initProxy
即执行 vm._renderProxy = new Proxy(vm, handlers); 其中 handlers 为 hasHandler。
如果 options 有 render,且 _withStripped 为真(比如组件),handlers 为 getHandler。
initLifecycle
初始化了实例上 $parent, $root, $children, _watcher, _isMounted 等属性。
这里如果是组件执行的话,会更新其父子组件的对应关系,更新父组件上的 $children 实例数组,更新当前组件的 $parent, $root 实例。
这里如果是 keep-alive 组件里的子组件的话,即 parent.options.abstract 为真,则更新子组件的 $parent 为 keep-alive 的父组件。
initEvents
如果是在子组件,则判断是否存在 $options._parentListeners 即在组件 Vnode 上的事件,如果有则通过 updateComponentListeners->updateListeners->add 执行 vm.$on 监听当前子组件实例上的自定义事件,事件由 vm.$emit 触发。
$on,$emit本质为一个发布订阅,$on收集订阅挂载在实例的_events属性下,$emit执行订阅事件列表。
initRender
初始化了渲染相关的函数 _c , $createElement 等,然后监听了实例上:
$attrs(包含了父作用域中不作为prop被识别 (且获取) 的特性绑定)$listeners(包含了父作用域中的 (不含.native修饰器的)v-on事件监听器)
这里两个属性(各自 new 了一个 Dep 订阅者列表)。
如果是子组件,会将 _parentVnode 赋给 vm.$vnode。
如果有普通插槽,命名插槽,作用域插槽等,则初始化 $slots, $scopedSlots 。
触发 beforeCreate 钩子
callHook(vm, 'beforeCreate');执行生命周期钩子 beforeCreate,打印 vue beforeCreate。
本章小结
- 本章介绍了
vue执行的beforeCreate阶段; - 合并处理了组件
options,父组件options,平台初始化的options,得到vm.$options; - 初始化与生命周期相关的
Vue实例的各种属性,如父子关系的$parent等,初始化event事件传递相关; - 初始化了渲染相关的
Vue实例上的函数,插槽等。