超详细vue生命周期解析(详解)-程序员宅基地

技术标签: vue  vue.js  javascript  

vue是每一个前端开发人员都绕不过的一个技术,在国内的市场占有量也是非常的大,我们大部分人用着vue, 却不知道他内部其实经历了一些什么。每个生命周期又是什么时候开始执行的。我们今天来详细的看一看

首先,生命周期是个啥?
借用官网的一句话就是:每一个vue实例从创建到销毁的过程,就是这个vue实例的生命周期。在这个过程中,他经历了从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、卸载等一系列过程。那么这些过程中,具体vue做了些啥,我们今天来了解一下。

语述

了解之前,我们先贴上一张官网的生命周期图,从图上,我们再一步一步来理解vue生命周期。
在这里插入图片描述
我们先简单的来解说这张图,然后再通过例子来详看
首先,从图上,我们可以看出,他的一个过程是

  1. new Vue()实例化一个vue实例,然后init初始化event 和 lifecycle, 其实这个过程中分别调用了3个初始化函数(initLifecycle(), initEvents(), initRender()),分别初始化了生命周期,事件以及定义createElement函数,初始化生命周期时,定义了一些属性,比如表示当前状态生命周期状态得_isMounted ,_isDestroyed ,_isBeingDestroyed,表示keep-alive中组件状态的_inactive,而初始化event时,实际上就是定义了$once、$off、$emit、$on几个函数。而createElement函数是在初始化render时定义的(调用了initRender函数)
  2. 执行beforeCreate生命周期函数
  3. beforeCreate执行完后,会开始进行数据初始化,这个过程,会定义data数据,方法以及事件,并且完成数据劫持observe以及给组件实例配置watcher观察者实例。这样,后续当数据发生变化时,才能感知到数据的变化并完成页面的渲染
  4. 执行created生命周期函数,所以,当这个函数执行的时候,我们已经可以拿到data下的数据以及methods下的方法了,所以在这里,我们可以开始调用方法进行数据请求了
  5. created执行完后,我们可以看到,这里有个判断,判断当前是否有el参数(这里为什么需要判断,是因为我们后面的操作是会依赖这个el的,后面会详细说),如果有,我们再看是否有template参数。如果没有el,那么我们会等待调用$mount(el)方法(后面会详细说)。
  6. 确保有了el后,继续往下走,判断当有template参数时,我们会选择去将template模板转换成render函数(其实在这前面是还有一个判断的,判断当前是否有render函数,如果有的话,则会直接去渲染当前的render函数,如果没有那么我们才开始去查找是否有template模板),如果没有template,那么我们就会直接将获取到的el(也就是我们常见的#app,#app里面可能还会有其他标签)编译成templae, 然后在将这个template转换成render函数。
  7. 之后再调用beforMount, 也就是说实际从creted到beforeMount之间,最主要的工作就是将模板或者el转换为render函数。并且我们可以看出一点,就是你不管是用el,还是用template, 或者是用我们最常用的.vue文件(如果是.vue文件,他其实是会先编译成为template),最终他都是会被转换为render函数的。
  8. beforeMount调用后,我们是不是要开始渲染render函数了,首先我们会先生产一个虚拟dom(用于后续数据发生变化时,新老虚拟dom对比计算),进行保存,然后再开始将render渲染成为真实的dom。渲染成真实dom后,会将渲染出来的真实dom替换掉原来的vm.$el(这一步我们可能不理解,请耐心往下看,后面我会举例说明),然后再将替换后的$el append到我们的页面内。整个初步流程就算是走完了
  9. 之后再调用mounted,并将标识生命周期的一个属性_isMounted 置为true。所以mounted函数内,我们是可以操作dom的,因为这个时候dom已经渲染完成了。
  10. 再之后,只有当我们状态数据发生变化时,我们在触发beforeUpdate,要开始将我们变化后的数据渲染到页面上了(实际上这里是有个判断的,判断当前的_isMounted是不是为ture并且_isDestroyed是不是为false,也就是说,保证dom已经被挂载的情况下,且当前组件并未被销毁,才会走update流程)
  11. beforeUpdate调用之后,我们又会重新生成一个新的虚拟dom(Vnode),然后会拿这个最新的Vnode和原来的Vnode去做一个diff算,这里就涉及到一系列的计算,算出最小的更新范围,从而更新render函数中的最新数据,再将更新后的render函数渲染成真实dom。也就完成了我们的数据更新
  12. 然后再执行updated,所以updated里面也可以操作dom,并拿到最新更新后的dom。不过这里我要插一句话了,mouted和updated的执行,并不会等待所有子组件都被挂载完成后再执行,所以如果你希望所有视图都更新完毕后再做些什么事情,那么你最好在mouted或者updated中加一个$nextTick(),然后把要做的事情放在$netTick()中去做(至于为什么,以后讲到$nextTick再说吧)
  13. 再之后beforeDestroy没啥说的,实例销毁前,也就是说在这个函数内,你还是可以操作实例的
  14. 之后会做一系列的销毁动作,解除各种数据引用,移除事件监听,删除组件_watcher,删除子实例,删除自身self等。同时将实例属性_isDestroyed置为true
  15. 销毁完成后,再执行destroyed

示例

大致过程就是这样,下面我们来通过例子来看一看

<body>
    <div id="app">
        <p>{
    {
    message}}</p>
        <button @click="changeMsg">改变</button>
    </div>
</body>
<script>
    var vm = new Vue({
    
        el: '#app',
        data: {
    
            message: 'hello world'
        },
        methods: {
    
            changeMsg () {
    
                this.message = 'goodbye world'
            }
        },
        beforeCreate() {
    
            console.log('------初始化前------')
            console.log(this.message)
            console.log(this.$el)
        },
        created () {
    
            console.log('------初始化完成------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeMount () {
    
            console.log('------挂载前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        mounted () {
    
            console.log('------挂载完成---------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeUpdate () {
    
            console.log('------更新前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        updated() {
    
            console.log('------更新后---------')
            console.log(this.message)
            console.log(this.$el)
        }
    })
</script>

我们先看看首次加载时,输出了啥
在这里插入图片描述

从上面我们可以看出几点,

  • 首次,只执行了4个生命周期,beforeCreate,created, beforeMount, mounted。
  • 同时,我们可以看出,第一个生命周期中,我们拿不到data中的数据,因为这个时候数据还未初始化
  • created中,我们可以拿到data中的message数据了,因为初始化已经完成
  • beforeMount中,我们可以看出,我们拿到了$el,而mounted中,我们也拿到了$el, 不过好像有点不一样是吧。一个好像是渲染前的,一个是渲染后的。对的。看过MVVM响应式原来或者Vue源码你们就会发现,最初其实我们是会去让this.$el = new Vue时传入的那个el的dom。所以在beforMount中,其实我们拿到的就是页面中的#app。而再继续往后,首先我们是不是没有找到render函数啊,也没有找到template啊,所以他会怎么做啊,是不是会把我们的这个el(#app)编译成template模板啊,再转换为render函数,最后将render函数渲染成为真实dom,渲染成真实dom后,我们是不是会用这个渲染出来的dom去替换原来的vm.$el啊。这也就是我们前面所说到的替换$el是什么意思了。
  • 所以, 在mounted中,我们所得到的渲染完成后的$el。

下面我们再看个例子

var vm = new Vue({
    
        el: '#app',
        data: {
    
            message: 'hello world'
        },
        template: '<div>我是模板内的{
    {message}}</div>',
        methods: {
    
            changeMsg () {
    
                this.message = 'goodbye world'
            }
        },
        beforeCreate() {
    
            console.log('------初始化前------')
            console.log(this.message)
            console.log(this.$el)
        },
        created () {
    
            console.log('------初始化完成------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeMount () {
    
            console.log('------挂载前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        mounted () {
    
            console.log('------挂载完成---------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeUpdate () {
    
            console.log('------更新前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        updated() {
    
            console.log('------更新后---------')
            console.log(this.message)
            console.log(this.$el)
        }
    })

我们在new Vue实例的时候直接传入了一个template,这时候我们再看输出
在这里插入图片描述
这么看是不是就很清晰了啊 ,在beforeMount的时候,$el还是#app, 但是在mounted的时候就变成模板的div了,是不是因为我们传了个template啊,所以,他直接将这个template转换成render函数啦。再渲染成真实dom后,用渲染出来的真实dom替换了原来的$el。

下面我们删除上面的template, 点击按钮更改下message,查看输出
在这里插入图片描述
哎。。。有没有看到一个很奇怪的东西啊,在beforeUpdate中输出的$el居然和updated里面输出的是一样的。这不对啊,以我们上面所说的逻辑的话,beforeUpdate内的$el应该是更新前的啊。这是怎么回事呢。这时候我们先来看一下mounted里面的。mounted里面我们看到p标签内依旧是hello world 对不对,其实这是因为,我是先点击了#app那个div的箭头,将这个div展开了以后,我再点击的按钮去更改了message,所以mounted里面还是原来的。那我现在如果先不展开mounted里面的div的话,我们来看看会怎么样

在这里插入图片描述

可以看到,初始输出,其实是这样的,我们看不到#app内的东西,需要点击箭头展开才能看到,现在,我不展开,然后我先点击按钮去改变message, 等beforUpdate和updated都执行完成后,我们再来一起展开,看下会怎么样
在这里插入图片描述
这是点击改变了message后的截图,然后我们现在展开div看看
在这里插入图片描述
看到没有,我们发现什么啦,怎么现在mounted里面的$el也变成更新后的啦。
呵呵,不要慌,其实啊,因为this.$el是一个对象,其实本质就是一个指针,当我们刚console.log输出的时候,其实并没有显示内容,而当我们点击箭头去展开这个div的时候,将指针指向了当前的$el,所以我们看到的才会都是改变后的$el。这也就是为什么之前mounted里面的$el是改变之前的值,而现在是改变之后的值了,因为之前那张图,我是先展开了mounted中的div,再去改变的message。下面我们再来验证下是不是这么回事
怎么验证,我们修改下代码

mounted () {
    
            console.log('------挂载完成---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        },
        beforeUpdate () {
    
            console.log('------更新前---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        },
        updated() {
    
            console.log('------更新后---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        }

我们增加一个输出 this.$el.innerHTML, 再查看结果
在这里插入图片描述

这么看是不是就很明了啦,beforeUpdate里面的$el的内容,确实还是改变之前的,而我们之前看到的,只是因为我们后面展开时指针指向了当前值才导致的,是个视觉差而已。

后面两个销毁的,我就不举例说明了,没啥说的。下面我们再看一个问题,就是如果我们没有设置el时,会怎么样,我们在之前的生命周期图中,是说过,当没有找到el时, 说是不是会等待vm.$mount(el) 啊,这句话啥意思,我们来看一下
在这里插入图片描述
首先,我们看下,vue源码中,
在这里插入图片描述
在执行完,beforeCreate和created之后,是做了个判断,当存在el时,调用了 $mount方法,created之后的步骤,就是在这里面去走的。那如果没有el呢, 生命周期图中是说等待vm. $mount调用。那是不是只能等待我们手动去调用啊。

var vm = new Vue({
    
        data: {
    
            message: 'hello world'
        },
        // template: '<div>我是模板内的{
    {message}}  <button @click="changeMsg">点我</button></div>',
        methods: {
    
            changeMsg () {
    
                this.message = 'goodbye world'
            }
        },
        beforeCreate() {
    
            console.log('------初始化前------')
            console.log(this.message)
            console.log(this.$el)
        },
        created () {
    
            console.log('------初始化完成------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeMount () {
    
            console.log('------挂载前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        mounted () {
    
            console.log('------挂载完成---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        },
        beforeUpdate () {
    
            console.log('------更新前---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        },
        updated() {
    
            console.log('------更新后---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        }
    })

这个时候,我们删除了el属性,看看结果
在这里插入图片描述
是不是只走了前面两个生命周期啊,后面就没走了,这个时候其实就是在等$mount被调用了,那我们加个按钮,点击按钮,手动调用一下$mount看会怎样
在这里插入图片描述
没点击之前
在这里插入图片描述
点击后
在这里插入图片描述
可以看到,生命周期继续往下走了。
这时候不知道大家是不是想起来,看到有些vue项目的main.js里面是这样的

export default new Vue({
    
  el: '#app',
  router,
  store,
  i18n,
  render: h => h(App)
})

而有些vue项目中人家用的又是这样的

export default new Vue({
    
  router,
  store,
  i18n,
  render: h => h(App)
}).$mount('#app')

其实后者,就相当于是手动调用了$mount了。

好了,言尽于此,有没有看懂的朋友,请直接私信或者评论。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42707287/article/details/111641286

智能推荐

攻防世界 Pwn 进阶 第二页_pwn snprintf-程序员宅基地

文章浏览阅读581次,点赞2次,收藏5次。00为了形成一个体系,想将前面学过的一些东西都拉来放在一起总结总结,方便学习,方便记忆。攻防世界 Pwn 新手攻防世界 Pwn 进阶 第一页01 4-ReeHY-main-100超详细的wp1超详细的wp203 format2栈迁移的两种作用之一:栈溢出太小,进行栈迁移从而能够写入更多shellcode,进行更多操作。栈迁移一篇搞定有个陌生的函数。C 库函数 void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 _pwn snprintf

浅谈数据库----基于SQLsever_实现一个基于sql server数据库的应用(可以改用其他数据库),要求实现基本的crud(增-程序员宅基地

文章浏览阅读6.2k次,点赞2次,收藏5次。浅谈数据库—数据库系统的概述1.基础概念解释1.数据与信息(1)信息的定义。人脑对现实世界事物的存在方式、运动状态以及事物之间的抽象反映。信息是客观存在的,人类有意识的对信息进行采集加工、传递,从而形成了各种消息、情报、指令、数据以及信号。(2)数据的定义。数据是由用来记录信息的可识别的符号组合的,是信息的具体表现形式。2.数据处理与数据管理​ 数据处理是将数据转换成信息的过程,包括..._实现一个基于sql server数据库的应用(可以改用其他数据库),要求实现基本的crud(增

关于在PLSQL中实现DEBUG调试功能的方法_plsql debug-程序员宅基地

文章浏览阅读1.2w次。关于在PLSQL中实现DEBUG调试功能的方法2017年04月07日 14:27:52 samt007 阅读数:2179 标签: oracle调试plsql 更多个人分类: Oracle PL/SQL技巧前言 一个健康的PLSQL,应该都带有一套完整的调试逻辑。特别是那些功能很复杂的PLSQL,就更加有必要具备调试功能了。否则,当PLSQL处理数据出现问题的时候,分析(处理)起来会相..._plsql debug

R文件资源找不到,报错_r资源怎么找-程序员宅基地

文章浏览阅读885次。R文件报错,导致整个项目报红,清理工程,重新编译都不能解决,下面两种方法不能解决 Clean Project Rebuild Project 打开Android Studio菜单的Help然后点击Edit Custom Properties再写入# custom Android Studio propertiesidea.max.intellis..._r资源怎么找

opencv截取图片的某一部分的信息_opencv提取快递地址-程序员宅基地

文章浏览阅读1.6k次。已知一张图片宽为1024,高为768.img = img[80:550, 430:650];80~550代表取得的高度范围。430~650代表取得的宽度范围。_opencv提取快递地址

Day1:OpenCV安装及处理图像_学图像处理装opencv哪个版本-程序员宅基地

文章浏览阅读92次。Day1:OpenCV安装及处理图像00 安装在Prompt中输入pip install opencv-python,如果报错找不到匹配的版本可以在清华镜像中寻找合适版本,放到Scripts文件夹中,然后打开到该路径下用pip install xxx.whl安装,可以通过import cv2验证是否安装成功,若依然报错,可能是缺少MSVCP140.DLL模块,可以在官网下载。01 读写图片import cv2import numpy as np# 读入图像img = cv2.imread('i_学图像处理装opencv哪个版本

随便推点

题解 | #删除字符串中出现次数最少的字符#用pytho字典-程序员宅基地

文章浏览阅读783次,点赞20次,收藏18次。国微前三年总包每年都比华子高将近20w,但是担心国微是反向,而且是做特种装备的,不好跳槽。1月底参加了京东方的全球博士专项活动,报销来回路费,然后去进行了一次比较简单的面试,准备了ppt,没机会讲,就问了一些专业方面的问题,没想到后续就不用再面试了,将对象转换为数组,数组中的每个元素都是一个对象,包含对象的属性以及对象的父节点和子节点。s = str(input())根据题意,把所有英文做了对应映射的字典,找到字典中的字符,转换成str加到字符串尾,如果不是,直接加就行了maping = {'

学员心得 | 关于跨专业拿下数通HCIE这件“小”事儿_hcie开班-程序员宅基地

文章浏览阅读325次。“敢破敢立,即有无限可能。”本期供稿:文同学编辑:小誉大家好,我是誉天数通班的文同学。终于拿到了期盼已久的华为HCIE R&S证书,心里久久不能平静。回想起这半年的HCIE学习之路,有艰辛,也有收获成功后的喜悦。整个备考过程几乎不亚于当年的升学考试。对于非网络专业的我来说,面对华为最高等级的认证,难度大、挑战大,压力也非常大。也正因为如此,我一度非常庆幸自己的选择——到誉天来学习。成为誉天大家族的一份子,是我考证道路上迈出的最正确一步。这里要感谢誉天,也要特别感谢胡老师和大祥老_hcie开班

什么支撑了5G基站的飞跃式发展? 5G基站建设新变化,超高可靠低时延通信,通信响应速度将降至毫秒级。_5g 终端响应-程序员宅基地

文章浏览阅读404次。4G改变生活,5G改变社会,只是这个改变并没那么容易。2020年是ITU所定义的全球5G商用元年,而中国则还要早一年。据中国信息通信研究院,2021年1~4月国内5G手机出货量为9126.7万部,占市场总体的72.7%,同比增长38.4%。这在一定程度上反映了5G通信在个人用户层面的推进速度。但5G不止于手机,在万物互联时代,必须提前搭建好一条条高速路,5G因此无可争议地成为新基建之首。相比4G,5G在初始阶段就明确规划了三大应用场景:增强移动宽带,其峰值速率将是4G网络的10倍以上;海量机器通信,_5g 终端响应

Flask-RESTFul API 和 Blueprint 的结合_flask-rest + blueprint-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏11次。首先展示一下项目目录树:.├── frf_demo # 存放整个项目│ ├── apps # 存放应用模块包│ │ ├── cart # 购物车模块│ │ │ ├── __init__.py # 创建cart应用蓝图│ │ │ ├── models.py # 数据库模型│ │ │ ├── urls.py # 创建api,进行..._flask-rest + blueprint

计算机网络(五)和ctf做题(四)_telnet ctf-程序员宅基地

文章浏览阅读1.2k次。应用层域名系统DNS域名系统DNS(Domain Name System)是互联网使用的命名系统,用来把便于人们使用的机器名字转换为IP地址。互联网的域名系统DNS被设计成为一个联机分布式数据库,并采用客户服务器方式。互联网的域名结构互联网采用层次树状结构的命名方法,采用这种命名方法,任何一个连接在互联网上的主机或路由器,都有一个唯一的层次结构的名字,即域名(Domain Name)。“..._telnet ctf

归并排序求逆序数的个数_即逆置数的个数;-程序员宅基地

文章浏览阅读515次,点赞2次,收藏6次。描述在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。现在,给你一个N个元素的序列,请你判断出它的逆序数是多少。比如 1 3 2 的逆序数就是1。格式输入格式第一行输入一个整数T表示测试数据的组数(1<=T<=5)每组测试数据的每一行是一个整数N表示数列中共有N个元素(2〈=N〈=100000)随后的一行共有N个整数Ai(0<=Ai<1000000000),表示数列中的所有元素_即逆置数的个数;