咱们知道 Vue 实例都有着相同的生命周期,并且你会发现随着对 Vue 的深刻使用,也老是离不开对生命周期的使用。html
下面咱们就一块儿来分析下 Vue 中生命周期都有哪些,有什么样的做用,以及咱们能够从中学到些什么。前端
生命周期是什么呢?按照 Vue 官网的描述 cn.vuejs.org/v2/guide/in…vue
大概能够理解为:从 Vue 实例的建立到更新、销毁的完整的一个过程,在这个过程当中 Vue 会执行一些对应的钩子函数,进而达到了用户更强大的自定义功能的能力。node
完整的生命周期,Vue 文档中也给出了 cn.vuejs.org/v2/guide/in…react
上边图示的生命周期钩子的含义分别为(这些也是最为重要的生命周期钩子):git
beforeCreate
在实例初始化以后,数据观测 (data observer) 和 event/watcher 事件配置以前被调用。created
在实例建立完成后被当即调用。在这一步,实例已完成如下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property 目前尚不可用。beforeMount
在挂载开始以前被调用:相关的 render 函数首次被调用。mounted
实例被挂载后调用,这时 el 被新建立的 vm.$el 替换了。若是根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。beforeUpdate
数据更新时调用,发生在虚拟 DOM 打补丁以前。这里适合在更新以前访问现有的 DOM,好比手动移除已添加的事件监听器。updated
因为数据更改致使的虚拟 DOM 从新渲染和打补丁,在这以后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,因此你如今能够执行依赖于 DOM 的操做。beforeDestroy
实例销毁以前调用。在这一步,实例仍然彻底可用。destroyed
实例销毁后调用。该钩子被调用后,对应 Vue 实例的全部指令都被解绑,全部的事件监听器被移除,全部的子实例也都被销毁。固然,Vue 中还存在其余的一些生命周期钩子:activated、deactivated、errorCaptured,这里咱们就不作详细介绍了。github
那这些生命周期钩子是在什么时机调用的,咱们一块儿来看下。web
咱们为了简化,以最简单的示例来看:express
var vm = new Vue({
data: {
msg: 'Hello World!'
},
render(h) {
return h('div', this.msg)
}
})
vm.$mount('#app')
复制代码
首先来看下,初始化的两个钩子 beforeCreate
和 created
。文件在 github.com/vuejs/vue/b… 核心在 _init 中(Vue 初始化的时候 会调用 _init):api
export function initMixin (Vue: Class<Component>) {
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)
// 初始化 render
initRender(vm)
// 1. 调用 beforeCreate 钩子
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
// 2. 调用 created 钩子
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)
}
}
}
复制代码
在 beforeCreate 以前,初始化了生命周期、事件、render:
// https://github.com/vuejs/vue/blob/v2.6.14/src/core/instance/lifecycle.js
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
// https://github.com/vuejs/vue/blob/v2.6.14/src/core/instance/events.js
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)
}
}
// https://github.com/vuejs/vue/blob/v2.6.14/src/core/instance/render.js
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}
复制代码
能够看出基本上就是初始化一些对应的模块须要用到的一些变量,处理一些初始值的case,能够先大概了解下,也约等于知道了在 beforeCreate 中能够访问哪些属性(能够访问不表明有效或者叫有正确的值)。
调用完 beforeCreate 钩子以后,作了三个初始化的操做:Injections、State、Provide。
Inject 和 Provide 是相对应的,也须要一块儿使用,相关文档能够参考 cn.vuejs.org/v2/api/#pro…
// https://github.com/vuejs/vue/blob/v2.6.14/src/core/instance/inject.js
export function initInjections (vm: Component) {
// 层层查找注入项,从 vm 的 _provided 值上取
const result = resolveInject(vm.$options.inject, vm)
if (result) {
// 刻意为之的 非响应式
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
// https://github.com/vuejs/vue/blob/v2.6.14/src/core/instance/state.js
// 初始化和状态相关的逻辑
// 主要包含了熟知的:props methods data computed watch
export 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)
}
}
// https://github.com/vuejs/vue/blob/v2.6.14/src/core/instance/inject.js
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
// 直接 call 便可,获得了 provided 的值,挂载在 _provided 上
// 和上边 inject 的逻辑对应上了
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
复制代码
上边有一个很重要的初始化状态相关的逻辑,依次初始化了 props、methods、data、computed 以及 watch,这些在 Vue 中是至关重要的组成部分,这个初始化的顺序也决定了咱们能够在对应的配置项中能够访问的内容。
咱们也一块儿来重点看(调整了顺序)下 github.com/vuejs/vue/b… :
// 初始化 props
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
// 只有根实例的 props 才会被转为 响应式对象 其余实例不会
// 由于非根的实例的 props 都是父组件传递下去的,理论上都已是响应式对象了
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
defineReactive(props, key, value, () => {
if (!isRoot && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
// 注意这里 转为响应式的 KV
// defineReactive 见 响应式 文章
defineReactive(props, key, value)
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
// 初始化 methods
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
if (typeof methods[key] !== 'function') {
warn(
`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
// 绑定上下文 这些方法的调用 上下文必定是 当前组件实例
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
// 初始化 data
function initData (vm: Component) {
let data = vm.$options.data
// 若是 data 是 function 则调用 获得 data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
// data 转换为响应式对象
observe(data, true /* asRootData */)
}
export function getData (data: Function, vm: Component): any {
// #7573 disable dep collection when invoking data getters
pushTarget()
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return {}
} finally {
popTarget()
}
}
const computedWatcherOptions = { lazy: true }
// 初始化 computed
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
// _computedWatchers 保存
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
// 取默认 getter 或者 用户自定义的
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
if (!isSSR) {
// create internal watcher for the computed property.
// 建立 computed 所对应的watcher
// computedWatcherOptions 的 lazy 为 true 默认不会执行 取值的操做
// lazy 基本为 computed 定制的
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// 注意这里的注释,若是在组件原型上已经定义的 computed 这里就不须要定义了
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
// 定义 computed,约等因而 vm 上定义一个 key 的值
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
} else if (vm.$options.methods && key in vm.$options.methods) {
warn(`The computed property "${key}" is already defined as a method.`, vm)
}
}
}
}
export function defineComputed ( target: any, key: string, userDef: Object | Function ) {
const shouldCache = !isServerRendering()
// 只须要关注 createComputedGetter
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
// 利用 defineProperty 定义一个值
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
// 返回了一个 getter 函数 当访问 vm[key] 的时候 会触发 getter 进而 调用这个函数
return function computedGetter () {
// computedWatcher
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// dirty ,取值
if (watcher.dirty) {
// evaluate 会调用 watcher 的 getter,即 用户自定义的 computed 声明(get)
watcher.evaluate()
}
// 添加依赖
if (Dep.target) {
watcher.depend()
}
// 返回值
return watcher.value
}
}
}
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
// 初始化 watch
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
// handler 能够是数组
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
// 建立watcher
createWatcher(vm, key, handler)
}
}
}
function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) {
// 对象模式 参数兼容
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
// vm 实例上的 方法
if (typeof handler === 'string') {
handler = vm[handler]
}
// 调用实例 $watch
return vm.$watch(expOrFn, handler, options)
}
export function stateMixin (Vue: Class<Component>) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
// 给原型定义 $data $props 属性,
// 这是一个技巧,给原型定义 getter 在全部的实例上均可以访问 且上下文是 实例
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
// 建立 watcher 实例
const watcher = new Watcher(vm, expOrFn, cb, options)
// 当即执行
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
// 返回值,一个 unwatch 的函数,用于取消 watcher 的监测
return function unwatchFn () {
watcher.teardown()
}
}
}
复制代码
以上基本上就完成了从 beforeCreate 到 created 钩子的全部的逻辑,重点就是和 state 相关处理,经过分析,咱们知道了 props、methods、data、computed 以及 watch 具体有怎样的逻辑实现,进而知道他们的做用。
回到 _init 的逻辑中,若是配置项存在 el,那么就会调用 vm.$mount(el)
去挂载实例。
这个逻辑在 github.com/vuejs/vue/b… 这里
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
复制代码
而这个 mountComponent 就来自于 github.com/vuejs/vue/b… 中:
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component {
vm.$el = el
// render 必须存在
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
// 传说中的 render watcher
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
// 在目前咱们的场景中 所走的逻辑
if (vm.$vnode == null) {
// 设置标识
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
复制代码
能够看到基本上,当即调用了 beforeMount 钩子。
后边就初始化了大名鼎鼎的 render watcher,每个实例都会对应一个 render watcher,做用也很明显,用于监控是否应该 rerender 的。
接着基本上就调用了 mounted 的钩子。
那咱们就详细看下这个 render watcher,传入的第二个参数,即 getter 就是 updateComponent,默认实例化 Watcher 的时候就会调用这个 getter。updateComponent 作了两个事情:
先来看 _render 的逻辑 github.com/vuejs/vue/b…
export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
// vm.$vnode 指向的是 parent vnode
vm.$vnode = _parentVnode
// render self
let vnode
try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
// 又见利用 JS 单线程的变量
currentRenderingInstance = vm
// 调用 options 中的 render 函数 获得 vnode 数据
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
// 执行完 设置 null
currentRenderingInstance = null
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
// 这样 vnode 的完整 tree 一层层就构建好了
vnode.parent = _parentVnode
return vnode
}
}
复制代码
再来看 _update 的逻辑 github.com/vuejs/vue/b…
// 在 lifecycleMixin 中 定义的
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
// 上一次的 DOM 元素(根),咱们这里就是挂载的容器元素
const prevEl = vm.$el
// 上一次调用 render() 获得的 vnode 数据
const prevVnode = vm._vnode
// 设置当前 active instance 且获得恢复上一个 active instance 的 函数
const restoreActiveInstance = setActiveInstance(vm)
// 设置 _vnode
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
// 初次渲染 咱们的逻辑,注意这里的 第一个参数是 vm.$el 咱们实际的容器元素
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
// 其余时间 就是 两次 vnode 数据进行对比 patch 以更新 DOM
vm.$el = vm.__patch__(prevVnode, vnode)
}
执行完 patch 就恢复 active instance
restoreActiveInstance()
// update __vue__ reference
// 干掉引用关系 释放
if (prevEl) {
prevEl.__vue__ = null
}
// 设置新的
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
// 若是高阶组件,即 <template><child>xx</child></template> 这种case
// parent 的 $el 同样须要更新
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
复制代码
这里相关逻辑有个巧妙的设计:
export let activeInstance: any = null
export function setActiveInstance(vm: Component) {
const prevActiveInstance = activeInstance
activeInstance = vm
return () => {
activeInstance = prevActiveInstance
}
}
复制代码
巧用闭包的特性,实现了一个相似于链表的感受,处理完当前的,直接恢复到上一个 active Instance,也就是根据当前的这个 老是可以恢复(找到)上一个,可是利用闭包,他们之间并不须要存在实体的关联。
接下来就是重点的 __patch__ 逻辑 github.com/vuejs/vue/b…
Vue.prototype.__patch__ = inBrowser ? patch : noop
复制代码
而 patch 就来自于 github.com/vuejs/vue/b…
import { createPatchFunction } from 'core/vdom/patch'
import baseModules from 'core/vdom/modules/index'
import platformModules from 'web/runtime/modules/index'
// the directive module should be applied last, after all
// built-in modules have been applied.
const modules = platformModules.concat(baseModules)
export const patch: Function = createPatchFunction({ nodeOps, modules })
复制代码
重点就是这个 createPatchFunction 所返回的 patch,来自 github.com/vuejs/vue/b…
// 较长,这里简化了
export function createPatchFunction (backend) {
// ...
return function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
// 此时 咱们的场景 isRealElement 为 true
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if (process.env.NODE_ENV !== 'production') {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
)
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode)
}
// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
// 建立新元素
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
// #6513
// invoke insert hooks that may have been merged by create hooks.componentInstance
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
}
复制代码
上述逻辑此时须要咱们重点关注:createElm
let creatingElmInVPre = 0
function createElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) {
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode)
}
vnode.isRootInsert = !nested // for transition enter check
// 建立组件 & 检查
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
if (process.env.NODE_ENV !== 'production') {
if (data && data.pre) {
creatingElmInVPre++
}
if (isUnknownElement(vnode, creatingElmInVPre)) {
warn(
'Unknown custom element: <' + tag + '> - did you ' +
'register the component correctly? For recursive components, ' +
'make sure to provide the "name" option.',
vnode.context
)
}
}
// 建立实际的 DOM 元素
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
// in Weex, the default insertion order is parent-first.
// List items can be optimized to use children-first insertion
// with append="tree".
const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
if (!appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
createChildren(vnode, children, insertedVnodeQueue)
if (appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
} else {
// 建立子元素
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// 插入元素
insert(parentElm, vnode.elm, refElm)
}
if (process.env.NODE_ENV !== 'production' && data && data.pre) {
creatingElmInVPre--
}
} else if (isTrue(vnode.isComment)) {
// 注释元素
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
// 文本元素
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (nodeOps.parentNode(ref) === parent) {
nodeOps.insertBefore(parent, elm, ref)
}
} else {
nodeOps.appendChild(parent, elm)
}
}
}
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(children)
}
// 循环建立子元素们
for (let i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
}
} else if (isPrimitive(vnode.text)) {
// 子元素 文本
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
}
}
复制代码
到这里结束,能够看到元素已经能够正常渲染出来了,mount 阶段最核心的就是根据虚拟 DOM 数据,进行 patch 获得实际的 DOM 元素,而后插入到页面中。
这样就构成了完整的挂载。
分析 mounted 中,咱们知道有这样一段逻辑,也就是咱们的渲染 watcher 部分
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
// 若是 mounted 了 还没销毁 就调用 beforeUpdate 钩子
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
复制代码
咱们知道在执行 updateComponent 的过程当中,会收集依赖,看当前执行 render() 过程当中依赖了哪些响应式数据,那么当数据变化的时候,会调用 Watcher 实例的 update 方法,这个在响应式文章有聊过,这里看下这个后续执行的事情:
// Watcher class
/** * Subscriber interface. * Will be called when a dependency changes. */
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
复制代码
针对于咱们的场景,就是调用 queueWatcher(this)
。这个 queueWatcher 的大概逻辑 github.com/vuejs/vue/b… :
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
复制代码
能够认为基本上加入到一个 watcher 的队列中,利用 nextTick 集中在下一个 tick 执行这些 watcher,即 flushSchedulerQueue
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
// 若是 watcher 有 before 钩子 则执行他们
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
// 调用 run
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}
// keep copies of post queues before resetting state
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// call component updated and activated hooks
callActivatedHooks(activatedQueue)
// 重点 调用 updated 钩子
callUpdatedHooks(updatedQueue)
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
复制代码
针对于咱们的场景,若是咱们修改了依赖,例如
vm.msg = 'New Hello World!'
复制代码
那么就会在下一个 tick 的时候,先执行 watcher 的 before:
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
// 若是 mounted 了 还没销毁 就调用 beforeUpdate 钩子
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
复制代码
此时这个时机,就调用了 beforeUpdate 钩子。而后继续执行 watcher 的 run() 方法,会再次进行调用 watche 的 getter 进行新的一轮的求值,此时,也就意味着会从新调用 updateComponent,再次执行 render & update,此时 render() 获得的新的 vnode 数据其实发生了变化,接着重点就是这个 update 操做,也就是最终执行的 patch 操做:
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
// 上一次调用 render() 获得的 vnode 数据
const prevVnode = vm._vnode
// 设置 _vnode
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
// 此时就是单纯的上一次的 vnode 和新的 vnode 进行 patch 处理!!
vm.$el = vm.__patch__(prevVnode, vnode)
}
// ...
}
复制代码
因此再次回到了 patch 逻辑,此时的 case,会执行这样一段逻辑:
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
// 重点 patch vnode
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
}
复制代码
sameVnode 的逻辑比较简单
function sameVnode (a, b) {
return (
a.key === b.key &&
a.asyncFactory === b.asyncFactory && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
isUndef(b.asyncFactory.error)
)
)
)
}
复制代码
最核心的判断是 key 以及 tag 是不是相同的,此时咱们的场景也是符合的,因此会进行下一个重点 patchVnode
function patchVnode ( oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly ) {
if (oldVnode === vnode) {
return
}
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode)
}
// 元素直接复用
const elm = vnode.elm = oldVnode.elm
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
// reset by the hot-reload-api and we need to do a proper re-render.
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
if (isUndef(vnode.text)) {
// children 对比
if (isDef(oldCh) && isDef(ch)) {
// 而后重点是这个 updateChildren
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(ch)
}
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
removeVnodes(oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
// 纯文本 case
nodeOps.setTextContent(elm, vnode.text)
}
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
复制代码
一块儿来看看这个 updateChildren
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
const canMove = !removeOnly
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh)
}
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
// 递归调用 patchVnode
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}
复制代码
基本按照一些策略(头和头、尾和尾、头和尾、头尾同步遍历),同层的 vnode 之间对比 check,详细文章能够参考 github.com/CommanderXL…
回到咱们的核心,会继续递归调用了 patchVnode,由于只有 msg 的纯文本信息发生了变动,因此执行 setTextContent 更新元素文本内容即完成了全部的 patch 更新 DOM 的操做。
到这里,由于 msg 的更新,引发 DOM 更新,整个过程已经完成了。
再次回到 flushSchedulerQueue 中,有一个重点
function flushSchedulerQueue () {
// ...
// 重点 调用 updated 钩子
callUpdatedHooks(updatedQueue)
// ...
}
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
// 判断是 渲染 watcher 且已经 mounted 且没有 destroy
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
}
复制代码
遍历队列中的 watcher,只要是渲染 watcher,那么就调用这个实例的 updated 钩子。
销毁的逻辑,基本要从 $destroy
开始,相关代码 github.com/vuejs/vue/b…
// 在lifecycleMixin中
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
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
// render watcher 须要卸载监听
if (vm._watcher) {
vm._watcher.teardown()
}
// 普通的 watch 和 computed 对应的 watchers
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
// 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
// invoke destroy hooks on current rendered tree
// 执行 patch 第二个参数 新的 vnode 为 null
vm.__patch__(vm._vnode, null)
// fire destroyed hook
callHook(vm, 'destroyed')
// turn off all instance listeners.
// 事件
vm.$off()
// remove __vue__ reference
// 取消相关引用 释放内存 以防内存泄漏
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
复制代码
能够看到逻辑仍是比较清晰的,有一个防重处理,大概作的核心事情:
__patch__
看起来仍是须要深刻看下此时的 __patch__
逻辑细节:
// 在 createPatchFunction 中的部分逻辑
return function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
// ...
}
function invokeDestroyHook (vnode) {
let i, j
const data = vnode.data
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
}
if (isDef(i = vnode.children)) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j])
}
}
}
复制代码
能够看到在这种状况下,基本上就是遍历 vnode,而后依次调用对应 vnode 上的对应的销毁 hook。
此时也能够看出,针对于咱们的场景,Vue 根实例销毁了,并不会移除 DOM 元素,这些元素仍是会保持原样。
这个在官网上其实也有涉及,生命周期钩子的最核心的做用就是让用户在不一样时机能够自定义不一样的行为。
这个问题,能够理解为:怎么样可让开发者更方便、更优雅的定义自定义逻辑?
那反过来思考,都在一个函数中或者在一个地方去写自定义逻辑是否是也是能够的?固然能够,可是可能引起什么样的问题呢?
那针对于这些问题,采用定义好的生命周期钩子,开发者就能够很方便的在不一样的状态(阶段)执行本身想要的逻辑,很好的组织了自定义代码,同时各个的职责很清晰。
额外的,这是一个完整的统一的生命周期,对于开发者理解实例的执行逻辑也是很重要的,实现自定义逻辑的时候,能够作到心中有图。
固然也是有成本的,须要理解这些生命周期,以及对应的生命周期能够作的事情。可是,若是没有的话,只会让这个成本变得更大。
复杂度自己是不会消失的,只能转移他们。
相信你经过上边的分析大概知道了 Vue 中几个核心的生命周期钩子以及他们的实现上作了什么样的事情。能够看出仍是比较复杂的,有不少细节咱们目前都是没有涉及到的,尤为是和组件相关的,这个会留到组件的分析部分。
那经过以上的分析,咱们能够学到哪些东西吗?能够有什么样的收获?
生命周期,就是一个个有序的阶段,不一样的阶段能够作不一样的事情,这些阶段构成了一个完整的全局或者总体。视角的话,是站在全局来看的,是一种自顶向下拆分设计系统的一种很好的方法。
这些不一样的阶段之间,区分又是明显的,每一个阶段或者步骤都很简单、易懂。
固然,咱们分析了 Vue 最核心的几个生命周期钩子,他们分别在什么时机调用的,作了哪些事情,相信你已经有了大概的了解了。
这里的重点实际上是钩子,钩子自己就是一个模板方法的模式的应用,常常和插件化一块儿出现。钩子的最大好处就是提供了强大的用户自定义能力,且很好的实现了隔离和扩展。也基本上意味着,能够保持较好的可维护性。
钩子自己也是一种 DI依赖注入 的思想,由上层使用者决定注入什么样的内容去执行。
上边的分析 instance 模块相关能力的时候,咱们能够发现 Vue 中去扩展能力的方式是按照 Mixin 混入的这种方式组织的,github.com/vuejs/vue/b…
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
复制代码
经过这种方式实现了模块的拆解,各个模块的职责比较清晰,共同混入各类能力组成了最终的完整功能。
在 init 模块中,也是从其余各个模块中引入了对应的 init 方法依此来执行初始化的功能
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
// ...
/* 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')
// ...
}
}
复制代码
经过这种组合的方式,去拆分咱们的功能,这里边仍是须要咱们根据实际的场景,去好好分析模块之间的组织关系,进而合理拆分和组合他们。作到模块职责聚合,达到各司其职的目标。
Vue 文档中专门花了一个篇幅来说计算属性 computed 和侦听器 watch,也讲明了为啥须要这两个特性,他们分别解决了什么场景的事情,也是基于响应式系统之上提供的十分好用的特性,详情能够看 cn.vuejs.org/v2/guide/co…
相信有过 Vue 实践的同窗必定用了很多这两个特性,能够说在咱们实际开发逻辑的过程当中,给了咱们不少的便利,并且至关直观,易于理解。
滴滴前端技术团队的团队号已经上线,咱们也同步了必定的招聘信息,咱们也会持续增长更多职位,有兴趣的同窗能够一块儿聊聊。