js nextTick和生命周期

之前对dom更新的事件循环有点模糊,今天总结一下,这里只提几个比较重要的生命周期函数:
dom产生分为初始化和更新阶段:初始化可以理解为是特殊的更新,dom更新并不是直接更新,而是把待更新的结点放入事件队列中,然后等待至一定时间,取出队列中的结点,一一更新,这样的一次循环就是一次tick;最后这些结点是否更新是通过watch监听的,以及diff算法(这个我暂时也没看过)

created: 组件创建完就执行该回调函数,但是这个时候并没有执行dom的渲染,也获取不到子组件和dom的任何信息,如果有数据更新或者是初始化的操作,则进入第一次的事件队列中;
mounted:组件挂载完执行的回调函数,即组件经过之前数据初始化,已经挂载到dom上了,可以获取子组件以及dom,所以从组件创建到mounted这中间的数据更新相当于是一次事件循环(异步操作除外),这也是组件的第一次dom更新事件循环,这mounted是组件挂载完后的回调函数,所以mounted函数内部的数据更新操作只会放在下一次dom更新循环事件中,这就开启了第二轮事件队列;
beforeUpdated: 如果在这个函数内部有数据操作,则所再一次进入刚才的事件队列,这个函数一结束就进入事件循环;
updated:一旦执行到这个函数,说明刚才的事件循环已经结束了,这个时候如果updated回调里有数据更新的操作,则继续进行下一轮的事件队列中,重新执行beforeUpdated函数,再执行updated函数…如此反复
下面看一下代码:

<!--
 * @Descripttion: 
 * @version: 
 * @Author: congsir
 * @Date: 2021-06-29 15:51:54
 * @LastEditors: Please set LastEditors
 * @LastEditTime: 2021-07-03 09:58:59
-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <div id="app">
        
        <h3 id="h">{{testMsg}}</h3>

    </div>
</body>

   
  <script>
      const app = new Vue({
        el:"#app",
        data:{
            testMsg:"原始值"
        },
/*         mounted() {
            console.log("mounted-------------start")
            let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
            console.log("%c%s", "color:red", domTxt);
            console.log("--------------")
            this.testMsg += "+mounted";
            domTxt=document.getElementById('h').innerText;
            console.log("%c%s", "color:red",domTxt);
            console.log("mounted-------------end")

            this.$nextTick(function() {
                    console.log("nextTick-----------------mounted----start")
                    let domTxt=document.getElementById('h').innerText;  
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------mounted----end")
            })
        }, */

            console.log("beforeUpdated----------------start")
            this.testMsg += "+beforeUpdated"
            console.log(this.testMsg)
            let domTxt=document.getElementById('h').innerText;
            console.log("%c%s", "color:red",domTxt);
            console.log("beforeUpdated----------------end")


            this.$nextTick(function() {
                    console.log("nextTick-----------------beforeupdated----start")
                    let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------beforeupdated----end")
            })

        },//这个函数一结束立马开始一次更新循环(tick) */

        updated() {//一次tick已经结束,dom的一次更新已经完成
            console.log("updated----------------start")
            let domTxt=document.getElementById('h').innerText;
            console.log("%c%s", "color:red",domTxt);
            console.log("updated----------------end")
            this.$nextTick(function() {
                    console.log("nextTick-----------------updated----start")
                    let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------updated----end")
            })
        },

        methods:{
            changeTxt() {
                let that=this;
                let domTxt=document.getElementById('h').innerText;
                console.log("%c%s", "color:red",domTxt)
                that.testMsg="第一次更改的初始文本值";
                setTimeout(()=>{
                    console.log("********SettimeOUt*********")
                    that.testMsg+="+setTimeout";
                },5000)
                this.$nextTick(function() {
                    console.log("nextTick-----------------created----start")
                    let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------created----end")
                })
            }
        },
        created(){
            console.log("created-------------start")
            this.changeTxt()
            console.log("created-------------end")

        }
      })
    

  </script>
   
</html>

从上面的代码我们可以得出三个结论:

一、只有和视图相关联的数据才会被beforeUpdated和updated监听,否则即使数据发生改变了,也不会触发这2个函数:

