谈谈Vue的生命周期

一、引入生命周期

        Vue的生命周期,指Vue的实例从初始化创建到最终销毁的整个过程。

        在整个生命周期中的各个阶段,会执行各个阶段所对应的一些特殊命名(不可修改)的回调函数,我们称之为生命周期钩子,引入官方文档Vue生命周期的流程图如下:

         Vue生命周期共有八个生命周期钩子,下面我通过这八个生命周期钩子对生命周期的流程进行大致分析:

二、生命周期流程分析

1.初始

初始阶段流程(如图):

         当我们通过new Vue()创建Vue实例时,生命周期还未真正开始,下面我们进入第一个环节:Init Events & Lifecycle: 初始化Vue中的许多事件,做准备工作,生命周期将从这里开始。就好比与新生的婴儿呱呱坠地,此刻正由护士天使进行各项检查及清洗等准备工作。

        在初始化(Init)生命周期(Lifecycle)及各项事件(Events)的准备工作完成后,诞生了我们第一个生命周期钩子:beforeCreate(),当它执行完毕后,我们进入了下一个环节:Init injections & reactivity:初始化Vue中的数据监测和数据代理(通过实例对象代理data中的数据)下面介绍beforeCreate()


beforeCreate()

        顾名思义,就是“创建前”,创建的是啥呢?通过以上流程图及文字分析,创建的是Vue中的数据监测和数据代理。该钩子函数横在了Init Events & Lifecyclenit injections & reactivity环节的中间,也就是在该钩子函数执行时,Vue还未进行数据监测和数据代理的环节

举个栗子:

<script>
    Vue.config.productionTip = false;
    const vm = new Vue({
        el:'#root',
        data: {
            n:1
        },
        methods: {
            add() {
                this.n++;
            }
        },
        beforeCreate() {
            console.log('此时数据代理还未开始,无法通过vm访问data中数据和methods中方法!');
            console.log(this);
            debugger;    
        }
    });
</script>

运行结果:

分析:我们在钩子函数beforeCreate()中打印Vue实例对象并设置断点(防止继续执行,Vue走完它的生命周期),打开控制台,我们发现date中的数据 n 及methods中的 add() 方法还未在实例对象vm中找到,说明此时Vue还未对配置项中的数据进行监测和代理,于是我们取消断点继续执行,让Vue走完整个流程,可以发现实例对象vm已经存有了数据n及方法add(),如下图:

created()

        同理,就是“创建后”,由生命周期图分析:也就是初始化数据监测和数据代理完毕后执行该生命周期钩子,此时我们已经可以通过Vue实例对象访问Vue中的数据了

举个栗子:

<script>
    Vue.config.productionTip = false;
    const vm = new Vue({
        el:'#root',
        data: {
            n:1
        },
        methods: {
            add() {
                this.n++;
            }
        },
        beforeCreate() {
            console.log('此时数据代理还未开始,无法通过vm访问data中数据和methods中方法!');
            console.log(this);
        },
        created() {
            console.log('此时可以通过vm访问data中数据和methods中方法!');
            console.log(this);
        }
    });
</script>

