Vue 初始化流程分析 (src/core/instance/init.js)
这个文件定义了 Vue 实例的初始化过程,是整个 Vue 生命周期的起点。它实现了从用户创建 Vue 实例到实例准备就绪的全部流程。
文件结构概览
javascript
import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
// ...初始化逻辑
}
}
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
// ...内部组件优化逻辑
}
export function resolveConstructorOptions (Ctor: Class<Component>) {
// ...解析构造函数选项的逻辑
}
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
// ...解析修改过的选项的逻辑
}
详细分析
1. initMixin - 向 Vue 原型添加 _init 方法
javascript
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
// ...性能测量代码...
// a flag to avoid this being observed
vm._isVue = true
// 合并选项
if (options && options._isComponent) {
// 优化内部组件实例化
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// 设置渲染代理
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// 暴露自身
vm._self = vm
// 初始化各个部分
initLifecycle(vm) // 生命周期
initEvents(vm) // 事件
initRender(vm) // 渲染
callHook(vm, 'beforeCreate') // 调用 beforeCreate 钩子
initInjections(vm) // 注入项(在 data/props 之前)
initState(vm) // 状态(data、props、computed、watch 等)
initProvide(vm) // 提供项(在 data/props 之后)
callHook(vm, 'created') // 调用 created 钩子
// ...性能测量代码...
// 如果提供了 el 选项,则自动挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
_init 方法的主要流程:
- 分配 UID:每个实例都有一个唯一的标识符
- 性能测量:开发环境下可能会进行性能测量
- 设置 _isVue 标志:防止被响应式系统观察
- 合并选项 :Vue 选项合并详解
- 对于内部组件,使用优化的
initInternalComponent
方法 - 对于普通实例,使用
mergeOptions
合并构造函数选项和用户选项
- 对于内部组件,使用优化的
- 设置渲染代理:用于运行时警告和错误处理
- 初始化实例的各个部分 ,顺序非常重要:
- initLifecycle :设置 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a r e n t 、 parent、 </math>parent、root、$children 等
- initEvents:设置父组件传递的事件监听器
- initRender:设置 $createElement 方法
- callHook(beforeCreate):调用 beforeCreate 钩子
- initInjections:解析注入的数据
- initState:处理 props、methods、data、computed、watch
- initProvide:提供数据给后代组件
- callHook(created):调用 created 钩子
- 自动挂载:如果设置了 el 选项,则调用 $mount 方法
2. initInternalComponent - 内部组件优化
javascript
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
这个函数是对内部组件实例化的一种优化:
- 创建一个继承自构造函数选项的选项对象
- 直接设置特定属性,而不是通过合并选项(更快):
- parent:父组件实例
- _parentVnode:父虚拟节点
- propsData:传递的 props 数据
- _parentListeners:父组件的事件监听器
- _renderChildren:子节点
- _componentTag:组件标签
- render 和 staticRenderFns:如果有的话
3. resolveConstructorOptions - 解析构造函数选项
javascript
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
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.super
),则递归解析父类的选项 - 检查父类选项是否变化:
- 如果变化,更新缓存的父类选项
- 检查是否有后期修改的选项
- 更新扩展选项
- 重新合并选项
- 在组件集中注册自身(如果有 name)
4. resolveModifiedOptions - 解析修改过的选项
javascript
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
const latest = Ctor.options
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = latest[key]
}
}
return modified
}
这个函数检测构造函数的选项自封存后是否有变化:
- 比较最新的选项(
Ctor.options
)和封存的选项(Ctor.sealedOptions
) - 如果某个键的值发生变化,则将其添加到
modified
对象中 - 返回修改过的选项,如果没有修改则返回
undefined
初始化流程总结
Vue 初始化流程清晰体现了框架的设计思想:
- 组件化:通过 initInternalComponent 优化内部组件性能
- 渐进式:各个部分独立初始化,可以灵活组合
- 响应式:通过 initState 建立数据响应系统
- 生命周期:在恰当的时机调用各个生命周期钩子
特别值得注意的是初始化的顺序:
rust
创建实例 -> 初始化生命周期 -> 初始化事件 -> 初始化渲染
-> beforeCreate 钩子 -> 初始化注入项 -> 初始化状态
-> 初始化提供项 -> created 钩子 -> 挂载(如果有 el)
这个顺序决定了在各个生命周期钩子中可以访问到哪些属性:
- beforeCreate:实例创建后,数据观测和事件配置之前,此时不能访问 data、computed 等
- created:数据观测、计算属性、方法和事件配置完成后,此时可以访问 data 等,但尚未挂载 DOM
Vue 的初始化过程是理解整个框架工作原理的关键入口,它建立了实例的基本结构和响应式系统,为后续的挂载和更新过程奠定了基础。
Vue 初始化过程疑难解答
1. 内部组件与普通实例的区别
内部组件指的是Vue组件系统内部创建的组件实例,具有以下特点:
- 是通过Vue内部组件创建流程创建的,而非用户直接通过
new Vue()
创建 - 其选项中会有
_isComponent: true
标志 - 通常是在虚拟DOM的patch过程中被创建,作为其他组件的子组件
- 通过
createComponentInstanceForVnode
函数创建,位于src/core/vdom/create-component.js
普通实例指的是:
- 直接通过
new Vue(options)
创建的实例 - 通常是应用的根实例或独立实例
- 不包含
_isComponent
标志 - 需要完整的选项合并过程
示例代码对比:
javascript
// 普通实例 - 由用户直接创建
const app = new Vue({
el: '#app',
data: { message: 'Hello' }
})
// 内部组件 - 在模板中使用组件,Vue内部会创建组件实例
// <div id="app">
// <my-component></my-component>
// </div>
Vue.component('my-component', {
template: '<div>Component</div>'
})
2. initInternalComponent 的优化原理
initInternalComponent
函数优化了组件实例的选项处理过程,具体优化点:
优化前(使用mergeOptions):
javascript
// 完整的选项合并过程
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
mergeOptions
函数需要:
- 遍历所有选项属性
- 对每个属性使用特定的合并策略
- 处理生命周期钩子函数数组合并
- 递归合并嵌套对象
- 解析并合并各种资源(components, directives, filters)
优化后(使用initInternalComponent):
javascript
const opts = vm.$options = Object.create(vm.constructor.options)
// 直接设置特定属性
opts.parent = options.parent
opts._parentVnode = parentVnode
// ...其他直接赋值
为什么更快:
- 原型继承替代深度拷贝 :通过
Object.create()
创建一个原型链指向构造函数选项的新对象,避免了属性的复制和合并 - 直接属性赋值:直接设置特定属性,避免了选项合并策略的判断和处理
- 避免递归处理:不需要递归处理嵌套选项
- 减少对象创建:减少了临时对象的创建
性能差异: 对于一个包含数百个组件的大型应用,这种优化可能会带来显著的性能提升,因为组件实例化是频繁操作。Vue测试显示,这种优化可以使组件初始化速度提高约20%-30%。
3. 封存的选项(Ctor.sealedOptions)的来源
Ctor.sealedOptions
是在创建组件构造函数时"密封"(保存)的选项,用于后续检测选项是否被修改。
封存选项的来源
这个封存发生在Vue.extend
方法中,位于src/core/global-api/extend.js
文件:
javascript
Vue.extend = function (extendOptions: Object): Function {
// ...创建子类构造函数Sub
// 缓存构造函数选项
Sub.options = mergeOptions(
Super.options,
extendOptions
)
// 封存选项 - 创建选项的副本
Sub.sealedOptions = extend({}, Sub.options)
// ...
return Sub
}
封存的目的
- 检测选项变化:在组件更新时,可以通过比较当前选项和封存选项来检测哪些选项被修改
- 优化更新:如果组件继承链上的选项发生变化,需要重新解析和合并选项
使用场景示例
当使用Vue.mixin
全局混入选项后,所有已创建的组件构造函数需要更新其选项。通过比较sealedOptions
和当前选项,Vue可以知道哪些选项需要更新:
javascript
// 创建组件构造函数后
const MyComponent = Vue.extend({ data: {...} })
// 之后全局混入新选项
Vue.mixin({
methods: {
newMethod() { /* ... */ }
}
})
// 当再次使用MyComponent时,Vue会检测到Super.options与缓存的不同,
// 需要重新合并选项,但只需要合并变化的部分
这种机制确保了即使在应用运行时动态修改组件选项,Vue依然能够正确地处理这些变化,同时避免不必要的重复合并操作。