# Vue2.x 的生命周期
前言
vue 中不同的时机执行不同的生命周期:
- 1.首次加载依次执行
 - 2.数据更新依次执行
 - 3.数据销毁依次执行
 - 4.组件报错时执行
 
先看一个问题引发的思考:
结果
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 beforeUpdate updated nextTick1 nextTick2 
nextTick1 、 nextTick2 为微任务异步执行,最后打印的,msg 修改引起的更新,所以 beforeUpdate、updated 生命周期同步执行?
目录
# 1.首次加载
# 1.1 beforeCreate
- Vue
 
function Vue(options) {
  this._init(options)
}
 2
3
- 初始化相关逻辑
 
Vue.prototype._init = function (options?: Record<string, any>) {
  ...
  callHook(vm, "beforeCreate", undefined, false /* setContext */)
  initInjections(vm)
  initState(vm)
  initProvide(vm)
}
 2
3
4
5
6
7
- initInjections 对 inject 进行初始化
 - initState 对 methods、data、computed、watch 进行初始化
 
结论
由此可知 beforeCreate不能访问 methods、data、computed、watch 数据
使用场景
一般插件会在此生命周期混入一些逻辑处理,如:vuex 、vue-router
# 1.2 created
Vue.prototype._init = function (options?: Record<string, any>) {
  ...
  initInjections(vm)
  initState(vm)
  initProvide(vm)
  callHook(vm, "created")
}
 2
3
4
5
6
7
结论
由此可知 created能访问 methods、data、computed、watch 数据
使用场景
可以用来处理接口请求相关的逻辑
# 1.3 beforeMount
Vue.prototype._init = function (options?: Record<string, any>) {
  ...
  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}
 2
3
4
5
6
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
 2
3
4
5
6
7
- 重写$mount
 
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  return mount.call(this, el, hydrating)
}
 2
3
4
5
6
7
8
export function mountComponent(
  vm: Component,
  el: Element | null | undefined,
  hydrating?: boolean
): Component {
  ...
  callHook(vm, "beforeMount")
}
 2
3
4
5
6
7
8
结论
由此可知没有 el 属性,没有 vm.$mount,beforeMount、mounted 不会调用,手动挂载 vm.$mount("#app")后可以恢复正常
new Vue({
  beforeCreate: function () {
    console.log("调用了beforeCreate")
  },
  created: function () {
    console.log("调用了created")
  },
  beforeMount: function () {
    console.log("调用了beforeMount")
  },
  mounted: function () {
    console.log("调用了mounted")
  },
})
// 输出结果
// 调用了beforeCreate
// 调用了created
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
使用场景
可以处理 dom 挂载前业务逻辑
# 1.4 mounted
- 渲染 watcher
 
export function mountComponent(
  vm: Component,
  el: Element | null | undefined,
  hydrating?: boolean
): Component {
  ...
  updateComponent = () => {
    vm._update(vm._render(), hydrating)
  }
  const watcherOptions: WatcherOptions = {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, "beforeUpdate")
      }
    },
  }
  new Watcher(
    vm,
    updateComponent,
    noop,
    watcherOptions,
    true /* isRenderWatcher */
  )
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, "mounted")
  }
}
 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
- Watcher
 
export default class Watcher implements DepTarget {
  constructor(
    vm: Component | null,
    expOrFn: string | (() => any),
    cb: Function,
    options?: WatcherOptions | null,
    isRenderWatcher?: boolean
  ) {
    this.getter = expOrFn
    this.value = this.lazy ? undefined : this.get()
  }
  get() {
    value = this.getter.call(vm, vm)
  }
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
根组件直接会走 mounted 生命周期
- 组件生命周期 init
 
const componentVNodeHooks = {
  insert(vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, "mounted")
    }
  },
}
 2
3
4
5
6
7
8
9
非根组件走组件自身的 hooks 触发 mounted:render-->组件 vnode--> patch-->创建组件-->实例化组件(内部调用 extend 相当于 new Vue)-- invokeInsertHook --> queue[i].data.hook.insert(queue[i])
结论
由此可知根组件和非根组件执行 mounted 的方式是不一样的,异步组件走非根组件逻辑,beforeCreate created beforeMount mounted 异步执行,其他情况下 beforeCreate created beforeMount mounted 同步执行
使用场景
可以处理 dom 相关的业务逻辑
# 1.5 父子组件单独加载案例
初始化
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 parent-beforeCreate parent-created parent-beforeMount parent-mounted 创建子组件
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 步骤 5 步骤 6 步骤 7 父组件 parent-beforeUpdate parent-updated 子组件 child-beforeCreate child-created child-beforeMount child-mounted child-activated 
# 1.6 父子组件一起加载案例
初始化
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 步骤 5 步骤 6 步骤 7 步骤 8 步骤 9 父组件 parent-beforeCreate parent-created parent-beforeMount parent-mounted 子组件 child-beforeCreate child-created child-beforeMount child-mounted child-activated 
# 2.数据更新
# 2.1 beforeUpdate
- 渲染 watcher
 
