Vue的初始化过程都做了什么?
从构造函数前面打断点,然后进行一步步的调试:
首先会进入init.js里面执行Vue构造函数。init函数之前会进行一下判断:即init之前的代码作用为:检测在使用 Vue 构造函数时是否使用了 new 关键字进行实例化。它主要用于开发环境下的警告提示,之后开始执行init函数正式进入Vue初始化过程。这个options就是用户传入的合并选项在这里,即
{data:{msg:'123'},
methods:{
eat(){console.log('eat')},
drink(){console.log('drink')}}}
下面让我们进入init方法,给出具体的源代码:rt
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
这个方法是vue实例的初始化方法,它会在创建一个 Vue 实例时被调用。让我们来进一步展开这个方法具体做了什么。
- 首先,给当前实例设置 _uid 属性,用于标识唯一的 Vue 实例。
- 创建变量startTag 和 endTag 是用于标记解析模板中的标签的起始和结束位置的变量。这是为了创建ast语法树用的,然后设置实例的 _isVue 属性为 true,表示该对象是一个 Vue 实例。
- 判断该组件是不是内部组件,如果是内部组件则调用initInternalComponent(vm, options)进行组件合并,否则调用mergeOptions进行组件合并,至于为什么这么做,就是为了提高性能,下面是详细的介绍:
当创建一个组件实例时,会调用 _init 方法进行初始化。在初始化过程中,会检查 options 对象是否存在且具有 _isComponent 属性。如果存在,并且 _isComponent 的值为 true,则表示当前组件是一个内部组件(可以想象成局部注册的组件)。
针对内部组件的实例化,由于内部组件的选项合并不需要特殊处理,而且动态选项合并的性能较低,因此可以进行优化。在这种情况下,会调用 initInternalComponent 方法,将内部组件实例和选项对象作为参数传递给该方法。
initInternalComponent 方法负责完成内部组件的初始化,包括对选项的处理、状态的初始化等。通过将内部组件的初始化逻辑单独提取为一个方法,可以提高内部组件实例化的性能,并避免对内部组件的选项进行不必要的处理。
总而言之,上述代码片段中的逻辑用于优化内部组件的实例化过程,通过调用 initInternalComponent 方法进行内部组件的初始化。这样可以提高内部组件实例化的性能,并简化内部组件选项的处理过程。
下面是处理内部组件合并的源代码:
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
}
}
这个函数的意思:子组件的选项会通过合并方式添加到子组件实例的选项中,以便子组件能够继承和访问父组件的选项。通过这种方式,子组件可以访问父组件的属性、方法、生命周期钩子等。
即initInternalComponent 方法主要用于局部注册的组件实例化时的选项合并,mergeOptions 方法用于全局注册和局部注册的组件选项的合并。合并的选项主要为components,directives,methods,computed,props等等,还有子组件的钩子函数等等。合并的结果是父组件的选项优先级高,子组件的选项会被合并到父组件的选项中,形成最终的组件选项对象
4. 接下来执行initLifecycle方法,它的作用就是初始化组件实例的生命周期相关属性:例如:
初始化 $parent 属性:将父组件实例赋值给 $parent,用于在组件实例中可以访问到其父组件实例。
初始化 $root 属性:将根组件实例赋值给 $root,用于在组件实例中可以访问到根组件实例。
初始化 $children 属性,初始化 $refs 属性
初始化 _watcher 属性
初始化 _inactive 属性
初始化 _isMounted 属性:将 false 赋值给 _isMounted,表示组件实例当前没有挂载到 DOM 中。
初始化 _isDestroyed 属性
初始化 _isBeingDestroyed 属性
5. initEvents 方法初始化组件实例的事件相关属性和方法。它主要完成以下几个任务:
初始化 _events 属性:将空对象赋值给 _events,用于存储组件实例的事件监听器。
初始化 _hasHookEvent 属性:将 false 赋值给 _hasHookEvent,表示组件实例当前没有绑定任何钩子函数的事件监听器。
将组件构造函数选项中的事件监听器合并到组件实例的 _events 属性中。这样可以在组件实例中通过
o
n
、
on、
on、off、$once 等方法进行事件的订阅和取消订阅。
将组件实例的父组件的事件监听器合并到组件实例的 _events 属性中。这样可以在组件实例中访问到其父组件的事件监听器。
总之,通过执行 initEvents 方法,Vue 组件实例可以进行事件的订阅和触发,从而实现组件间的通信和交互。
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
6.执行initRender 方法,Vue 组件实例具备了渲染功能所需的基本属性和方法(_vnode,$slots等等)。它为组件的模板渲染提供了必要的支持,使组件能够生成对应的虚拟节点,并进行相应的渲染和更新操作。
7.调用beforeCreate钩子函数,注意,beforeCreate 钩子函数在组件实例创建之前执行,此时组件的数据、计算属性、方法等还未初始化,但是可以在这个阶段进行一些初始化配置或执行其他操作。还有就是,选项合并是关于组件选项对象的处理,而初始化是关于组件实例的内部状态的设置和准备。
8.initInjections 方法的主要功能是处理组件实例的注入选项(inject)并将其添加到组件实例的 _provided 属性中。它会在组件实例化的过程中被调用,确保组件实例可以直接访问注入的数据。
9.执行 initState 方法,它的作用是初始化组件实例的状态,状态包括组件的数据(data)、计算属性(computed)、观察者(watchers)等,下面给出代码,按这个顺序进行的初始化:
function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
10.initProvide方法,用于初始化组件的Provide选项,其允许一个组件向其所有的子组件提供数据或者方法,这样子组件就可以通过inject来访问这些数据和方法。
11…调用created钩子函数.组件实例刚好被创建然后立即调用该方法,表明此时组件的数据观测 和事件机制都已经初始化完成。
12.模板渲染:在 created 钩子函数之后,Vue.js 将根据组件的虚拟 DOM 结构开始渲染组件的模板。这个过程涉及以下步骤:
将组件的虚拟 DOM 与实际的 DOM 进行比对,找出需要更新的部分。
将需要更新的部分进行 DOM 操作,实现视图的更新。
13.调用beforeMount钩子函数
14.模板挂载:在 beforeMount 钩子函数之后,Vue.js 开始将组件的虚拟 DOM 渲染到实际的 DOM 中。这个过程包括以下步骤:
将组件的虚拟 DOM 插入到实际 DOM 中的指定位置。
执行实际的 DOM 操作,如添加、修改或删除 DOM 元素。