Vue2源码,生命周期篇-initLifecycle + initEvents

406 阅读2分钟

大致可以分为四个阶段

  1. 初始化阶段:为Vue实例上初始化一些属性,事件以及响应式数据
  2. 模板编译阶段: 将模板编译成渲染函数
  3. 挂载阶段:将模板渲染到真实的DOM中,挂载指定的DOM上
  4. 销毁阶段:将实例自身从父组件中删除,并取消依赖追踪及事件监听器

mergeOptions 合并属性

vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
)

定义在 src/core/util/options.js

/* 
  这个函数挺常用的
  合并options
  把parent和child这两个对象,根据合并策略,
  合并成一个新的对象,并且返回
*/
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }
  if (typeof child === 'function') {
    child = child.options
  }
  /* 规范化 */
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  /* 这里是递归调用,有extends 或者 mixins */
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }
  /* 下面代码是真正合并 */
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    /* 
      strat 实际上是一个函数,通过不同的key,去用不同的函数 
      根据不同的key 是有不同的合并策略的
      设计模式中的【策略】模式
    */
    const strat = strats[key] || defaultStrat
    /* 这里进行合并 */
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

callHook函数如何触发钩子函数

/* 
  callhook 函数,
  触发生命周期钩子函数
  比较简单,就是遍历数组,执行钩子函数
*/
export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  /* 
      handlers 这里其实是个数组
  */
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      // 在这个函数里面执行call, 钩子函数
      /* 遍历数组,把每一个钩子函数都执行一遍 */
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    /* 这里触发hook, 是不是在这里挂载的 */
    vm.$emit('hook:' + hook)
  }
  popTarget()
}

initLifecycle

就是给实例初始化了一些属性,包括以$开头的供用户使用的外部属性,也包括以_开头的供内部使用的内部属性

/* 
  代码不多
  主要是挂载了一些默认属性
  $parent
  $root
*/
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
}

initEvents

<child @select="selectHandler" 	@click.native="clickHandler"></child>

本篇文章介绍了生命周期初始化阶段所调用的第二个初始化函数——initEvents。该函数是用来初始化实例的事件系统的。

我们先从模板编译时对组件标签上的事件解析入手分析,我们知道了,父组件既可以给子组件上绑定自定义事件,也可以绑定浏览器原生事件。这两种事件有着不同的处理时机,浏览器原生事件是由父组件处理,而自定义事件是在子组件初始化的时候由父组件传给子组件,再由子组件注册到实例的事件系统中。

也就是说:初始化事件函数initEvents实际上初始化的是父组件在模板中使用v-on或@注册的监听子组件内触发的事件。

最后分析了initEvents函数的具体实现过程,该函数内部首先在实例上新增了_events属性并将其赋值为空对象,用来存储事件。接着通过调用updateComponentListeners函数,将父组件向子组件注册的事件注册到子组件实例中的_events对象里