export function mountComponent(
  vm: Component,
  el: Element | null | undefined,
  hydrating?: boolean
): Component {
  ...
  updateComponent = () => {
    vm._update(vm._render(), hydrating)
  }
  const watcherOptions: WatcherOptions = {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, "beforeUpdate")
      }
    },
  }
  new Watcher(
    vm,
    updateComponent,
    noop,
    watcherOptions,
    true /* isRenderWatcher */
  )
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- watcher 类,响应式数据第一次访问时进行依赖收集,会将渲染 watcher 存储起来,数据有修改时派发更新
 
export default class Watcher implements DepTarget {
  constructor(
    vm: Component | null,
    expOrFn: string | (() => any),
    cb: Function,
    options?: WatcherOptions | null,
    isRenderWatcher?: boolean
  ) {
    if ((this.vm = vm) && isRenderWatcher) {
      vm._watcher = this
    }
    if (options) {
      this.before = options.before
    }
  }
  addDep(dep: Dep) {
    ...
    dep.addSub(this)
  }
  update() {
    ...
    queueWatcher(this)
  }
  run() {
    ...
    this.cb.call(this.vm, value, oldValue) // cb 就是 updateComponent
  }
}
 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
- 响应式数据
 
Object.defineProperty(obj, key, {
  get: function reactiveGetter() {
    ...
    dep.depend() // 依赖收集
  },
  set: function reactiveSetter(newVal) {
    ...
    dep.notify() // 派发更新
  },
})
 2
3
4
5
6
7
8
9
10
- Dep
 
export default class Dep {
  addSub(sub: DepTarget) {
    this.subs.push(sub)
  }
  depend(info?: DebuggerEventExtraInfo) {
    ...
    Dep.target.addDep(this) //Dep.target 就是 Watcher
  }
  notify(info?: DebuggerEventExtraInfo) {
    ...
  for (let i = 0, l = subs.length; i < l; i++) {
    const sub = subs[i]
      sub.update()
    }
  }
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- queueWatcher
 
let waiting = false
export function queueWatcher(watcher: Watcher) {
  ...
  if (!waiting) {
    waiting = true
    nextTick(flushSchedulerQueue) // 异步任务 nextTick
  }
}
 2
3
4
5
6
7
8
- flushSchedulerQueue
 
function resetSchedulerState() {
  waiting = flushing = false
}
function flushSchedulerQueue() {
  flushing = true
  let watcher, id
  queue.sort(sortCompareFn)
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    watcher.before() // 生命周期函数 beforeUpdate
    watcher.run() // 更新模板
  }
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()
  resetSchedulerState()
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)
  cleanupDeps()
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
结论
由此可知 beforeUpdate 通过 nextTick 异步执行
使用场景
可以处理 dom 更新前 相关的业务逻辑
# 2.2 updated
- callUpdatedHooks
 
function callUpdatedHooks(queue: Watcher[]) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    callHook(vm, "updated") // 生命周期函数
  }
}
 2
3
4
5
6
7
8
结论
由此可知 Vue 的 updated 钩子是异步的。Vue 在 DOM 更新完成后,会通过异步队列来执行 updated 钩子函数,但不保证它会在 DOM 更新完成后的下一个事件循环中同步触发。这意味着,如果你在 updated 钩子中需要访问更新后的 DOM 元素,你应该使用 this.$nextTick 来确保你的代码在 DOM 更新完成后执行
- 一般不要操作数据 可能会导致死循环
 
使用场景
# 2.3 父子组件单独加载案例
创建子组件
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 步骤 5 步骤 6 步骤 7 步骤 8 父组件 parent-beforeUpdate parent-updated 子组件 child-beforeCreate child-created child-beforeMount child-mounted child-activated 
# 2.4 父子组件一起加载案例
更新子组件数据
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 parent-beforeUpdate parent-updated 子组件 child-beforeUpdate child-updated 
# 3.数据销毁
# 3.1 beforeDestroy
组件中使用 this.$destroy() 手动卸载组件
  Vue.prototype.$destroy = function () {
    ...
    callHook(vm, 'beforeDestroy')
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    vm.__patch__(vm._vnode, null)
  }
 2
