Vue – 基础学习(1):对生命周期和钩子函的理解

1、简介

  先贴一下官网对生命周期/钩子函数的说明(先贴为敬):全部的生命周期钩子自动绑定 this 上下文到实例中,所以你能够访问数据,对属性和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。这是由于箭头函数绑定了父上下文,所以 this 与你期待的 Vue 实例不一样,this.fetchTodos 的行为未定义。javascript

  上面是官方文档对生命周期/钩子函数的总览介绍。若是单看这个总览介绍,绝对是一头雾水,不清不楚看不出个因此然。虽然文档后面对各个钩子函数的使用有具体说明,但具体实例却不是很清楚,因此在玩了一段时间的Vue项目后,闲来打算本身总结下生命周期和钩子函数的使用。下面先来一张官方生命周期图示:css

  

  生命周期:描述Vue实例或组件从建立到销毁(包括销毁前和销毁)的所有经历和过程。就像人同样,从母亲怀胎开始,而后出生,成长,衰老,一直到回光返照(销毁前),最后死去一把火(销毁)回归大天然,着重是介绍一种经历和过程html

  钩子函数:钩子函数则是Vue实例或组件在生命周期过程当中各个阶段自执行的回调函数。就如同新生儿出生后,饿了他会哭,上学途中被高年级学生欺负了会找家长告状,长大了要出去挣钱养家,老了会戴老花镜同样。在不一样的阶段Vue实例或组件内部,结构也在发生着变化,随着节点结构的变化就须要执行一些特定的钩子函数,去继续下一步变化和新节点的创建,也正是这些钩子函数的执行为实际开发过程当中可以添加自定义功能提供了入口。vue

 

  下面结合官方文档先对各个钩子函数作一个简略的总结。java

 

2、代码实测

   各个钩子函数的执行位置以及执行时间点,在上面的官方生命周期图示中已经标注得很清楚,下面经过代码实测来逐个加深认识。测试代码以下:ajax

<!DOCTYPE html>
<html>
<head>
    <title>Vue – 基础学习(1):对生命周期和钩子函的理解</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="app">
    <div>静态元素</div>
    <div style="margin-top: 5px">{{ testInfor }}</div>
    <div style="margin-top: 20px">
        <button @click.stop="editTestInfor">更新内容</button>
        <button @click.stop="destroyedNode">销毁实例</button>
    </div>
</div>

<script type="text/javascript" src="https://cdn.bootcss.com/vue/2.5.20/vue.min.js"></script>
<script type="text/javascript">
    new Vue({
        el: '#app',
        data() {
            return {
                testInfor: '测试信息!',
            };
        },
        beforeCreate() {
            console.group('beforeCreate:实例建立完成,但数据对象data、属性、event/watcher事件均未完成配置和初始化。挂载阶段还未开始,$el属性未初始化,$el元素不可见========》');
            console.log(this);                                                                      // object
            console.log('%c%s', 'color:red', 'el     : ' + this.$el);                               // undefined
            console.log(this.$el);                                                                  // undefined
            console.log('%c%s', 'color:red', 'data   : ' + this.$data);                             // undefined
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // undefined
            this.testFuntion('beforeCreate');                                                       // undefined    this.testFuntion is not a function
            debugger;
        },
        created() {
            console.group('created:实例数据对象data、属性、event/watcher事件均配置和初始化完成。但挂载阶段还未开始,$el属性未初始化,$el元素不可见=========================》');
            console.log(this);                                                                      // object
            console.log('%c%s', 'color:red', 'el     : ' + this.$el);                               // undefined
            console.log(this.$el);                                                                  // undefined
            console.log('%c%s', 'color:red', 'data   : ' + this.$data);                             // 初始化完成
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 初始化完成
            this.testFuntion('created');                                                            // event事件初始化完成
            debugger;
        },
        beforeMount() {
            console.group('beforeMount:在开始挂载以前被调用,相关的render函数首次被调用。$el属性初始化完成,但处于虚拟dom状态,具体的data.filter还没有替换,$el元素可见=======》');
            console.log(this);                                                                      // object
            console.log('%c%s', 'color:red', 'el     : ' + this.$el);                               // $el属性初始化完成
            console.log(this.$el);                                                                  // 节点挂载完成,但数据还没有渲染,处于虚拟DOM状态
            console.log('%c%s', 'color:red', 'data   : ' + this.$data);                             // 已被初始化
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 已被初始化
            this.testFuntion('beforeMount');                                                        // event事件已被初始化
            debugger;
        },
        mounted() {
            console.group('mounted:挂载完成,data.filter成功渲染,页面总体渲染完成,可进行DOM操做===================》');
            console.log(this);                                                                      // object
            console.log('%c%s', 'color:red', 'el     : ' + this.$el);                               // $el属性已被初始化
            console.log(this.$el);                                                                  // 节点挂载完成,数据渲染成功,页面所有渲染完成
            console.log('%c%s', 'color:red', 'data   : ' + this.$data);                             // 已被初始化
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 已被初始化
            this.testFuntion('mounted');                                                            // event事件已被初始化
            debugger;
        },
        beforeUpdate() {
            console.group('beforeUpdate:页面依赖的参数数据更改以后,DOM结构从新渲染以前触发执行(此时DOM结构尚未从新渲染)=============》');
            console.log(this.$el);
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 点击按钮调用方法更新后的数据
            this.testFuntion('beforeUpdate');
            this.testInfor = 'beforeUpdate修改后的信息!';                                           // 在beforeUpdate函数内再次修改页面依赖参数数据
            console.log('%c%s', 'color:blue', 'data   : ' + this.testInfor);                        // 在beforeUpdate函数内修改后的数据
            debugger;
        },
        updated() {
            console.group('updated:数据更新完成=====================================================================》');
            console.log(this.$el);
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 最终更新后数据
            this.testFuntion('updated');
            debugger;
        },
        beforeDestroy() {
            console.group('beforeDestroy:实例或组件销毁以前调用,在这一步,实例或组件仍然彻底可用===================》');
            console.log(this.$el);
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);
            this.testFuntion('beforeDestroy');                                                      // 此时实例内功能函数功能依然正常
            this.testInfor = 'beforeDestroy修改后的信息!';                                          // 在beforeDestroy函数内再次修改页面依赖参数数据,用以验证beforeUpdate和updated函数是否还监听执行
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 在beforeDestroy函数内修改后的数据
            debugger;
            // 此时实例、组件虽然页面结构完整,各类功能正常。但,页面依赖参数更新后生命周期函数beforeUpdate和updated均再也不执行,说明实例或组件的销毁一旦启动则不可逆转或中途打断。
        },
        destroyed() {
            console.group('destroyed:实例或组件已被销毁=============================================================》');
            console.log(this.$el);
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 在beforeDestroy函数内修改的页面依赖参数,依然能正确读取
            this.testFuntion('destroyed');                                                          // 此时实例内功能函数功能依然正常
            debugger;
            // 此时虽然"beforeDestroy"执行完毕,但实例指向的全部东西(参数,方法等)还没有解绑。因此此时实例内各参数、方法功能依然正常。等待"destroyed"执行完毕后,全部的东西才会解绑,尘归尘,土归土。
        },
        methods: {
            testFuntion(type) {
                console.log('当前运行钩子函数:' + type);
            },

            editTestInfor() {
                this.testInfor = '修改后的信息!';
            },

            destroyedNode() {
                this.$destroy();
            }
        }
    });