更改上面的代码如下:把html里的data:testMsg去掉

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <div id="app">
        
        <h3 id="h"></h3>

    </div>
</body>

   
  <script>
      const app = new Vue({
        el:"#app",
        data:{
            testMsg:"原始值"
        },
/*         mounted() {
            console.log("mounted-------------start")
            let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
            console.log("%c%s", "color:red", domTxt);
            console.log("--------------")
            this.testMsg += "+mounted";
            domTxt=document.getElementById('h').innerText;
            console.log("%c%s", "color:red",domTxt);
            console.log("mounted-------------end")

            this.$nextTick(function() {
                    console.log("nextTick-----------------mounted----start")
                    let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------mounted----end")
            })
        }, */

         beforeUpdate() 
            console.log("beforeUpdated----------------start")
            this.testMsg += "+beforeUpdated"
            console.log(this.testMsg)
            let domTxt=document.getElementById('h').innerText;
            console.log("%c%s", "color:red",domTxt);
            console.log("beforeUpdated----------------end")


            this.$nextTick(function() {
                    console.log("nextTick-----------------beforeupdated----start")
                    let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------beforeupdated----end")
            })

        },//这个函数一结束立马开始一次更新循环(tick) 

        updated() {//一次tick已经结束,dom的一次更新已经完成
            console.log("updated----------------start")
            let domTxt=document.getElementById('h').innerText;
            console.log("%c%s", "color:red",domTxt);
            console.log("updated----------------end")
            this.$nextTick(function() {
                    console.log("nextTick-----------------updated----start")
                    let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------updated----end")
            })
        },

        methods:{
            changeTxt() {
                let that=this;
                let domTxt=document.getElementById('h').innerText;
                console.log("%c%s", "color:red",domTxt)
                that.testMsg="第一次更改的初始文本值";
                setTimeout(()=>{
                    console.log("********SettimeOUt*********")
                    that.testMsg+="+setTimeout";
                },5000)
                this.$nextTick(function() {
                    console.log("nextTick-----------------created----start")
                    let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------created----end")
                })
            }
        },
        created(){
            console.log("created-------------start")
            this.changeTxt()
            console.log("created-------------end")


        }
      })
    

  </script>
   
</html>

js nextTick和生命周期_第1张图片
注意这个时候除了created函数内的数据初始化和同步更新有用,其他的都不会更新,包括created里的异步代码也不会执行

二、beforeUpdated和updated是由除了created里的同步更新代码之外的数据更新触发的

