源码笔记(三):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
实例上的函数,插槽等。