1.生命周期的基本概念

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

1.beforeCreate

在这个钩子函数里,只是刚开始初始化实例,你拿不到实例里的任何东西,比如data和methods和事件监听等。

  1. data: {
  2. msg: 'linlin'
  3. },
  4. methods: {
  5. getLists(){
  6. return 'aaa'
  7. }
  8. },
  9. beforeCreate() {
  10. console.log('beforeCreate',this.msg,this.getLists())
  11. }

运行上面代码,控制台报错

生命周期函数 - 图1

因为就像前面说的这个钩子函数里拿不到data和methods

2.created

在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el属性目前不可见。

这是最早能拿到实例里面的数据和方法的一个钩子函数。应用场景:异步数据的获取和对实例数据的初始化操作都在这里面进行。

  1. data: {
  2. msg: 'linlin',
  3. imgs: null
  4. },
  5. methods: {
  6. getLists(){
  7. this.$http.get(url).then(res=>{
  8. this.imgs = res.data.lists
  9. console.log(this.imgs)
  10. })
  11. }
  12. },
  13. created() {
  14. this.getLists()
  15. }

3.beforeMount

在挂载开始之前被调用:相关的render函数首次被调用。

不论是created还是beforeMount在它们里面都拿不到真实的dom元素,如果我们需要拿到dom元素就需要在mounted里操作

  1. <div id="app">
  2. <ul>
  3. <li v-for="(item,index) in arr" :key="index">{{item}}</li>
  4. </ul>
  5. </div>
  6. <script>
  7. let app = new Vue({
  8. data: {
  9. arr: [1,2,3]
  10. },
  11. created() {
  12. console.log('created',document.querySelectorAll('li').length)
  13. },
  14. beforeMount() {
  15. console.log('beforeMount',document.querySelectorAll('li').length)
  16. },
  17. mounted() {
  18. console.log('mounted',document.querySelectorAll('li').length)
  19. },
  20. })
  21. </script>

生命周期函数 - 图2

4.mounted

上面的案例mounted可以拿到dom元素,但也只是能拿到初始化数据里的dom元素,如果是存在异步对dom元素数据进行更改我们就只能在updated里获取,应用场景:初始数据(在data中有的)的dom渲染完毕,可以获取dom

  1. <div id="app">
  2. <ul>
  3. <li v-for="(item,index) in arr" :key="index">{{item}}</li>
  4. </ul>
  5. </div>
  6. created() {
  7. setTimeout(()=>{
  8. this.arr = [4,5,6,7]
  9. console.log('created',document.querySelectorAll('li').length)
  10. })
  11. },
  12. mounted() {
  13. console.log('mounted',document.querySelectorAll('li').length)
  14. }

执行上面代码,发现不管是mounted还是created里拿到的都是3,而不是4

生命周期函数 - 图3

这里之所以先打印出mounted是因为created里面的是异步操作所以最后才执行完,而我们如果想要拿到正确的dom元素的个数就需要在updated里面获取

  1. updated(){
  2. console.log('updated',document.querySelectorAll('li').length)
  3. }

生命周期函数 - 图4

另外$children子组件的获取也需要在mounted里

5.beforeUpdate

当数据更新后出发的钩子函数,这个钩子函数里拿到的是更改之前的数据,虚拟DOM重新渲染和打补丁之前被调用。

你可以在这个钩子中进一步地修改data,这不会触发附加的重渲染过程。

6.updated

上面虽然能在update里拿到更改后的数据,但是并不建议在这里面进行对异步数据得到的dom操作,因为有可能你当前的数据不止更改一次,而update只要相关的数据更改一次就会执行一次,注意:updated是指mouted钩子后(包括mounted)的数据更改,在created里的数据更改不叫更改叫做初始化,所以我们下面在created里修改数据是通过一个异步来确保updated可以执行的。我们一般都是在事件方法里更改数据,然后通过updated对其操作。应用场景:如果dom操作依赖的数据是在异步操作中获取,并且只有一次数据的更改 ,也可以说是数据更新完毕:如果对数据更新做一些统一处理在updated钩子中处理即可。

注意:当这个钩子被调用时,组件DOM的data已经更新,所以你现在可以执行依赖于DOM的操作。但是不要在当前钩子里修改当前组件中的data,否则会继续触发beforeUpdate、updated这两个生命周期,进入死循环!

  1. created() {
  2. setTimeout(()=>{
  3. this.arr = [4,5,6,7]
  4. console.log('created',document.querySelectorAll('li').length)
  5. })
  6. setTimeout(()=>{
  7. this.arr = [10,11,12,13,14]
  8. console.log('created',document.querySelectorAll('li').length)
  9. },1000)
  10. },
  11. beforeUpdate() {
  12. console.log('beforeUpdate',document.querySelectorAll('li').length)
  13. },
  14. updated() {
  15. console.log('updated',document.querySelectorAll('li').length)
  16. },

生命周期函数 - 图5

上面updated执行了两遍,之所以一开始created是3是因为上面我们说的他是在一个异步里,也是在mouted后获取的所以是3。