前面说过created内部的同步更新会被一起添加至第一次的更新队列中,后序的比如created内部的异步请求数据和mounted更新都是在beforeUpdate——updated之间更新的;这也好理解,如果created内部的请求数据代码是和里面的同步代码一起请求的,那万一网络请求很慢,岂不是阻塞了生命周期的继续执行了吗?类似地,mounted表示已经挂载好至dom了,那么mounted回调函数内部的更新就应该放到后面的dom更新事件队列中执行。所以如果created内部没有异步的数据操作mounted内部也没有数据更新,则不会执行beforeUpdated和updated。看代码,我把mounted注释了还要created内部的异步操作也取消了,注意上面的testMsg要在html里用到,不然textMsg不用的话,无论如何都不会触发这两个函数的:

         beforeUpdate() {//所有更新数据的操作已完成进入事件队列,这个函数一结束就进入事件循环
            console.log("beforeUpdated----------------start")
            this.testMsg += "+beforeUpdated"
            console.log(this.testMsg)
            let domTxt=document.getElementById('h').innerText;
            console.log("%c%s", "color:red",domTxt);
            console.log("beforeUpdated----------------end")


            this.$nextTick(function() {
                    console.log("nextTick-----------------beforeupdated----start")
                    let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------beforeupdated----end")
            })

        },
        updated() {//一次tick已经结束,dom的一次更新已经完成
            console.log("updated----------------start")
            let domTxt=document.getElementById('h').innerText;
            console.log("%c%s", "color:red",domTxt);
            console.log("updated----------------end")
            this.$nextTick(function() {
                    console.log("nextTick-----------------updated----start")
                    let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------updated----end")
            })
        },

        methods:{
            changeTxt() {
                let that=this;
                let domTxt=document.getElementById('h').innerText;
                console.log("%c%s", "color:red",domTxt)
                that.testMsg="第一次更改的初始文本值";
/*                 setTimeout(()=>{
                    console.log("********SettimeOUt*********")
                    that.testMsg+="+setTimeout";
                },5000) */
                this.$nextTick(function() {
                    console.log("nextTick-----------------created----start")
                    let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------created----end")
                })
        created(){
            console.log("created-------------start")
            this.changeTxt()
            console.log("created-------------end")
		}

js nextTick和生命周期_第2张图片
可以看到beforeUpdate和updated是不会执行的;
如果把mounted添加上:
js nextTick和生命周期_第3张图片
如果这个时候把html里地{{testMsg}}去除,则:
js nextTick和生命周期_第4张图片

同样地添加异步数据操作也是一样的;

三、nextTick是在把其内部的回调函数延迟到当前dom更新队列结束之后

为了方便看出在大部分生命周期内nextTick执行的时间点,我把上面的beforeUpdated,updated,created,以及mounted全部添加上

        created(){
            console.log("created-------------start")
            this.changeTxt()
            console.log("created-------------end")
        }
        
        mounted() {
            console.log("mounted-------------start")
            let domTxt=document.getElementById('h').innerText;  
            console.log("%c%s", "color:red", domTxt);
            console.log("--------------")
            this.testMsg += "+mounted";
            domTxt=document.getElementById('h').innerText;
            console.log("%c%s", "color:red",domTxt);
            console.log("mounted-------------end")

            this.$nextTick(function() {
                    console.log("nextTick-----------------mounted----start")
                    let domTxt=document.getElementById('h').innerText;  
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------mounted----end")
            })
        },

         beforeUpdate() {
            console.log("beforeUpdated----------------start")
            this.testMsg += "+beforeUpdated"
            console.log(this.testMsg)
            let domTxt=document.getElementById('h').innerText;
            console.log("%c%s", "color:red",domTxt);
            console.log("beforeUpdated----------------end")
            this.$nextTick(function() {
                    console.log("nextTick-----------------beforeupdated----start")
                    let domTxt=document.getElementById('h').innerText;  
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------beforeupdated----end")
            })

        },

        updated() {
            console.log("updated----------------start")
            let domTxt=document.getElementById('h').innerText;
            console.log("%c%s", "color:red",domTxt);
            console.log("updated----------------end")
            this.$nextTick(function() {
                    console.log("nextTick-----------------updated----start")
                    let domTxt=document.getElementById('h').innerText;  
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------updated----end")
            })
        },

        methods:{
            changeTxt() {
                let that=this;
                let domTxt=document.getElementById('h').innerText;
                console.log("%c%s", "color:red",domTxt)
                that.testMsg="第一次更改的初始文本值";
                setTimeout(()=>{
                    console.log("********SettimeOUt*********")
                    that.testMsg+="+setTimeout";
                },5000)
                this.$nextTick(function() {
                    console.log("nextTick-----------------created----start")
                    let domTxt=document.getElementById('h').innerText;  //后续js对dom的操作
                    console.log("%c%s", "color:red",domTxt);
                    console.log("nextTick-----------------created----end")
                })
            }
        },
      })

直接看运行结果:为了方便看结果,我用了--------作为各个生命周期的分界线

created-------------start
{{testMsg}}
created-------------end
mounted-------------start
第一次更改的初始文本值
--------------
第一次更改的初始文本值
mounted-------------end
nextTick-----------------created----start
第一次更改的初始文本值
nextTick-----------------created----end
beforeUpdated----------------start
第一次更改的初始文本值
beforeUpdated----------------end
updated----------------start
第一次更改的初始文本值+mounted+beforeUpdated
updated----------------end
nextTick-----------------mounted----start
第一次更改的初始文本值+mounted+beforeUpdated
nextTick-----------------mounted----end
nextTick-----------------beforeupdated----start
第一次更改的初始文本值+mounted+beforeUpdated
 nextTick-----------------beforeupdated----end
