vue2-生命周期1

lifecycle.png
  1. 初始阶段
    new Vue() 到created之间 的阶段叫做初始化阶段。
    这个阶段的目的是在vue.js实例上初始化一些属性、事件以及响应式数据。如: props,methods,data,computed,watch,provide和injdect等。

  2. 模板编译阶段
    created钩子函数 与 beforeMount 钩子函数 之间的阶段 是模板编译阶段。
    这个阶段的目的是 将模板编译为渲染函数,只存在于完整版中。
    如果在职包含运行时的构建版本中 执行 new Vue(),则不会存在这个阶段。
    当使用 vue-loader 或 vueify 时, *.vue 单文件 内部的 模板 template 会在构建时 预编译 成 javascript, 所有最终打好 的包里 是不需要编译器的(已经编译过了), 用运行时版本即可。

  3. 挂载阶段
    beforeMount钩子函数到 mounted钩子函数之间是挂载阶段。
    这个阶段,Vue.js会 将实例 挂载到DOM元素上, 并在挂载过程中,开启 watcher 来 持续追踪依赖变化。

在已挂载的状态下,当数据(状态)变化时, watcher 会通知 虚拟DOM重新渲染视图, 并且会在渲染视图前 触发 beforeUpdate钩子函数,渲染完毕后触发 updated 钩子函数。

  1. 卸载阶段
    应用调用vm.$destroy方法后, Vue.js的生命周期会进入卸载阶段。
    这个阶段,Vue.js会将自身 从父组件 中删除, 取消实例上 所有 依赖的追踪 并且 移除所有的 事件监听器。

源码角度了解生命周期

生命周期整体上分为两个部分:

  • 第一个部分: 初始化、模板编译、挂载阶段
  • 第二个部分:卸载阶段

事实上 卸载阶段 其实就是 vm.$destroy方法 内部原理(https://www.jianshu.com/p/b4737801a416),这里就不重复说了。

new Vue() 被调用时发生了什么

new Vue()被调用时, 会进行一些初始化操作,然后进入模板编译阶段,最后进入挂载阶段。
源码-vue2/src/core/instance/index.js

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) // Vue构造函数的prototype属性会被添加_init方法
stateMixin(Vue) // vue构造函数 的prototype属性挂载与数据相关的实例方法。 vm.$watch, vm.$set, vm.$delete. 并且会挂载上 $data 和 $props
eventsMixin(Vue) // eventsMixin中 会实现 vm.$on vm.$off vm.$once vm.$emit这个四个方法。
lifecycleMixin(Vue) // vm.$forceUpdate,vm.$destroy vm._update 
renderMixin(Vue) // vm.$nextTick  vm._render 的实现

export default Vue

构造函数的逻辑很简单。 首先进行安全检查,然后调用 this._init(options) 来执行 生命周期的 初始化流程。

_init方法是 initMixin方法 中被挂载到Vue.js原型上的。

initMixin 方法 的实现 是在 Vue.prototype属性上添加了_init方法。

