Vue生命周期

vue生命周期

在vue中,执行生命周期函数都是调用的callHook函数。

function callHook (vm, hook) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget();
  var handlers = vm.$options[hook];
  if (handlers) {
    for (var i = 0, j = handlers.length; i < j; i++) {
      try {
        handlers[i].call(vm);
      } catch (e) {
        handleError(e, vm, (hook + " hook"));
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook);
  }
  popTarget();
}

callHook的作用就是拿到实例上$options上的hook,也就是生命周期函数的数组。然后调用某个生命周期钩子注册所有的回调函数。

当实例化vue的时候,会先执行_init函数,beforeCreate和cteated生命周期就是在这里定义的。

Vue.prototype._init = function (options?: Object) {
  // ...
  callHook(vm, 'beforeCreate')
  initInjections(vm) // resolve injections before data/props
  initState(vm)
  initProvide(vm) // resolve provide after data/props
  callHook(vm, 'created')
  // ...
}

我们可以看到initState函数是在beforeCreate后调用的,而initState函数是初始化props、data、methods、watch、computed等属性值的,所以,没有办法在beforeCreate中获取props、data、methods、watch、computed等属性值。而在created中可以获取到。

在这两个生命周期函数中并没有渲染DOM,所以我们不能够访问DOM。

我们还可以看到inject是在data、props之前初始化的,provide是在data、props之后初始化的。

beforeMount是在mountComponent函数中调用的。

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // ...
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

我们可以看到在mountComponent中vm.$el = el;,将el挂载到了实例的$el上。然后执行beforeMount生命周期函数,但是这时候还是没有渲染DOM,我们看到这里使用了callHook(vm, 'mounted');,但是这里判断了一下vm.$vnode,如果是通过new vue的初始化过程就调用callHook(vm, 'mounted');,如果是组件的话,不是在这里调用的mounted生命周期函数。组件的mounted生命周期是在虚拟DOM patch到真实DOM上之后再调用的callHook来调用mounted生命周期。

beforeUpdate的执行时机也在mountComponent中。在mountComponent中有一个new Watchernew Watcher里的before函数里执行的callHook。注意的是,这里判断了一下vm._isMounted,当组件已经mounted之后才会去调用beforeUpdate这个生命周期函数。

new Watcher(vm, updateComponent, noop, {
   before () {
     if (vm._isMounted) {
       callHook(vm, 'beforeUpdate')
     }
   }
 }, true /* isRenderWatcher */)

Watcher就是监听数据变化的函数,采用的是发布订阅模式数据劫持结合的方法来实现。其中数据劫持在Vue2中使用的Object.defineProperty()的setter和getter属性,在Vue3中使用的代理模式,也就是proxy

updated执行的时机就是当Wather监听到数据变化后,就会去遍历谁订阅了这个数据,当mounted存在时才会去调用callHook执行updated。

beforeDestory和destroy生命周期函数会在组件销毁的时候调用,它们都在$destroy方法中。

Vue.prototype.$destroy = function () {
  const vm: Component = this
  if (vm._isBeingDestroyed) {
    return
  }
  callHook(vm, 'beforeDestroy')
  vm._isBeingDestroyed = true
  // remove self from parent
  const parent = vm.$parent
  if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
    remove(parent.$children, vm)
  }
  // teardown watchers
  if (vm._watcher) {
    vm._watcher.teardown()
  }
  let i = vm._watchers.length
  while (i--) {
    vm._watchers[i].teardown()
  }
  // remove reference from data ob
  // frozen object may not have observer.
  if (vm._data.__ob__) {
    vm._data.__ob__.vmCount--
  }
  // call the last hook...
  vm._isDestroyed = true
  // invoke destroy hooks on current rendered tree
  vm.__patch__(vm._vnode, null)
  // fire destroyed hook
  callHook(vm, 'destroyed')
  // turn off all instance listeners.
  vm.$off()
  // remove __vue__ reference
  if (vm.$el) {
    vm.$el.__vue__ = null
  }
  // release circular reference (#6759)
  if (vm.$vnode) {
    vm.$vnode.parent = null
  }
}

我们可以看到beforeDestory生命周期函数在$destory的最开始执行,接着执行一系列的销毁动作,包括从parent中的$children中删除自身。删除watcher,然后渲染的虚拟DOM执行销毁的钩子函数,执行完后调用destoryed生命周期函数。

activated和deactivated是专门为kppe-alive组件定制的生命周期函数。

总结:

  1. 创建阶段
    new 一个vue的实例,会先进入_inti函数,beforeCreate和created两个生命周期函数都在这里面,先调用callHook来执行beforeCreate生命周期函数,然后会调用initState函数来进行props、methods、data等数据的初始化,然后调用callHook来执行created生命周期。

  2. 渲染阶段先判断el是否存在,如果不存在就继续判断就调用vm.$mount(el),然后执行下一步,意味着生命周期的结束,如果存在el就继续执行。

判断是否包含template这个属性,如果有就把template解析成一个render函数,如果没有template就是将它外部的HTML作为模板编译。beforeMount和mounted两个生命周期函数都是在mountComponent函数里面调用的。beforeMount只会在有了render函数的时候才执行,然后调用render函数,然后调用callHook执行mounted生命周期函数。此时的vue实例上有了$el属性,可以在mounted中获取到,beforeMount无法获取到$el。

  1. 更新阶段在Vue中使用的数据劫持和发布订阅来进行数据改变后发起通知的,当data中的数据发生变化的时候,会执行beforeUpdate生命周期函数,然后经过虚拟DOM,最后调用updated生命周期函数。

  2. 销毁阶段
    beforeDestroy和destroyed都是在$destroy函数中执行的,会在$destroy的最开始执行beforeDestroy,然后将进行一系列的销毁,销毁自身的数据,将parent中的$children删除自身,删除Watcher等,然后再渲染虚拟DOM,执行完后调用destroyed生命周期函数

一般在哪个生命周期请求异步数据

我们可以在created、beforeMount、mounted中请求异步数据,因为在这三个生命周期中,data已经创建了,可以进行数据的赋值操作。推荐在created中进行异步请求,created中请求的数据比beforeMount、mounted中的请求更快的获取到服务器的数据,用户体验更好。最重要的是SSR不支持beforeMount、mounted。

vue父子组件执行顺序

创建和渲染阶段

父beforeCreate -> 父created -> 父beforeMount -> 
子boforeCreate -> 子created -> 子beforeMount -> 子mounted ->
父mounted

更新过程

父beforeUpdate ->
子beforeUpdate -> 子updated ->
父updated

销毁过程

父beforeDestroy ->
子beforeDestroy -> 子destroyed ->
父destroyed

keep-alive中的生命周期有哪些?

keep-alive是vue提供的一个内置组件,用来进行数据的缓存,在切换组件的时候,数据仍然在内存中,防止重复渲染DOM。keep-alive包裹后会多两个生命周期函数,activated和deactivated,当组件切走,会进行缓存,然后触发deactivated生命周期函数,当组件被切回来,去缓存里面找该组件,触发activated生命周期函数。