资讯专栏INFORMATION COLUMN

再谈Vue的生命周期----结合Vue源码

KavenFan / 3162人阅读

摘要:中的生命周期函数也可以称之为生命周期钩子函数,在特定的时期,调用特定的函数。吊起钩子函数调起钩子函数问题为什么是一个数组卸载组件,会触发一个这行代码之后发生了什么背后实现原理。

简介

关于Vue的生命周期函数,目前网上有许多介绍文章,但也都只是分析了表象。这篇文档,将结合Vue源码分析,为什么会有这样的表象。

Vue中的生命周期函数也可以称之为生命周期钩子(hook)函数,在特定的时期,调用特定的函数。

随着项目需求的不断扩大,生命周期函数被广泛使用在数据初始化、回收、改变Loading状态、发起异步请求等各个方面。

而Vue实例的生命周期函数有beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestpry、destroyed,8个。

本文假设读者使用过Vue.js,但对相应的开发经验不做要求。如果你对Vue很感兴趣,却不知如何下手,建议你先阅读官方文本

资源

以下是这篇文章所需的资源,欢迎下载。

项目仓库

Vue源码笔记

表象

我们在该页面来研究,Vue的生命周期函数究竟会在何时调用,又会有什么不同的特性。强烈建议你直接将项目仓库克隆至本地,并在真机环境中,跑一跑。Vue.js已经添加在版本库中,因此你不需要添加任何依赖,直接在浏览器中打开lifeCycle.html即可。

$ git clone https://github.com/AmberAAA/vue-guide
编写实现组件

定义templatedata,使其在可以在屏幕实时渲染出来。