</script>
</body>
</html>

  1. beforeCreate 和created

    beforeCreate:实例建立完成,但数据对象data、属性、event/watcher事件均未完成配置和初始化。挂载阶段未开始,$el属性还没有初始化,$el属性不可见,$el元素不可见。缓存

     

    created:实例数据对象data、属性、event/watcher事件均配置和初始化完成。但挂载阶段还没有开始,$el属性未初始化,$el属性不可见,$el元素不可见。session

     

    小结:虽然此时$el属性还没有初始化,页面元素不可见,但数据对象data、属性、event/watcher事件均已配置和初始化完成,因此一些须要先页面执行的方法(如ajax请求,页面功能权限检测(页面是否能加载、页面依赖参数是否合法)和配置(如按钮点击权限等))在created阶段能够执行,但不容许操做DOM节点和调用操做DOM节点的方法(页面总体结构未渲染完成)。app

 

  2. beforeMount和mounted

    beforeMount:在开始挂载以前被调用,相关的render函数首次被调用。$el属性初始化完成,$el属性可见,el元素可见。但此时el节点并无渲染进数据,el节点尚处于“虚拟”节点状态,可看到仍是取值表达式{{testInfor }}。这就是Virtual DOM(虚拟Dom)的巧妙之处,先占坑,而后到mounted挂载阶段时再渲染值。dom

    

    mounted :节点挂载完成,数据成功渲染,页面总体渲染完成,实例或组件彻底成熟,可进行DOM操做。

       

 

  3. beforeDestroy 和 destroy

    人生看似很漫长,但在不经意之间就走向了她的终点。Vue实例或组件也同样,在经历了多姿多彩的绚烂时光后,它也逐渐走向了它生命的终点。这里提一下为啥先不说 beforeUpdate 和 updated 而是直接跳到 beforeDestroy destroy,由于 beforeUpdate updated 不是生命周期过程当中必须执行的钩子函数。beforeUpdate updated 是基于组件内数据发生变化时触发执行,若是当期实例或组件内数据只是进行显示,不进行任何修改,那么这两个钩子函数将一直不会被触发,也就不会被执行。

    beforeDestroy:实例或组件销毁以前调用,在这一步,实例或组件内各参数,方法功能依然完整,实例仍彻底可用

     

    destroy:Vue实例或组件销毁后调用。调用后,Vue 实例指示的全部东西都会自动解绑,全部的事件监听器会被移除,全部的子实例也会被销毁。

    

    实例或组件销毁完成后,再次点击“更新内容”按钮,此时系统再也不作任何响应,但,已渲染完成的Dom结构和节点元素依然存在,因此当执行完destroy操做后,实例或组件就再也不受Vue系统控制。此时Vue实例或该子组件已经不存在了,实例或组件内的各参数,属性,方法均已被内存回收清空。

    小结:beforeDestroy阶段,此时实例、组件虽然页面结构完整,各类功能正常,但,页面依赖参数更新后生命周期函数beforeUpdate和updated均再也不执行说明实例或组件的销毁过程一旦启动则不可逆转或中途打断

    destroy阶段,此时虽然"beforeDestroy"执行完毕,但实例指向的全部东西(参数,方法等)还没有解绑。因此此时实例内各参数、方法功能依然正常。等待"destroyed"执行完毕后,全部的东西才会被解绑,资源被回收。尘归尘,土归土,从哪来回哪去!

    到此为止,Vue实例或组件从开始初始化到最终销毁,数据清空的六个钩子函数均测试完毕。这六个钩子函数是实例或组件生命周期历程中最主要的六个钩子函数,也是必须执行的六个函数,没法绕过

 

  4. beforeUpdate 和 updated

    如今,返回来看beforeUpdate 和 updated。Vue实例或组件在挂载完成后就标志着功能健全,功能健全的组件就如成年的人生同样丰富多彩,每时每刻均可能发生变化。接下来经过修改testInfor的值,来看看beforeUpdate和updated都各自作了什么。

