Skip to content

源码笔记(三):beforeCreate 阶段

根据 demo 配置,将 demo 跑起来,然后忽略掉分支剧情,只分析 Vue 运行的主流程。

引入 Vue 和 App

执行 index.js, 先执行:

js
import Vue from 'vue';
import App from './app.vue';
  • 变量 VueVue 的构造函数,在执行 Vue 文件的过程中,会初始化设置 Vue 上的原型变量方法,Watch 类,Dep 类等等。
  • 变量 App 为经过 webpack 编译,通过 vue-loader,vue-template-compiler,VueLoaderPlugin 作用后的包含 renderstaticRenderFns组件选项对象:
js
{
  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 方法,内部执行:

js
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

js
{
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

如果不是组件,则执行 resolveConstructorOptionsmergeOptions 设置 vm.$options

参数 Ctor 即为传入的 vm.constructorVue 构造函数。Ctor.options 即为 Vue.options,该 options 在方法 initGlobalAPI 中定义。本 demoCtor.options 为:

js
{
  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。然后根据是否有 extendsmixins 递归调用 mergeOptions 合并 extendsmixins

调用 mergeFieldparent(resolveConstructorOptions(vm.constructor) 得到)child(options) 进行了 strats 处理。 strats 用于合并处理 options 详解。合并后,其中 data 为函数 mergedInstanceDataFn。最后将 options 赋给实例 $options 属性上。得到 vm.$options

js
{
  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: [ƒ]
}

初始化

然后执行:

js
initProxy(vm);
//...
initLifecycle(vm);
initEvents(vm);
initRender(vm);

下面分别分析各初始化内容:

initProxy

即执行 vm._renderProxy = new Proxy(vm, handlers); 其中 handlershasHandler

如果 optionsrender,且 _withStripped 为真(比如组件),handlersgetHandler

initLifecycle

初始化了实例上 $parent, $root, $children, _watcher, _isMounted 等属性。

这里如果是组件执行的话,会更新其父子组件的对应关系,更新父组件上的 $children 实例数组,更新当前组件的 $parent, $root 实例。

这里如果是 keep-alive 组件里的子组件的话,即 parent.options.abstract 为真,则更新子组件的 $parentkeep-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 钩子

js
callHook(vm, 'beforeCreate');

执行生命周期钩子 beforeCreate,打印 vue beforeCreate

本章小结

  1. 本章介绍了 vue 执行的 beforeCreate 阶段;
  2. 合并处理了组件 options ,父组件 options,平台初始化的 options,得到 vm.$options
  3. 初始化与生命周期相关的 Vue 实例的各种属性,如父子关系的 $parent 等,初始化 event 事件传递相关;
  4. 初始化了渲染相关的 Vue 实例上的函数,插槽等。