{
  //...
  template: `
    

`, data () { return { info: "" } } //... }

beforeCreate为例,定义全部的证明周期函数。

  beforeCreate() {
    console.group("------beforeCreate------");
    console.log("beforeCreate called")
    console.log(this)
    console.log(this.$data)
    console.log(this.$el)
    this.info += "beforeCreate called 
" console.groupEnd(); }
屏幕输出

在浏览器中打开lifeCycle.html,点击挂载组件后,屏幕依次输出created called beforeMount called mounted called 。表现出,在挂载组件后,infocreated, beforeMount, mounted赋值,并渲染至屏幕上。但是本应在最开始就执行的beforeCreate却并没有给info赋值。
卸载组件时,因为程序运行太快,为了方便观察,特意为beforeDestroybeforeDestroy函数在最后添加了断点。发现点击卸载组价后,Vue在v-if=true时会直接从文档模型中卸载组件(此时组件已经不在document)。

控制台输出

控制台输出的内容非常具有代表性。

我们可以发现,控制台按照创建、挂载、更新、销毁依次打印出对应的钩子函数。展开来看

在触发beforeCreate函数时,vue实例还尚未初始化$data,因此也就无法给$data赋值,也就很好的解释了为什么在屏幕上,没有渲染出beforeCreate called。同时,因为尚未被挂载,也就无法获取到$el

在触发created函数时,其实就表明,该组件已经被创建了。因此给info赋值后,待组件挂载后,视图也会渲染出created called

在触发beforeMount函数时,其实就表明,该组件即将被挂载。此时组建表现出的特性与created保持一致。

在触发mounted函数时,其实就表明,该组件已经被挂载。因此给info赋值后,待组件挂载后,视图也会渲染出mounted called,并且控制台可以获取到$el

触发beforeUpdateupdated,分别表示视图更新前后,更新前$data领先视图,更新后,保持一致。在这两个回调函数中,更改data时注意避免循环回调。

触发beforeDestroydestroyed,表示实例在销毁前后,改变$data,会触发一次updated,因在同一个函数中(下文会介绍)回调,故捏合在一起说明。

名称 触发阶段 $data $el
beforeCreate 组件创建前
created 组件创建后
beforeMount 组件挂载前
mounted 组件挂载后
beforeUpdate 组件更新前
updated 组件更新后
beforeDestroy 组件创建前
destroyed 组件创建前
原理

Vue生命周期函数在源码文件/src/core/instance/init.js中定义,并在/src/core/instance/init.jssrc/core/instance/lifecycle.js/src/core/observer/scheduler.js三个文件中调用了所有的生命周期函数

callHooK

当在特定的使其,需要调用生命周期钩子时,源码只需调用callHook函数,并传入两个参数,第一个为vue实例,第二个为钩子名称。如下

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  if (handlers) {
    //? 这里为什么是数组?在什么情况下,数组的索引会大于1?
    for (let 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在打包时,并没有暴露在全局作用域。但我们可以根据Vue实例来手动调用生命周期函数。试着在挂在组件后在控制台输入vue.$children[0].$options["beforeCreate"][0].call(vue.$children[0]),可以发现组件的beforeCreate钩子已经被触发了。并且表示出了与本意相驳的特性。此时因为组件已经初始化,并且已经挂载,所以成功在控制台打印出$el$data,并在修改info后成功触发了beforeUpdatebeforeUpdate

beforeCreatecreated

Vue会在/src/core/instance/init.js中通过initMixin函数对Vue实例进行进一步初始化操作。

  export function initMixin (Vue: Class) {
    Vue.prototype._init = function (options?: Object) {
      /*
        ....
      */
      vm._self = vm
      initLifecycle(vm) 
      initEvents(vm)
      initRender(vm)
      callHook(vm, "beforeCreate")
      initInjections(vm) // resolve injections before data/props
      initState(vm)   // 定义$data
      initProvide(vm) // resolve provide after data/props
      callHook(vm, "created")

      /*
        ...
      */
    }
  }

可以看出在执行callHook(vm, "beforeCreate")之前,Vue还尚未初始化data,这也就解释了,为什么在控制台beforeCreate获取到的$dataundefined,而callHook(vm, "created")却可以,以及屏幕上为什么没有打印出beforeCreate called

beforeMountmounted

Vue在/src/core/instance/lifecycle.js中定义了mountComponent函数,并在该函数内,调用了beforeMountmounted

  export function mountComponent (
    vm: Component,
    el: ?Element,
    hydrating?: boolean
  ): Component {
    vm.$el = el    // 组件挂载时 `el` 为`undefined`

    callHook(vm, "beforeMount") // 所以获取到的`$el`为`undefined`

    /*
      ...
    */
    // 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

    //! 挖个新坑 下节分享渲染watch。 经过渲染后,即可获取`$el`
    new Watcher(vm, updateComponent, noop, null, 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
      // 因为已经渲染,`$el`此时已经可以成功获取
      callHook(vm, "mounted")
    }
    return vm
  }
beforeUpdateupdated

beforeUpdateupdated涉及到watcher,因此将会在以后的章节进行详解。

beforeDestroydestroyed

Vue将卸载组件的方法直接定义在原型链上,因此可以通过直接调用vm.$destroy()方法来卸载组件。

  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    // 吊起`beforeDestroy`钩子函数
    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
    // 调起`destroyed`钩子函数
    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
    }
  }
}
问题

vue.$children[0].$options["beforeCreate"]为什么是一个数组?

卸载组件,会触发一个updated called?

new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)这行代码之后发生了什么?

beforeUpdate背后实现原理。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98212.html

相关文章

  • 【前端词典】从源码解读 Vuex 注入 Vue 生命周期过程

    摘要:第一篇文章我会结合和的部分源码,来说明注入生命周期的过程。说到源码,其实没有想象的那么难。但是源码的调用树会复杂很多。应用的业务代码逐渐复杂,事件事件总线等通信的方式的弊端就会愈发明显。状态管理是组件解耦的重要手段。前言 这篇文章是【前端词典】系列文章的第 13 篇文章,接下的 9 篇我会围绕着 Vue 展开,希望这 9 篇文章可以使大家加深对 Vue 的了解。当然这些文章的前提是默认你对 ...

    Aklman 评论0 收藏0
  • 关于Vue2一些值得推荐文章 -- 五、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    Terry_Tai 评论0 收藏0
  • 关于Vue2一些值得推荐文章 -- 五、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    sutaking 评论0 收藏0
  • 关于Vue2一些值得推荐文章 -- 五、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    khs1994 评论0 收藏0
  • Vue原理】生命周期 - 源码

    摘要:其中的标志位什么时候设置呢,是在相应的钩子触发之后,具体看下面源码怎么执行钩子呢没错,就是下面这个函数是自己传入的等回调那是怎么用呢比如触发就会这么调用很简单不,直接拿到钩子,然后遍历执行,绑定上下文对象。 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 ...

    siberiawolf 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<