2.Vue初始化过程

Vue

/src/core/instance/index.js

1
2
3
4
5
6
7
8
9
10
11
12
import { initMixin } from './init'

// Vue 构造函数
function Vue (options) {
// 调用 Vue.prototype._init 方法,该方法是在 initMixin 中定义的
this._init(options)
}

// 定义 Vue.prototype._init 方法
initMixin(Vue)

export default Vue

vue 初始化时调用了 initMixin,为其绑定了 vue.prototype._init

在 new Vue(options) 时,会调用 Vue.prototype._init(options)

Vue.prototype._init

/src/core/instance/init.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
/**
* 定义 Vue.prototype._init 方法
* @param {*} Vue Vue 构造函数
*/
export function initMixin (Vue: Class<Component>) {
// 负责 Vue 的初始化过程
Vue.prototype._init = function (options?: Object) {
// vue 实例
const vm: Component = this
// 每个 vue 实例都有一个 _uid,并且是依次递增的
vm._uid = uid++

// a flag to avoid this being observed
vm._isVue = true
// 处理组件配置项
if (options && options._isComponent) {
/**
* 每个子组件初始化时走这里,这里只做了一些性能优化
* 将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码的执行效率
*/
initInternalComponent(vm, options)
} else {
/**
* 初始化根组件时走这里,合并 Vue 的全局配置到根组件的局部配置,比如 Vue.component 注册的全局组件会合并到 根实例的 components 选项中
* 至于每个子组件的选项合并则发生在两个地方:
* 1、Vue.component 方法注册的全局组件在注册时做了选项合并
* 2、{ components: { xx } } 方式注册的局部组件在执行编译器生成的 render 函数时做了选项合并,包括根组件中的 components 配置
*/
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
// 设置代理,将 vm 实例上的属性代理到 vm._renderProxy
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 初始化组件实例关系属性,比如 $parent、$children、$root、$refs 等
initLifecycle(vm)
/**
* 初始化自定义事件,这里需要注意一点,所以我们在 <comp @click="handleClick" /> 上注册的事件,监听者不是父组件,
* 而是子组件本身,也就是说事件的派发和监听者都是子组件本身,和父组件无关
*/
initEvents(vm)
// 解析组件的插槽信息,得到 vm.$slot,处理渲染函数,得到 vm.$createElement 方法,即 h 函数
initRender(vm)
// 调用 beforeCreate 钩子函数
callHook(vm, 'beforeCreate')
// 初始化组件的 inject 配置项,得到 result[key] = val 形式的配置对象,然后对结果数据进行响应式处理,并代理每个 key 到 vm 实例
initInjections(vm) // resolve injections before data/props
// 数据响应式的重点,处理 props、methods、data、computed、watch
initState(vm)
// 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
initProvide(vm) // resolve provide after data/props
// 调用 created 钩子函数
callHook(vm, 'created')

// 如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了 el 选项,就不需要再手动调用 $mount,反之,没有 el 则必须手动调用 $mount
if (vm.$options.el) {
// 调用 $mount 方法,进入挂载阶段
vm.$mount(vm.$options.el)
}
}
}

初始化过程做了什么

  • 处理组件配置项
    • 初始化根组件时进行了选项合并操作,将全局配置合并到根组件的局部配置上
      • mergeOptions
      • resolveConstructorOptions
    • 初始化每个子组件时做了一些性能优化,将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码的执行效率
      • initInternalComponent
  • 初始化组件实例的关系属性,比如 $parent、$children、$root、$refs 等
  • initLifecycle
  • 处理自定义事件
  • initEvents
  • 调用 beforeCreate 钩子函数
  • callHook(vm, ‘beforeCreate’)
  • 初始化组件的 inject 配置项,得到 ret[key] = val 形式的配置对象,然后对该配置对象进行浅层的响应式处理(只处理了对象第一层数据),并代理每个 key 到 vm 实例上
    • initInjections(vm)
  • 数据响应式,处理 props、methods、data、computed、watch 等选项
    • initState(vm)
  • 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
    • initProvide(vm)
  • 调用 created 钩子函数
    • callHook(vm, ‘created’)
  • 如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了 el 选项,就不需要再手动调用 ​$mount 方法,反之,没提供 el 选项则必须调用 $mount
  • 接下来则进入挂载阶段

源码层面解释面试题

  1. beforeCreate 时有数据响应式吗?

从源码上来看,在 vue 初始化时,会首先调用已经在 initMixin 时绑定在 vue 原型上的 _init 方法,在 _init 函数中,会按顺序执行很多个初始化函数以及生命周期调用函数,而 initState 是写在 callHook(vm, ‘beforeCreate’) 后面的,所以 beforeCreate 时还没有数据响应式。

参考

https://juejin.cn/post/6950084496515399717


2.Vue初始化过程
http://example.com/2022/10/21/2-Vue初始化过程/
Author
John Doe
Posted on
October 21, 2022
Licensed under