点击页面“更新内容”按钮,修改testInfor的值。

    beforeUpdate:页面依赖的参数数据更改以后,虚拟DOM从新渲染和打补丁以前执行(此时DOM结构还未从新渲染)。

     

    updated:页面依赖的参数数据更改以后,beforeUpdate钩子函数执行完毕,会当即进行DOM结构的从新渲染。DOM结构渲染完成以后才会调用updated钩子函数,而不是渲染时就调用

     

    

    此图就能够彻底看出,调用updated时组件DOM结构已从新渲染完成,因此此时updated函数内是能够进行相关DOM操做的。

 

    小结:在debugger beforeUpdate钩子函数时发现一个小细节,既然beforeUpdate是在页面依赖数据修改以后,虚拟DOM从新渲染以前执行,那么我在beforeUpdate函数内,是能够对依赖数据进行再次修改的,而不会致使多重渲染,也不会屡次调用updated函数。

     

    

    从上两图能够看出,即便在beforeUpdate函数内修改无数次页面依赖参数数据,组件Dom结构也只会从新渲染一次,即 将最后修改的依赖参数数据渲染到对应节点,updated函数也只会执行一次。只是这样作没有多大实际意义,毕竟其余地方调用其余方法更新后的数据,是页面功能需求的数据,在beforeUpdate这又瞎改一通,于功能于系统毫无益处。固然你胆肥不怕死,整一些恶搞和乱操做仍是能够玩的。

    虽然beforeUpdate和updated是基于页面依赖参数数据更改后触发和执行,对于页面依赖参数的变化能够起到监控做用,以及在参数变化以后执行其余后续操做,但,它们没法断定是哪一个参数发生了变化。虽然每次参数数据变化以后能够经过比较各个参数值的先后值是否相等来断定是哪一个参数发生了变化,但,那是基于参数量少,参数数据类型是基本数据类型的状况。一旦须要监控的参数量大,参数数据类型复杂,beforeUpdate和updated就将变得很难处理。因此实际开发过程当中,除非一些特别的参数和操做,绝大部分参数的更新监听和后续操做,都是使用watch对象进行监听,于是在实际开发过程当中beforeUpdate和updated使用得至关少。

    另外Vue是数据驱动页面刷新,因此必然是在数据更新以后系统才会驱动虚拟DOM View层的刷新,于是beforeUpdate必然是在参数数据更新以后View(视图)层数据(节点内数据)更新以前触发

 

3、总结

  整体而言,生命周期函数虽然有这么多个,但实际开发过程当中使用最频繁的也就那么几个,如:created,mounted,beforeDestory,destoryed。开发人员能够:

  在created内进行:页面是否加载 权限断定或页面依赖参数初始化(如按钮权限配置)、ajax数据请求、自执行函数调用等操做。

  在mounted内进行:数据过滤、数据渲染赋值(以下拉框选项赋值)、DOM节点操做等功能。

  在beforeDestroy内进行:参数断定,肯定当前页面是否容许切换或刷新、必要数据缓存,操做记录上传等操做。

  在destoryed内进行:清除当前页面其余缓存数据,如sessionStorage、定时器等。

  而其余生命周期函数,并非说它们就不重要,只是它们在日常的开发过程当中,使用得不是那么频繁而已。它们也有它们本身独特的用处和用法,因此对于生命周期和生命周期函数的善加利用,可让实际开发事半功倍,并收到良好的效果。 

转载于:https://www.cnblogs.com/donghuang/p/10840584.html