nextTick-----------------updated----start
第一次更改的初始文本值+mounted+beforeUpdated
nextTick-----------------updated----end
********SettimeOUt*********
beforeUpdated----------------start
第一次更改的初始文本值+mounted+beforeUpdated+setTimeout+beforeUpdated
第一次更改的初始文本值+mounted+beforeUpdated
beforeUpdated----------------end
updated----------------start
第一次更改的初始文本值+mounted+beforeUpdated+setTimeout+beforeUpdated
updated----------------end
nextTick-----------------beforeupdated----start
 第一次更改的初始文本值+mounted+beforeUpdated+setTimeout+beforeUpdated
nextTick-----------------beforeupdated----end
 nextTick-----------------updated----start
第一次更改的初始文本值+mounted+beforeUpdated+setTimeout+beforeUpdated
nextTick-----------------updated----end

首先明确2点:

  1. dom更新队列分为初始化更新(在mounted之前)和后序更新(beforeUpdate和updated之间
  2. nextTick是把对应的回调放在了当前tick之后,这个tick就是dom更新队列事件循环,是一个当前tick运行结束后的延迟回调

首先执行created回调:
这个时候是拿不到dom的,所以打印结点信息:{{testMsg}},同时该函数内部有一个同步地数据更新和一个异步的以及一个nextTick,这些异步的暂时放入异步API,这里我是拿setTimeout模拟异步操作的,这个时候nextTick是不执行的,因为本次的dom还未更新;
然后是mounted回调:
首先打印dom结点信息,可以看到dom已被更改为第一次更改的初始文本,第一次初始的事件队列已结束,dom已经更新;然后再更改一下textMsg,然后取出dom打印,发现并没有更新,其实是添加到了新一轮的dom更新队列中;最后又遇到了mounted内部的nextTick,mounted执行完毕,到此第一轮dom更新队列事件循环执行结束,这个时候延迟执行上一个存留的nextTick;
接着执行beforeUpdate:
同刚才的执行过程一样,打印发现仍然是第一次事件循环后的结果,因为第二次的事件队列并没有开始,也就是dom还未更新,所以beforeUpdate
内部的数据操作同刚才的mounted内部的数据操作一起放入第二轮队列中,这个时候的dom值仍然是”第一次更改的初始文本值";另外,这一轮的nextTick也是延迟到当前的dom更新结束才执行
队列信息是:
testMsg:[”mounted“,“beforeupdate”]
最后是updated:
执行到这个函数说明当前轮的事件循环已经结束,打印dom,可以看到是:第一次更改的初始文本值+mounted+beforeUpdated;既然结束了,则执行刚才栈内保存的2个nextTick(分别是mounted和beforeUpdate)

最后别忘记了还有一个异步操作:
这个时候执行setTimeout,然后同样地触发beforeUpdate -> update,就是把刚才的后面重复了一次,所以最后的结果是:第一次更改的初始文本值+mounted+beforeUpdated+setTimeout+beforeUpdated

如果在updated内仍然有异步操作会怎么样?
会无限循环,除非你的修改是保持不变的。因为updated内部的数据如果被watch检测到,则修改继续添加至新的循环队列中,准备进入下一轮的
beforeUpdate -> update,如此往复…

综上我们要明白3点:

  1. 如果要在created里更改数据,获取数据,那么最好放在created内部的nextTick中,因为这里的nextTick全部监听的是created内的数据修改;
  2. 如果碰到加载图片这种异步操作,或者是其他异步操作,则最好把获取dom这种操作放在updated里面,并设置防抖,因为异步操作获取数据一般在最后,如果直接在前面的生命周期中执行,则不一定能拿到所有数据,尤其是根据加载的图片获取高度这种操作时;当然根据图片加载高度也可以设置图片加载完成监听这样的操作,一旦图片全部加载完成则执行相应的dom操作也是可以的
  3. 最后再提一下:beforeUpdate和updated的触发只能靠异步或者是mounted内的操作;异步更新异步都会放在最后;mounted和updated分别代表第一次的事件循环和后面n次的事件循环结束了

之所以想看下这个,是因为之前nextTick之前的运行时间结点让我郁闷

以上结论以及部分名词是我自己写的,如有不对,还请指正

你可能感兴趣的:(javascript,es6,html)