export function initMixin (Vue: Class) {
  Vue.prototype._init = function (options?: Object) {
...
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        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')
...
    // 如果用户 传入el 选项, 则自动开启模板编译阶段和挂载阶段
    // 否则 不进入下一个生命周期 需要用户 执行 vm.$mount方法,手动开启编译和挂载阶段
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

vue.js会在初始化流程的不同时期通过callHook函数触发生命周期钩子。

在执行初始化流程之前, 实例上挂载了 $options属性。 目的是 将用户的options 选项与 钩子函数的options属性 及其父级实例 钩子函数 的options合并赋值给 $options属性。

可以通过代码看到, 初始过程中, 首先 初始化事件与属性, 然后 触发生命周期钩子 beforeCreate. 随后 初始化 provide/inject 和状态(props、methods、 data、computed 、 以及 watch)。随后 触发 生命周期钩子 created. 最后 判断是否有el选项, 如果是 直接调用 vm.$mount方法 ,进入后面 的生命周期阶段,否则等待 用户 执行 vm.$mount方法之后 进入模板编译与挂载阶段。

callHook函数的内部原理
callHook的作用 是触发 用户设置的生命周期钩子,生命周期 钩子 会在 执行 new Vue()时 通过参数传递给 Vue.js.

用户传入的options 参数 最后会与 构造函数的 options合并 赋值给 vm.$options属性, 我们就可以 得到用户设置 的生命周期函数了。例如: vm.$opionts.created 得到用户 设置 的created钩子函数。

vue.js在合并options 的过程中 会找出 options中所有key 是 钩子函数的名称 ,并将它转换为数组。

也就是说 vm.$options.cretead 是一个 数组。数组中 包含了钩子函数。
这里设置为数组的原因是:
Vue.mixin和 用户 实例化Vue.js时,如果设置了同一个 生命周期钩子,那么保存为数组后, 就可以在同一个 生命周期钩子中保存过得生命周期钩子了。

比如: Vue.mixin设置了生命周期钩子 mounted, new Vue时,参数中也设置了 mounted 生命钩子。 这时 ,vm.$options.mounted这个数组中,就会包括这 两个生命周期钩子。

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  // 获取全局当前 唯一的watcher
  pushTarget()
  // 尝试获取 对应 hook的函数 列表
  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, null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  // 删除targetStack从删除最后一个 targetStack.pop()
  // Dep.target = targetStack[targetStack.length - 1]
  popTarget()
}

参数:vm 是 vue.js实例 , hook 是生命周期钩子的名称

我们获取到对应生命周期钩子 的列表后 遍历执行每一个 钩子函数。

初始化实例属性

Vue的生命周期中,初始实例属性是第一步(initLifecycle向实例中挂载属性)。
实例化的属性既有 Vue.js内部使用 属性(vm._watcher),也有外部使用的属性(vm.$parent)

以$ 开头的属性 提供给用户使用的 外部属性, 以_开头的属性是 内部使用的内部属性。

vue通过initLifecycle函数向实例中挂载属性, 该函数接受vue.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
}

逻辑就是在Vue.js实例上设置一下属性 并提供默认值。

注意 : vm.$parent 属性 需要找到 第一个 非抽象类型的父类。 (抽象组件与普通组件一样,只是它不会在界面上显示任何 DOM 元素。它们只是为现有组件添加额外的行为。 就像很多你已经熟悉的 Vue.js 的内置组件,比如:

另一个注意的是 vm.$children属性,它会包含当前实例的直接子组件。该属性的值是从子组件中 主动添加 到父组件中的。 上面代码中的 parent.$children.push(vm), 就是将当前实例添加到父组件实例的$children属性中。
最后一个 注意的是 vm.$root。 当前组件的根Vue.js实例。 如果当前组件没有父组件,那么它就是根组件。 它的$root就是自己。 然后它 的子组件的vm.$root 沿用父级的$root, 孙组件的$root沿用子组件的root 传递给每一个子组件。

初始化事件

实例初始化阶段,初始化的事件指的是 父组件 在模板 中 使用 v-on监听的子组件内触发的事件。
比如: v-on 写在 组件标签上 ,那么这事件会被注册到子组件vue.js事件系统中。 如果写在 平台标签如div上,那么事件会被注册到浏览器事件中。

initEvents函数 执行 初始化事件相关的逻辑

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // 初始化 父组件 附加的事件
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

vm上新增 _events属性初始化为 空对象,用来储存事件。 所有使用 vm.$on注册的事件监听器 都会保存到 vm._events属性中。

在模板编译阶段,当模板解析到组件标签时,会实例化子组件,同时将标签上注册的事件解析成 object 并 通过参数 传递 给 子组件。 所有当 子组件实例化时, 可以在参数中获取 父组件向 自己注册的事件, 这些 事件最终 会 保存在 vm.$options._parentListeners

updateComponentListeners方法,将父组件向子组件注册的事件注册到子组件的实例中。

let target: any

function add (event, fn) {
  target.$on(event, fn)
}

function remove (event, fn) {
  target.$off(event, fn)
}

function createOnceHandler (event, fn) {
  const _target = target
  return function onceHandler () {
    const res = fn.apply(null, arguments)
    if (res !== null) {
      _target.$off(event, onceHandler)
    }
  }
}
export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}

