vuejs源码解剖 — initLifecycle、initEvents、initRender

1,939 阅读2分钟

Vue初始化之initLifecycle

再次回到_init函数中,上一篇我们花了大量的时间详细描述了vm.$options是如何策略合并的。接下来我们顺着代码继续往下看:

if (process.env.NODE_ENV !== 'production') {
  initProxy(vm)
} else {
  vm._renderProxy = vm
}
vm._self = vm

我们看到在非生产环境执行initProxy(vm),而生产环境则直接定义_renderProxy属性指向当前实例本身。本着生产环境和非生产环境功能必须保持一致的原则,initProxy(vm)的目的,其实也是在实例上增加一个_renderProxy属性。鉴于是非生产环境,这里不再展开讨论,只用一句话总结initProxy的作用:通过原生Proxy对实例对象vm进行代理。
那么 _renderProxy到底是干嘛用的呢?目前只是定义个指向,是后面vm.$options.render函数中this的指向。

接下来我们回归本篇正题:initLifecycle都做了些啥

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
}

源代码很少,逻辑也相对简单:

  • 定义一个常量options指向vm.$options
  • 搜索当前实例的父实例,如果没有父实例,则给当前实例的$parent设置为undefined,比如当前组件是new出来的,那么就是根组件,父辈肯定是undefined了;
  • 如果当前parent存在且不是抽象组件,则将当前实例添加到父组件的$children中,成为他的孩子之一;
  • 找到当前组件的根组件$root,其实就是父组件的根组件。为何这样就认为是根组件了呢?因为组件是树状关系,每一个“出生”的孩子在出生的时候就认准了自己的祖先,这样代代相传,每生一个孩子就告诉他:孩子,你的祖先是谁谁谁,可不能忘本啊。这样千秋万代,都知道自己的祖先叫啥了。
  • 接下来就是定义子组件容器$children$refs等,都是与生命周期有关的数据,这里先占个坑位。

那么问题来了,什么叫“抽象组件”?一个最大的区别就是它不渲染真实DOM,例如keep-alivetransitontransition-group,抽象组件实例中一定有个属性abstract:true

Vue初始化之initEvents

初始化完毕initLifecycle之后,紧接着就是初始化initEvents。老规矩,先上源码:

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)
  }
}

初始化自定义事件,开始在实例上定义了一个内部原型为null的空对象,然后又增加一个内部属性_hasHookEvent,初始值为false。之后是判断$options上是否含有_parentListeners,若存在则执行updateComponentListeners方法。这里就有点奇怪,$options上哪来的_parentListeners属性呢?其实根组件初始化的时候是没有的,这是初始化子组件需要执行的,这里暂时先忽略,后面会详细讲解。

Vue初始化之initRender

initEvents简单带过后,我们接着看下一个初始化的函数initRender,老规矩,先上源码:

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)
  }
}

首先定义两个初始值都为null的属性_vnode_staticTrees,先占个位,混个眼熟,功能后面自然会用到。剩下的代码相对复杂,目前不一一解释,只简单总结:初始化插槽信息$slots以及初始化$createElement方法,使用defineReactive方法让$attrs$listeners响应式。