Vue实例初始化-生命周期相关——vue2源码探究(9)

107 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情

我们继续来看Vue实例初始化的时候_init方法做了什么:

// 源码文件:src\core\instance\init.ts
//...
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate', undefined, false /* setContext */)
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
//...

这是一系列的初始化和生命周期钩子的调用,今天我们拿两个出来说,生命周期的初始化和生命周期钩子的调用:

initLifecycle(vm)
callHook(vm, 'beforeCreate', undefined, false /* setContext */)
callHook(vm, 'created')

生命周期的初始化

与其说是生命周期的初始化,initLifecycle不如说是父组件的绑定:

export function initLifecycle(vm: Component) {
  const options = vm.$options

  // 定位到第一个非抽象组件
  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._provided = parent ? parent._provided : Object.create(null)
  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

option中的parent存在,且当前组件不是抽象组件时,向上逐层查找,直到查找到第一个非抽象组件的父组件,将组件加入到该父组件的$children中,同时,将该组件的$parent指向这个父组件。

接着给实例挂载$root属性,逻辑是如果上述父组件存在,则指向那个父组件,如果不存在,则指向实例本身。

之后,对一些状态的初值进行初始化:

// 向子组件传递的数据选项
vm._provided = parent ? parent._provided : Object.create(null)
// 用于监听的watcher实例对象
vm._watcher = null
// keep-alive的组件状态属性
vm._inactive = null
// 同上
vm._directInactive = false
// 标记是否完成挂载
vm._isMounted = false
// 标记是否完成销毁
vm._isDestroyed = false
// 标记是否在销毁过程中(在beforeDestory和destoryed两个生命周期之间)
vm._isBeingDestroyed = false

生命周期的调用

Vue实例初始化这篇文章中,我们提到过,传入的生命周期方法会合并成一个数组,因此在触发相关生命周期的时候,需要将这个数组里的所有方法全部进行调用:

// 源码文件:src\core\instance\lifecycle.ts
export function callHook(
  vm: Component,
  hook: string,
  args?: any[],
  setContext = true
) {
  pushTarget()//依赖收集,依赖管理入栈处理,避免重复依赖
  const prev = currentInstance
  setContext && setCurrentInstance(vm)
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, args || null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  setContext && setCurrentInstance(prev)
  popTarget()//同上,依赖收集,依赖管理出栈处理,避免重复依赖
}

执行数组中所有钩子方法的时候进行了一层封装,主要就是做一个错误捕捉:

// 源码文件:src\core\util\error.ts
export function invokeWithErrorHandling(
  handler: Function,
  context: any,
  args: null | any[],
  vm: any,
  info: string
) {
  let res
  try {
    res = args ? handler.apply(context, args) : handler.call(context)
    if (res && !res._isVue && isPromise(res) && !(res as any)._handled) {
      res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
      // issue #9511
      // avoid catch triggering multiple times when nested calls
      ;(res as any)._handled = true
    }
  } catch (e: any) {
    handleError(e, vm, info)
  }
  return res
}