add 新增事件 remove删除事件 createOnceHandler新增一次性事件

updateListeners函数的实现

export function updateListeners (
  on: Object, // 需要执行的事件列表
  oldOn: Object, // 旧的事件列表
  add: Function, // 新增 函数
  remove: Function, // 移除 函数
  createOnceHandler: Function, // 一次性 执行事件
  vm: Component // 实例
) {
  let name, def, cur, old, event
  // 循环 需要执行的事件列表
  for (name in on) {
    def = cur = on[name]
    old = oldOn[name]
    event = normalizeEvent(name)
    /* istanbul ignore if */
    if (__WEEX__ && isPlainObject(def)) {
      cur = def.handler
      event.params = def.params
    }
    // 判断事件名对应的值是否是 undefined和null。如果是调用控制台警告
    if (isUndef(cur)) {
      process.env.NODE_ENV !== 'production' && warn(
        `Invalid handler for event "${event.name}": got ` + String(cur),
        vm
      )
    } 
    // 判断事件名 是否 在oldOn中 ,如果不存在,则调用 add 注册事件
    else if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        cur = on[name] = createFnInvoker(cur, vm)
      }
      if (isTrue(event.once)) {
        // 一次性 事件
        cur = on[name] = createOnceHandler(event.name, cur, event.capture)
      }
      add(event.name, cur, event.capture, event.passive, event.params)
    }
    //  如果 on和oldOn都存在,但是它们不相同,则将事件回调替换成 on 中的回调,并且把on 中的回调引用指向真实的事件系统中注册的事件,也就是 oldOn 中对应的事件
    else if (cur !== old) {
      old.fns = cur
      on[name] = old
    }
  }
  // 循环 旧 的事件列表
  // 删除 不存在 的事件
  for (name in oldOn) {
    if (isUndef(on[name])) {
      event = normalizeEvent(name)
      remove(event.name, oldOn[name], event.capture)
    }
  }
}

该函数接收5个参数,分别是on,onldOn, add,remove,vm.
主要逻辑对比on和oldon来分辨哪些事件需要执行add注册事件,哪些事件需要执行remove删除事件。

上面代码一部分循环on,第二部分循环oldOn。 第一个部分主要作用是判断哪些事件在oldOn中不存在,调用add注册这些事件。 第二部分的作用是循环oldOn, 判断哪些事件在on中不存在,调用remove移除这些事件。

其中代码中 normalizeEvent 函数 的作用 是将事件修饰符解析出来。

Vue的模板中支持事件修饰符,例如capture,once,passive等,如果我们在模板中注册事件时使用了事件修饰符,那么在编译阶段解析标签上的属性时,会将这些修饰符改成对应的符号加上事件名前面。
例如: . 此时vm.$options._parentListeners 是这个样子 {~increment: function(){}}

const normalizeEvent = cached((name: string): {
  name: string,
  once: boolean,
  capture: boolean,
  passive: boolean,
  handler?: Function,
  params?: Array
} => {
  const passive = name.charAt(0) === '&'
  name = passive ? name.slice(1) : name
  const once = name.charAt(0) === '~' // Prefixed last, checked first
  name = once ? name.slice(1) : name
  const capture = name.charAt(0) === '!'
  name = capture ? name.slice(1) : name
  return {
    name,
    once,
    capture,
    passive
  }
})

代码中 如果修饰符 ,则会将它截取出来。最终 输出的对象中保存了事件名已经一些事件修饰符,这些修饰符为true说明事件使用了此事件修饰符。

你可能感兴趣的:(vue2-生命周期1)