之前对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>
从上面的代码我们可以得出三个结论:
更改上面的代码如下:把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>
注意这个时候除了created函数内的数据初始化和同步更新有用,其他的都不会更新,包括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")
}
可以看到beforeUpdate和updated是不会执行的;
如果把mounted添加上:
如果这个时候把html里地{{testMsg}}去除,则:
同样地添加异步数据操作也是一样的;
为了方便看出在大部分生命周期内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点:
首先执行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点:
之所以想看下这个,是因为之前nextTick之前的运行时间结点让我郁闷
以上结论以及部分名词是我自己写的,如有不对,还请指正