初始化
new Vue()
这个阶段做的第一件事就是 new
创建一个 Vue
实例对象
new Vue({
el: '#app',
render: h => h(App)
})
源码地址:src/core/instance/index.js
function Vue (options) {
...
this._init(options)
}
initMixin(Vue)
_init()
源码地址:src/core/instance/init.js
主要流程:
- 合并配置,把一些内置组件
<component />
、<keep-alive />
、<transition />
、directive
、filter
等合并到 Vue.optoins - 调用一些初始化函数
- 触发生命周期钩子,
beforeCreate
和created
- 最后调用
$mount
挂载,进入下一阶段
export function initMixin(Vue: typeof Component) {
// 在原型上添加 _init 方法
Vue.prototype._init = function (options?: Record<string, any>) {
// 保存当前实例
const vm: Component = this
...
// merge options
if (options && options._isComponent) {
// 把子组件依赖父组件的 props, listeners 挂载到 options 上,并指定组件的 $options
initInternalComponent(vm, options as any)
} else {
// 把传进来的 options 和当前构造函数和父级的 options 进行合并,并挂载到原型上
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor as any),
options || {},
vm
)
}
...
// expose real self
vm._self = vm
initLifecycle(vm) // 初始化实例的属性、数据:$options, $children, $refs, $root, _watcher ...等
initEvents(vm) // 初始化事件:$on, $off, $emit, $once
initRender(vm) // 初始化渲染:render, mixin
callHook(vm, 'beforeCreate')
initInjections(vm) // 初始化 inject
initState(vm) // 初始化组件数据:props、data、methods、watch、computed
initProvide(vm) // 初始化 provide
callHook(vm, 'created') // 调用声明周期钩子函数
...
if (vm.$options.el) {
// 如果传了 el 就会调用 $mount 进入模板编译和挂载阶段
// 如果没有传就需要手动执行 $mount 才会进入下一阶段
vm.$mount(vm.$options.el)
}
}
}
模板编译阶段
$mount
源码地址:dist/vue.js - 11927
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
...
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
...
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (__DEV__) {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
// @ts-expect-error
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (__DEV__ && config.performance && mark) {
mark('compile')
}
const { render, staticRenderFns } = compileToFunctions(
template,
{
outputSourceRange: __DEV__,
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
},
this
)
options.render = render
options.staticRenderFns = staticRenderFns
...
}
}
return mount.call(this, el, hydrating)
}
$mount
主要就是判断要不要编译,使用哪一个模板编译
挂载阶段
这个阶段主要做的事有两件
- 根据 render 返回的虚拟 DOM 创建真实的 DOM 节点,插入到视图中,完成渲染
- 对模板中数据或状态做响应式处理
mountComponent()
这里主要做的就是
- 调用钩子函数
beforeMount
- 调用
_update()
方法对新老虚拟 DOM 进行patch
以及new Watcher
对模板数据做响应式处理 - 再调钩子函数
mounted
源码地址:src/core/instance/lifecycle.js - 139
export function mountComponent(
vm: Component,
el: Element | null | undefined,
hydrating?: boolean
): Component {
vm.$el = el
// 判断有没有渲染函数 render
if (!vm.$options.render) {
// 如果没有,默认创建一个注释节点
vm.$options.render = createEmptyVNode
}
callHook(vm, 'beforeMount')
let updateComponent
updateComponent = () => {
// 调用 _update 对 render 返回的虚拟 DOM 进行 patch(也就是 Digg)到真实 DOM,这里是首次渲染
vm._update(vm._render(), hydrating)
}
const watcherOptions: WatcherOptions = {
// 触发更新时,会在更新之前调用
before() {
// 判断 DOM 是否是挂载状态,就是说首次渲染和卸载的时候不执行
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}
// 为当前组件实例设置观察者,监控 updateComponent 函数得到的数据
new Watcher(
vm,
updateComponent,
noop,
watcherOptions,
true /* isRenderWatcher */
)
hydrating = false
// 没有旧 vnode,说明是首次渲染
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
Watcher
_update()
源码地址:src/core/instance/lifecycle.js - 62
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el // 当前组件根节点
const prevVnode = vm._vnode // 旧的 vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode // 更新旧的 vnode
// 如果是首次渲染
if (!prevVnode) {
// 对 vnode 进行 patch 创建真实的 DOM,挂载到 vm.$el 上
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// 修改的时候,进行新旧 vnode 对比
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// 删除旧根节点的引用
if (prevEl) {
prevEl.__vue__ = null
}
// 更新当前根节点的引用
if (vm.$el) {
vm.$el.__vue__ = vm
}
// 更新父级的引用
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
销毁阶段
$destroy()
- 调用生命周期钩子函数
beforeDestroy
- 从父组件中删除当前组件
- 移除当前组件内的所有观察者(依赖追踪),删除数据对象的引用,删除虚拟 DOM
- 调用生命周期钩子函数
destroyed
- 关闭所有事件监听,删除当前跟组件的引用,删除父级的引用
源码地址:src/core/instance/lifecycle.js - 100
Vue.prototype.$destroy = function () {
const vm: Component = this
// 如果实例正在被销毁的过程中,直接跳过
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy')
// 更新销毁状态
vm._isBeingDestroyed = true
// remove self from parent
const parent = vm.$parent
// 如果父级存在,并且父级没有在被销毁,并且不是抽象组件而是真实组件(<keep-alive>就是抽象组件,它的 abstract 为 true)
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
// 从父组件中删除
remove(parent.$children, vm)
}
// teardown scope. this includes both the render watcher and other
// watchers created
vm._scope.stop()
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// call the last hook...
vm._isDestroyed = true
// 删除实例的虚拟 DOM
vm.__patch__(vm._vnode, null)
// fire destroyed hook
callHook(vm, 'destroyed')
// turn off all instance listeners.
vm.$off()
// 删除当前跟组件的引用
if (vm.$el) {
vm.$el.__vue__ = null
}
// 删除父级的引用
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
什么时候调用 $destroy()
- 新的 vnode 不存在,旧的 vnode 存在时,触发卸载旧 vnode 对应组件的 destroy
- 新的 vnode 根节点被修改时,调用旧 vnode 对应组件的 destroy
- 新旧 vnode 对比结束后,调用旧 vnode 对应组件的 destroy