运行结果:


        在执行完以上两个生命周期钩子后,Vue实例已经实现了数据监测和代理。我们顺着初始阶段流程图往下走,就正式进入了解析模板生成虚拟Dom的环节,图中进行了流程判断如下:

  1. 是否有el配置项(有)—>是否有template(模板)项(无)—>编译el的外部HTML作为模板(Compile el's outerHtml as template)
  2. 是否有el配置项(有)—>是否有template(模板)项(有)—>编译该模板到渲染函数render()中(Compile template in render function)
  3. 是否有el配置项(无)—>等待Vue实例调用$mount来传入el参数(when vm.$mount(el) is called),从而指定Vue服务的容器—>执行1、2步骤

        至此,Vue生命周期的初始阶段便结束了,我们进行以下总结:

  1. Init Events & Lifecycle: 初始化Vue中的许多事件,做准备工作,生命周期将从开始。
  2. 执行生命周期钩子:beforeCreate()
  3. Init injections & reactivity:初始化Vue中的数据监测和数据代理
  4. 执行生命周期钩子:created()
  5. 解析模板:生成虚拟Dom

2.挂载

挂载阶段流程(如图):

         当走过了Vue的初始时期,Vue实例对象已经对Vue中的数据进行监测和代理,同时也已经通过解析模板在内存中生成了虚拟Dom,于是乎,我们来到了Vue生命周期的第二个时期——挂载

        一上来,我们便开始执行第三个生命周期钩子——beforeMount()


beforeMount()

        此时页面呈现的是未经Vue编译的Dom结构,也就是我们前一章节所说的虚拟Dom还未生成真实Dom渲染在页面上

举个栗子:

<div id="root">
    <h2>当前的n为:{{n}}</h2>
    <button @click="add">点我n++</button>
</div>
<script>
    Vue.config.productionTip = false;
    const vm = new Vue({
        el:'#root',
        data: {
            n:1
        },
        methods: {
            add() {
                this.n++;
            }
        },
        beforeCreate() {
            console.log('此时数据代理还未开始,无法通过vm访问data中数据和methods中方法!');
        },
        created() {
            console.log('此时可以通过vm访问data中数据和methods中方法!');
        },
        beforeMount() {
            console.log('此时已经解析完模板生成了虚拟Dom(内存中),但还没在页面编译真实Dom!')
            debugger;
        }
    });
</script>

运行结果:

 分析:可见,在生命周期钩子beforeMount()执行时期,内存中虚拟Dom还未转为真实Dom渲染在页面中,因此页面中模板语法中的数据n还未被渲染。

        毋庸置疑,执行下一个环节:Create vm.$el and replace "el" with it 想必再清楚不过了,那就是开始渲染真实Dom了。让我们沿着Vue的生命线继续向下走。接下来便开始执行第四个生命周期钩子——mounted()

mounted()

        这个时期,页面呈现的是初次经过Vue编译而渲染在页面上的真实Dom,此时也叫做挂载完毕,此时此刻万象更新,呈现出万物竞发、勃勃生机的态势,一般选择在这个时期开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作

举个栗子:

<div id="root">
    <h2>当前的n为:{{n}}</h2>
    <button @click="add">点我n++</button>
</div>
<script>
    Vue.config.productionTip = false;
    const vm = new Vue({
        el:'#root',
        data: {
            n:1
        },
        methods: {
            add() {
                this.n++;
            }
        },
        beforeCreate() {
            console.log('此时数据代理还未开始,无法通过vm访问data中数据和methods中方法!');
        },
        created() {
            console.log('此时可以通过vm访问data中数据和methods中方法!');
        },
        beforeMount() {
            console.log('此时已经解析完模板生成了虚拟Dom(内存中),但还没在页面编译真实Dom!')
        },
        mounted() {
            console.log('此时页面已经通过虚拟Dom渲染为真实Dom了,你可以开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作啦!');
            debugger;
        },
    });
</script>

运行结果:

        至此,Vue生命周期的挂载阶段便结束了,我们进行以下总结:

  1. 执行生命周期钩子:beforeMount()
  2. Create vm.$el and replace "el" with it : 渲染真实Dom
  3. 执行生命周期钩子:mounted()

3.更新

更新阶段流程(如图):

        

        当走过了Vue生命周期的挂载时期,我们已经可以对页面中的Dom节点进行操作了,但是除了操作,要实现对页面中数据的更新,那就顺着Vue的生命周期来到了第三个时期——更新

        根据流程图,一上来,when data changes,就是当页面中的数据发生变化时,执行第五个生命周期钩子:beforeUpdate()


beforeUpdate()

        顾名思义,就是“更新前”,当页面数据发送变化时,也就是说此时页面还是旧的,但数据是新的页面尚未和数据保持同步的时期执行的该生命周期钩子

举个栗子:

<div id="root">
    <h2>当前的n为:{{n}}</h2>
    <button @click="add">点我n++</button>
</div>
<script>
    Vue.config.productionTip = false;
    const vm = new Vue({
        el:'#root',
        data: {
            n:1
        },
        methods: {
            add() {
                this.n++;
            }
        },
        beforeCreate() {
            console.log('此时数据代理还未开始,无法通过vm访问data中数据和methods中方法!');
        },
        created() {
            console.log('此时可以通过vm访问data中数据和methods中方法!');
        },
        beforeMount() {
            console.log('此时已经解析完模板生成了虚拟Dom(内存中),但还没在页面编译真实Dom!')
        },
        mounted() {
            console.log('此时页面已经通过虚拟Dom渲染为真实Dom了,你可以开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作啦!');
        },
        beforeUpdate() {
            console.log('此时数据已经更新,但页面还没更新');
            debugger;
        },
    });
</script>

运行结果:

 

 分析:此时我们通过在页面中点击按钮使得n++,同样设置断点,可以在控制台发现执行了该钩子函数,但页面中的数据还未发送变化,打开Vue开发者工具发现此时数据n已经发生变化

        那么页面的数据需要更新鸭!该咋办呢,根据流程图,进入下一个时期——Virtual Dom re-render and patch,就是前一章所说的根据新数据生成新的虚拟Dom,随后与旧的虚拟Dom进行比较,最终完成页面的更新。

updated()

        顾名思义,“更新后”,这个时期,结果新旧虚拟Dom的一系列比较后,已经生成了真实Dom对页面的数据进行了更新。此时执行该生命周期钩子。

举个栗子:

<div id="root">
    <h2>当前的n为:{{n}}</h2>
    <button @click="add">点我n++</button>
</div>
<script>
    Vue.config.productionTip = false;
    const vm = new Vue({
        el:'#root',
        data: {
            n:1
        },
        methods: {
            add() {
                this.n++;
            }
        },
        beforeCreate() {
            console.log('此时数据代理还未开始,无法通过vm访问data中数据和methods中方法!');
        },
        created() {
            console.log('此时可以通过vm访问data中数据和methods中方法!');
        },
        beforeMount() {
            console.log('此时已经解析完模板生成了虚拟Dom(内存中),但还没在页面编译真实Dom!')
        },
        mounted() {
            console.log('此时页面已经通过虚拟Dom渲染为真实Dom了,你可以开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作啦!');
        },
        beforeUpdate() {
            console.log('此时数据已经更新,但页面还没更新');
        },
        updated() {
            console.log('此时页面与数据都是新的啦!');
        },
    });
</script>

运行结果:

  分析:此时我们通过在页面中点击按钮使得n++,发现页面数据实现了更新,同时在控制台输出结果观测到在页面数据更新前后的两个更新阶段的生命周期钩子beforeUpdate()和updated()均已执行。

        至此,Vue生命周期的更新阶段便结束了,我们进行以下总结:

  1. 执行生命周期钩子:beforeUpdate()
  2. Virtual Dom re-render and patch:新旧虚拟Dom对比,渲染真实Dom,更新页面数据
  3. 执行生命周期钩子:updated()

4.销毁

销毁阶段流程(如图):

         当走过了Vue生命周期的更新时期,我们已经可以操作页面中的Dom节点致使Vue可以实现对页面数据进行更新的操作了。下面我们就进入了Vue生命周期的最后一个时期——销毁时期

        一上来先进入一个判断环节,when vm.$destroy() is called,即当Vue实例对象调用$destroy()时,进入下一个阶段,执行第七个生命周期钩子——beforeDestroy()


beforeDestroy()

        顾名思义,“销毁前”,此时vm中的所有配置项、指令等等都处于可用状态,马上要执行销毁过程,在执行销毁过程前执行的生命周期钩子,一般选择在这个阶段关闭定时器、取消订阅消息、解绑自定义事件等收尾操作

举个栗子:

<div id="root">
    <h2>当前的n为:{{n}}</h2>
    <button @click="add">点我n++</button>
    <button @click="bye">点我销毁vm</button>
</div>
<script>
    Vue.config.productionTip = false;
    const vm = new Vue({
        el:'#root',
        data: {
            n:1
        },
        methods: {
            add() {
                this.n++;
            },
            bye() {
                this.$destroy();
            }
        },
        beforeCreate() {
            console.log('此时数据代理还未开始,无法通过vm访问data中数据和methods中方法!');
        },
        created() {
            console.log('此时可以通过vm访问data中数据和methods中方法!');
        },
        beforeMount() {
            console.log('此时已经解析完模板生成了虚拟Dom(内存中),但还没在页面编译真实Dom!')
        },
        mounted() {
            console.log('此时页面已经通过虚拟Dom渲染为真实Dom了,你可以开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作啦!');
        },
        beforeUpdate() {
            console.log('此时数据已经更新,但页面还没更新');
        },
        updated() {
            console.log('此时页面与数据都是新的啦!');
        },
        beforeDestroy() {
            console.log('vm中的配置项、指令等等即将销毁,请关闭定时器、解绑订阅消息、解绑自定义事件等收尾操作!');
            debugger;
        }
    });
</script>

运行结果:

   分析:通过对按钮绑定点击事件调用vm.$destroy(),点击按钮销毁Vue实例对象,同样设置断点,执行该生命周期钩子,此时还未真正销毁实例对象,提示进行最终销毁前的收尾工作。

destroyed()

        同理,就是“销毁后”,根据流程图,当销毁环节前的生命周期钩子beforeDestroy()执行后便进入了下一个阶段——Teardown watchers, child components and event listeners,此刻,vm中的所有配置项、子组件、指令等都被销毁了。最后我们执行Vue最后一个生命周期钩子——destroyed(),该钩子存在感比较低(毕竟vm已经没了)

举个栗子:

<div id="root">
    <h2>当前的n为:{{n}}</h2>
    <button @click="add">点我n++</button>
    <button @click="bye">点我销毁vm</button>
</div>
<script>
    Vue.config.productionTip = false;
    const vm = new Vue({
        el:'#root',
        data: {
            n:1
        },
        methods: {
            add() {
                this.n++;
            },
            bye() {
                this.$destroy();
            }
        },
        beforeCreate() {
            console.log('此时数据代理还未开始,无法通过vm访问data中数据和methods中方法!');
        },
        created() {
            console.log('此时可以通过vm访问data中数据和methods中方法!');
        },
        beforeMount() {
            console.log('此时已经解析完模板生成了虚拟Dom(内存中),但还没在页面编译真实Dom!')
        },
        mounted() {
            console.log('此时页面已经通过虚拟Dom渲染为真实Dom了,你可以开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作啦!');
        },
        beforeUpdate() {
            console.log('此时数据已经更新,但页面还没更新');
        },
        updated() {
            console.log('此时页面与数据都是新的啦!');
        },
        beforeDestroy() {
            console.log('vm中的配置项、指令等等即将销毁,请关闭定时器、解绑订阅消息、解绑自定义事件等收尾操作!');
        },
        destroyed() {
            console.log('bye!');
        }
    });
</script>

运行结果:

        至此,Vue生命周期的销毁阶段便结束了,我们进行以下总结:

  1. Vue实例对象调用$destroy()方法
  2. 执行销毁前生命周期钩子:beforeDestroy()
  3. Teardown watchers, child components and event listeners:销毁实例对象中的配置项、子组件、指令等
  4. 执行销毁后生命周期钩子:Destroyed()

 三、常用的生命周期钩子

  1. mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
  2. beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】

总结

        至此我们就走完了整个Vue的生命周期流程图,以下我们配上整体流程总结,便于梳理各个环节: