vue的生命周期理解

Vue的生命周期

概述

每个Vue实例的生命周期大致如下:创建实例,初始化数据,模板编译,挂载DOM,,更新数据然后视图重新渲染,实例销毁等一系列过程

各个生命周期的具体作用

在Vue实例的生命周期中,Vue提供了一系列相关的生命周期钩子的函数用来帮助开发者在Vue的不同生命周期阶段来添加适当的代码

以下是一张Vue官方文档中的生命周期示意图

我们来详细理解以下这张生命周期示意图

  1. new Vue()阶段 创建Vue实例,这一步也就是调用Vue构造函数
    1
    var vm = new Vue({});
  2. 初始化了一个空的Vue实例,此时该Vue实例对象上只有默认的一些生命周期函数和默认事件。
  3. 在这里有一个beforeCreate()生命周期钩子,但是在该钩子函数里我们还无法访问到data以及methods里的方法,当然更无法访问el
    1
    2
    3
    beforeCreate() {
    console.log(this.$data,this.$el);
    }
  4. 数据初始化完毕,此时data和methods可以被访问了
  5. 在该阶段有一个created()的钩子可以供我们调用,此时在该钩子函数中我们可以访问到data和methods内的属性了,但是此时仍然无法访问到el。因此,我们也可以看到该钩子函数是我们在所有钩子函数中最早能够访问到data和methods的阶段
  6. Vue开始进行模板编译
  7. 当模板编译完毕后,Vue在这里提供了一个beforeMount()的生命周期钩子函数,此时render函数将开始被调用,在这个钩子里,我们已经可以访问到el属性了
    1
    2
    3
    beforeMount() {
    console.log(this.$el);
    } // <div id="app">...</div>

    但是,在此阶段模板仅仅只是被编译好,而没有真正地替换到页面中去,我们点击上面的this.$el的输出结果可以发现:

    1
    2
    3
    4
    <div id="app">
    <input v-model="value" />
    <p>{{value}}</p>
    </div>

    我们可以看到此时的输出的要挂载的节点内容并没有被真实地替换掉,模板语法仍旧存在

  8. 将编译好的模板真实地替换到页面中去,此时我们在mounted()钩子中就可以看到真实被渲染完毕的el
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    mounted() {
    console.log(this.$el);
    }

    /* 输出结果
    <div>
    <input />
    <p>123</p>
    </div>
    */

    此时,Vue实例已经被初始化完毕了,之后就是实例的运行阶段。

  9. 此时,在Vue组件运行阶段我们可能会要更新数据,当更新数据的时候,beforeUpdate钩子就会被调用,此时,data中的数据是最新的,但是还没有更新到视图中
  10. 虚拟DOM树重新渲染,然后更新到视图中
  11. 更行完毕后,updated钩子会被调用,此时数据与视图已经是同步的了
  12. 当vm.$destroy()实例销毁函数显式被调用时,便会触发触发 beforeDestroy 和 destroyed 的钩子。
  13. 在实例销毁之前我们可以调用beforeDestroy钩子,此时实例并没有被销毁,该组件所有的功能都还是可用的
  14. destroyed()调用时,此时实例已经被销毁完毕了,该组件的所有功能都不再可用

各个生命周期适合开发者适合做的操作

异步操作应该在哪个生命周期阶段进行

首先我们需要确认异步操作会不会阻塞Vue的整个生命周期的进行

我们可以使用宏任务setTimeout并且不设置过时时间来模拟异步操作的进行并修改data中的数据,具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 确定脚本开始运行的时间
let start = Date.now();

let vm = new Vue({
el: '#app',
data: {
value: 123
},
created() {
console.log(created time <span class="subst">${<span class="built_in">Date</span>.now()-start}</span>s);
// 没有设置宏任务setTimeout的过时时间
setTimeout(()=>{
console.log(<span class="subst">${<span class="built_in">Date</span>.now()-start}</span>s change the value to 321);
this.value = 321;
})
},
mounted() {
console.log(mounted time <span class="subst">${<span class="built_in">Date</span>.now()-start}</span>s);
},
updated() {
console.log(<span class="subst">${<span class="built_in">Date</span>.now()-start}</span>s updated the value to <span class="subst">${<span class="keyword">this</span>.value}</span>);
}
})

/* 输出结果
created time 2s
mounted time 29s

140s change the value to 321
141s start updating the value to 321
143s updated the value to 321
*/

我们可以看到宏任务setTimeout在Vue实例完全初始化后,也就是mounted阶段后才会开始运行。因此,对于异步请求数据操作也只会在Vue实例初始化完毕后(mounted阶段后)才会进行,之后的数据修改或赋值就会直接触发beforeUpdate以及后续进行的updated钩子

通过以上的输出结果,我们可以得出一个结论:

异步操作并不会阻塞Vue初始化生命周期的进行。

因此,我们将异步操作放在created,beforeMount,mounted这三个钩子里都是OK的。因为这三个阶段data数据都已经被初始化好了,我们可以在异步操作的回调中对data中的数据进行修改或赋值了

但是,为了减少用户的等待时间,我们最好在created阶段进行异步操作。因为异步任务都会被放入event table中进行,越早声明也就代表着被运行的时间越早,因此也就越有可能会在异步回调中的任务队列优先被执行回调

DOM访问操作应该在哪个生命周期阶段进行

只有在Vue实例挂在到DOM节点时,我们才可以去访问和操作DOM,因此,DOM访问操作应该在mounted阶段进行