3
4
5
6
7
8
9
结论
由此可知 beforeDestroy 同步执行,在 beforeDestroy 生命钩子调用之前,所有实例都可以用
使用场景
# 3.2 destroyed
  Vue.prototype.$destroy = function () {
    ...
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    vm.__patch__(vm._vnode, null)
    callHook(vm, 'destroyed')
  }
 2
3
4
5
6
7
8
9
结论
由此可知 destroyed 同步执行,在 destroyed 生命钩子调用之前,实列上的方法 监听都被移除掉,所有的子实例也会被销毁
使用场景
卸载定时器或者原生 js 相关方法
# 3.3 有 keep-alive 包裹案例
销毁子组件
组件类型 步骤 1 步骤 2 步骤 3 父组件 parent-beforeUpdate parent-updated 子组件 child-deactivated 
# 3.4 无 keep-alive 包裹案例
这里使用 v-if 来销毁子组件
销毁子组件
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 parent-beforeUpdate parent-updated 子组件 child-beforeDestroy child-destroyed 
# 4.其他生命钩子
# 4.1 activated
const componentVNodeHooks = {
  insert(vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, "mounted")
    }
    if (vnode.data.keepAlive) {
      activateChildComponent(componentInstance, true /* direct */)
    }
  },
}
 2
3
4
5
6
7
8
9
10
11
12
export function activateChildComponent(vm: Component, direct?: boolean) {
  ...
  callHook(vm, "activated")
}
 2
3
4
结论
由于在组件中使用 keep-alive,所以被包裹的组件不可能是根组件,组件如果执行 mounted,会在后面执行 activated
使用场景
缓存组件
# 4.2 deactivated
const componentVNodeHooks = {
  destroy(vnode: MountedComponentVNode) {
    const { componentInstance } = vnode
    if (!componentInstance._isDestroyed) {
      if (!vnode.data.keepAlive) {
        componentInstance.$destroy()
      } else {
        deactivateChildComponent(componentInstance, true /* direct */)
      }
    }
  },
}
 2
3
4
5
6
7
8
9
10
11
12
export function deactivateChildComponent(vm: Component, direct?: boolean) {
  ...
  callHook(vm, "deactivated")
}
 2
3
4
结论
由于在组件中使用 keep-alive,所以被包裹的组件不可能是根组件,组件 destroy 和 deactivated 是互斥的,既只能执行其中一个
使用场景
缓存组件
# 4.3 errorCaptured
export function handleError(err: Error, vm: any, info: string) {
  try {
    if (vm) {
      let cur = vm
      while ((cur = cur.$parent)) {
        const hooks = cur.$options.errorCaptured
        if (hooks) {
          for (let i = 0; i < hooks.length; i++) {
            try {
              const capture = hooks[i].call(cur, err, vm, info) === false
            } catch (e: any) {
            }
          }
        }
      }
    }
  }
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
结论
当捕获一个来自子孙组件的错误时被调用
使用场景
捕获 vue 内部错误
# 5.案例分析
结果
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 beforeUpdate updated nextTick1 nextTick2 
通过上述知识点的分析,mounted 由于是非根组件,不确定是否是同步执行,beforeUpdate、updated 异步执行
这里有两点要研究:1.此处的 mounted 是否是同步加载、2.beforeUpdate、updated 异步执行过程是否和 this.$nextTick 一致
# 5.1 mounted 同步异步
建立一个空项目,将上述代码放入App.vue中运行查看
结果
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 nextTick1 beforeUpdate updated nextTick2 
这里可以确定组件是同步加载,符合预期
猜想案例中 mounted 是异步加载,所以组件是异步的,通过 () => import('./xxx.vue')的方式异步加载组件,发现和案例中的结果一致,所以可以得出结论
这里的组件异步加载的,mounted 是异步执行的
我们都知道实例化渲染watcher时/更新数据时,会触发传入的updateComponent函数,通过vm._render生成vnode,让后通过vm._update将vnode派发更新到dom上
具体分析 render和update过程,可以了解其大概的流程
# 5.1.1 render 生成 vnode
- createComponent
 
export function createComponent(
  Ctor: typeof Component | Function | ComponentOptions | void,
  data: VNodeData | undefined,
  context: Component,
  children?: Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  // async component
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
    }
  }
  const name = getComponentName(Ctor.options) || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ""}`,
    data,
    undefined,
    undefined,
    undefined,
    context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  return vnode
}
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
- resolveAsyncComponent
 
export function resolveAsyncComponent(
  factory: { (...args: any[]): any, [keye: string]: any },
  baseCtor: typeof Component
): typeof Component | void {
  const owner = currentRenderingInstance
  const owners = (factory.owners = [owner])
  const forceRender = (renderCompleted: boolean) => {
    for (let i = 0, l = owners.length; i < l; i++) {
      owners[i].$forceUpdate()
    }
  }
  const resolve = once((res: Object | Component) => {
    factory.resolved = ensureCtor(res, baseCtor)
    if (!sync) {
      forceRender(true)
    } else {
      owners.length = 0
    }
  })
  const res = factory(resolve, reject)
  if (isPromise(res)) {
    // () => Promise
    if (isUndef(factory.resolved)) {
      res.then(resolve, reject)
    }
  }
  return factory.loading ? factory.loadingComp : factory.resolved
}
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
执行res.then,通过forceRender强制更新组件
- createAsyncPlaceholder
 
export function createAsyncPlaceholder(
  factory: Function,
  data: VNodeData | undefined,
  context: Component,
  children: Array<VNode> | undefined,
  tag?: string
): VNode {
  const node = createEmptyVNode()
  node.asyncFactory = factory
  node.asyncMeta = { data, context, children, tag }
  return node
}
 2
3
4
5
6
7
8
9
10
11
12
- $forceUpdate
 
Vue.prototype.$forceUpdate = function () {
  const vm: Component = this
  if (vm._watcher) {
    vm._watcher.update()
  }
}
2
3
4
5
6
通过渲染watcher上的update方法更新组件
- watcher
 
export default class Watcher implements DepTarget {
  update() {
    ...
    queueWatcher(this)
  }
}
2
3
4
5
6
- queueWatcher
 
let waiting = false
export function queueWatcher(watcher: Watcher) {
  ...
  if (!waiting) {
    waiting = true
    nextTick(flushSchedulerQueue) // 异步任务 nextTick
  }
}
2
3
4
5
6
7
8
- flushSchedulerQueue
 
function resetSchedulerState() {
  waiting = flushing = false
}
function flushSchedulerQueue() {
  flushing = true
  let watcher, id
  queue.sort(sortCompareFn)
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    watcher.before() // 生命周期函数 beforeUpdate
    watcher.run() // 更新模板
  }
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()
  resetSchedulerState()
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
可以看到异步任务通过nextTick异步队列就开始执行了
# 5.1.2 patch 到 dom
- patch
 
return function patch(oldVnode, vnode, hydrating, removeOnly) {
  ...
  createElm(vnode, insertedVnodeQueue)
  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
}
 2
3
4
5
invokeInsertHook 调用 mounted
- createElm
 
function createElm() {
  ...
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return
  }
}
 2
3
4
5
6
- createComponent
 
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data
  if (isDef(i)) {
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
    if (isDef((i = i.hook)) && isDef((i = i.init))) {
      i(vnode, false /* hydrating */)
    }
    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue)
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- componentVNodeHooks
 
const componentVNodeHooks = {
  init(vnode: VNodeWithData, hydrating: boolean): boolean | void {
    ...
      const child = (vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      ))
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  },
}
 2
3
4
5
6
7
8
9
10
createComponentInstanceForVnode 相当于 new Vue
child.$mount 手动挂载由于不是根组件,所以不会走 mounted,只走 beforeMount
- createComponentInstanceForVnode
 
export function createComponentInstanceForVnode(): Component {
  ...
  return new vnode.componentOptions.Ctor(options)
}
 2
3
4
- invokeInsertHook
 
function invokeInsertHook(vnode, queue, initial) {
  if (isTrue(initial) && isDef(vnode.parent)) {
    vnode.parent.data.pendingInsert = queue
  } else {
    for (let i = 0; i < queue.length; ++i) {
      queue[i].data.hook.insert(queue[i])
    }
  }
}
 2
3
4
5
6
7
8
9
- insert
 
const componentVNodeHooks = {
  insert(vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, "mounted")
    }
  },
}
 2
3
4
5
6
7
8
9
到此为止,父组件的挂载结束,同步代码执行完毕
上述render阶段产生的异步任务开始执行
# 5.1.3 $forceUpdate
从上述流程中可以看到,通过$forceUpdate可以更新组件,并且时放到$nextTick中进行异步更新的,此时触发更新的主体还是在父组件中,放入任务队列中的渲染watcher还是父组件
更新组件走更新逻辑,所以父组件上会触发beforeUpdate和updated两个生命周期(可以自己建一个空项目验证,父组件下有一个异步组件,父组件未更新数据却会触发更新的生命周期)
- 父组件更新时:render 阶段
 
createComponent 之前初始化时由于是异步组件,走异步组件加载逻辑resolveAsyncComponent,现在可以拿到数据了走Ctor = baseCtor.extend(Ctor as typeof Component)逻辑
  Vue.extend = function (extendOptions: any): typeof Component {
    ...
    const Super = this
    const Sub = function VueComponent(this: any, options: any) {
      this._init(options)
    } as unknown as typeof Component
    return Sub
  }
2
3
4
5
6
7
8
可以看到子组件通过extend继承到了_init,后续在有需要的地方通过new就可以走和new Vue一样的初始化逻辑
通过createComponent生成组件vnode后就进入了 patch 阶段
patch
vm._update拿到vnode后,会在createElm通过createComponent执行组件上的生命周期init
const child = (vnode.componentInstance = createComponentInstanceForVnode(
  vnode,
  activeInstance
))
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
 2
3
4
5
从上面可以知道这两步两个目的:
- 1.实例化子组件:
createComponentInstanceForVnode执行后会执行this._init - 2.挂载子组件:会生成渲染
watcher 
到此处已经进入了子组件内部逻辑处理部分了,后面的流程可以看作,一个同步组件的初次加载流程了,由于不是根组件所以步骤2这里只执行beforeMount,不执行mounted
mounted的执行在组件的生命周期insert中执行
insert(vnode: MountedComponentVNode) {
  ...
  callHook(componentInstance, 'mounted')
}
 2
3
4
此时可以知道异步子组件和同步子组件不同之处有:(父组件为根组件、或同步组件)
- 1.父组件没有更新操作,使用同步子组件父组件不会触发
beforeCreate、created这两个生命周期,但是用异步子组件时,父组件会触发这两个生命周期 - 2.同步子组件在父组件中初始化时是同步渲染的,派发过程也是同步的;异步子组件在父组件中初始化时是异步渲染的,派发过程也是异步的,生命周期
beforeCreate、created、beforeMount、mounted都是异步执行的,且异步都是在nextTick中执行的 
都按微任务理解,更新也是微任务,那么打印顺序应该是:nextTick1 nextTick2 beforeUpdate updated
但是真实结果和这个不一样,那么具体来分析下nextTick
# 5.2 $nextTick
- nextTick
 
const callbacks: Array<Function> = [] // 关键点
let pending = false
function flushCallbacks() {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
let timerFunc
const p = Promise.resolve()
timerFunc = () => {
  p.then(flushCallbacks)
}
export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e: any) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
}
 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
分析可以知道 nextTick 原理就是开启一个队列,存储$nextTick的回调函数,同时开启微任务,与一般的微任务的区别就是这里的任务是放入一个队列中,同一段代码同步执行都会放入队列中
  mounted() {
    this.$nextTick(() => {
      logsData.push("nextTick1")
    })
    this.msg = "111"
    this.$nextTick(() => {
      logsData.push("nextTick2")
    })
  },
  beforeUpdate() {
    logsData.push("beforeUpdate")
  },
  updated() {
    logsData.push("updated")
  },
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
异步组件挂载过程中是在微任务中执行mounted,任务都放在updatedQueue中,这个任务还没执行完,此时this.$nextTick开启了另一个微任务,任务不会放到updatedQueue中;this.msg = "111"触发更新操作,更新任务是放到updatedQueue中的,所以在这个微任务中会继续执行beforeUpdate、updated,然后才执行
nextTick1、nextTick2
总结
通过上述案例可以了解到:
- 1.同一组件内生命周期首次加载同步执行、数据更新异步加载、数据销毁同步加载
 - 2.异步组件会影响父组件的生命周期,异步组件内部自身执行逻辑顺序和同步时也是不一样的,特别是使用自带的
nextTick时容易出现 bug 
← Vue2.x 的指令 Vue2.x 的动画 →