在事件方法里更改数据

  1. <div id="app">
  2. <ul>
  3. <li v-for="(item,index) in arr" :key="index" @click="getAdd">{{item}}</li>
  4. </ul>
  5. <div>{{msg}}</div>
  6. </div>
  7. data: {
  8. arr: [1,2,3],
  9. },
  10. methods: {
  11. getAdd(){
  12. this.arr = [4,5,6,7]
  13. }
  14. },
  15. updated() {
  16. console.log(this.arr)
  17. }

生命周期函数 - 图6

如果想分别区别不同的数据更新,同时要对dom进行操作那么需要使用nextTick函数处理

  1. created() {
  2. setTimeout(()=>{
  3. this.arr = [4,5,6,7]
  4. this.$nextTick(()=>{
  5. console.log('nextTick',document.querySelectorAll('li').length)
  6. })
  7. })
  8. setTimeout(()=>{
  9. this.arr = [10,11,12,13,14]
  10. this.$nextTick(()=>{
  11. console.log('nextTick',document.querySelectorAll('li').length)
  12. })
  13. },1000)
  14. },

生命周期函数 - 图7

这样我们想对哪一次的数据进行操作就直接在this.$nextTick里面写就行

在updated中修改数据陷入死循环

  1. <div id="app">
  2. {{a}}
  3. <button @click="a=2">点我</button>
  4. </div>
  5. <script>
  6. new Vue({
  7. el: '#app',
  8. data: {
  9. a: [1]
  10. },
  11. beforeUpdate() {
  12. console.log('beforeUpdate')
  13. },
  14. updated() {
  15. console.log(this.a)
  16. console.log('updated')
  17. this.a = [2]
  18. },
  19. })
  20. </script>

updated,watch和nextTick区别

updated对所有数据的变化进行统一处理

watch对具体某个数据变化做统一处理

nextTick是对某个数据的某一次变化进行处理

如果实例里面没写el挂载点你就需要在实例后面通过.$mount(‘#app’)来手动触发

7.beforeDestroy

实例销毁之前调用。在这一步,实例仍然完全可用

8.destroyed

Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

beforeDestroy和destroyed只能通过手动触发$destroy来调用

  1. let app = new Vue({
  2. beforeDestroy() {
  3. console.log('beforeDestroy')
  4. },
  5. destroyed() {
  6. console.log('destroyed')
  7. }
  8. })
  9. app.$destroy()

2.vue中created与mounted的区别

关于vue.js中的生命周期,如果不是有特别的需求,一般在项目开发过程中更多的使用created和mounted,
所以在本文中主要讲解created与mounted在开发中的主要使用区别。

关于完整的生命周期,不久会在另一篇文章中做整体的理解,包括activated、destroyed等,不过可能会有点晚,大家可以留意一下

1.生命周期

完整的生命周期图示为了避免占用板块,这里就不贴出来了,大家可以自行前往vue生命周期查看。

2.浏览器渲染过程

  • 构建DOM树
  • 构建css规则树,根据执行顺序解析js文件。
  • 构建渲染树Render Tree
  • 渲染树布局layout
  • 渲染树绘制

3.生命周期中的浏览器渲染

  1. beforeCreate(){
  2. console.log('beforecreate:',document.getElementById('first'))//null
  3. console.log('data:',this.text);//undefined
  4. this.sayHello();//error:not a function
  5. },
  6. created(){
  7. console.log('create:',document.getElementById('first'))//null
  8. console.log('data:',this.text);//this.text
  9. this.sayHello();//this.sayHello()
  10. },
  11. beforeMount(){
  12. console.log('beforeMount:',document.getElementById('first'))//null
  13. console.log('data:',this.text);//this.text
  14. this.sayHello();//this.sayHello()
  15. },
  16. mounted(){
  17. console.log('mounted:',document.getElementById('first'))//<p></p>
  18. console.log('data:',this.text);//this.text
  19. this.sayHello();//this.sayHello()
  20. }

通过上述测试我们可以知道,

生命周期 是否获取dom节点 是否可以获取data 是否获取methods
beforeCreate
created
beforeMount
mounted

以我的个人理解,vue生命周期实际上和浏览器渲染过程是挂钩的,

在beforecreate阶段,对浏览器来说,整个渲染流程尚未开始或者说准备开始,对vue来说,实例尚未被初始化,data observer和 event/watcher也还未被调用,在此阶段,对data、methods或文档节点的调用现在无法得到正确的数据。

在created阶段,对浏览器来说,渲染整个HTML文档时,dom节点、css规则树与js文件被解析后,但是没有进入被浏览器render过程,上述资源是尚未挂载在页面上,也就是在vue生命周期中对应的created
阶段,实例已经被初始化,但是还没有挂载至$el上,所以我们无法获取到对应的节点,但是此时我们是可以获取到vue中data与methods中的数据的

在beforeMount阶段,实际上与created阶段类似,节点尚未挂载,但是依旧可以获取到data与methods中的数据。

在mounted阶段,对浏览器来说,已经完成了dom与css规则树的render,并完成对render tree进行了布局,而浏览器收到这一指令,调用渲染器的paint()在屏幕上显示,而对于vue来说,在mounted阶段,vue的template成功挂载在$el中,此时一个完整的页面已经能够显示在浏览器中,所以在这个阶段,即可以调用节点了(关于这一点,在笔者测试中,在mounted方法中打断点然后run,依旧能够在浏览器中看到整体的页面)。