官方文檔解釋如下:在下次 DOM 更新循環(huán)結束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個方法,獲取更新后的 DOM。
一、具體來說,異步執(zhí)行的運行機制如下。
- (1)所有同步任務都在主線程上執(zhí)行,形成一個執(zhí)行棧(execution context stack)。
- (2)主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
- (3)一旦"執(zhí)行棧"中的所有同步任務執(zhí)行完畢,系統(tǒng)就會讀取"任務隊列",看看里面有哪些事件。那些對應的異步任務,于是結束等待狀態(tài),進入執(zhí)行棧,開始執(zhí)行。
- (4)主線程不斷重復上面的第三步。
下圖就是主線程和任務隊列的示意圖。

上圖事件循環(huán)說明:
簡單來說,Vue 在修改數(shù)據(jù)后,視圖不會立刻更新,而是等同一事件循環(huán)中的所有數(shù)據(jù)變化完成之后,再統(tǒng)一進行視圖更新。
//改變數(shù)據(jù)
vm.message = 'changed'
//想要立即使用更新后的DOM。這樣不行,因為設置message后DOM還沒有更新
console.log(vm.$el.textContent) // 并不會得到'changed'
//這樣可以,nextTick里面的代碼會在DOM更新后執(zhí)行
Vue.nextTick(function(){
console.log(vm.$el.textContent) //可以得到'changed'
})
圖解:

事件循環(huán)解析:
(1)、第一個 tick(圖例中第一個步驟,即'本次更新循環(huán)'):
- 首先修改數(shù)據(jù),這是同步任務。同一事件循環(huán)的所有的同步任務都在主線程上執(zhí)行,形成一個執(zhí)行棧,此時還未涉及 DOM 。
- Vue 開啟一個異步隊列,并緩沖在此事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。如果同一個 watcher 被多次觸發(fā),只會被推入到隊列中一次。
(2)、第二個 tick(圖例中第二個步驟,即'下次更新循環(huán)'):
- 同步任務執(zhí)行完畢,開始執(zhí)行異步 watcher 隊列的任務,更新 DOM 。Vue 在內(nèi)部嘗試對異步隊列使用原生的 Promise.then 和 MessageChannel 方法,如果執(zhí)行環(huán)境不支持,會采用 setTimeout(fn, 0) 代替。
(3)、第三個 tick(圖例中第三個步驟):
- 此時就是文檔所說的
簡單總結事件循環(huán):
同步代碼執(zhí)行 -> 查找異步隊列,推入執(zhí)行棧,執(zhí)行Vue.nextTick[事件循環(huán)1] ->查找異步隊列,推入執(zhí)行棧,執(zhí)行Vue.nextTick[事件循環(huán)2]...
總之,異步是單獨的一個tick,不會和同步在一個 tick 里發(fā)生,也是 DOM 不會馬上改變的原因。
二、什么時候需要用的Vue.nextTick()
- (1).你在Vue生命周期的created()鉤子函數(shù)進行的DOM操作一定要放在
Vue.nextTick()的回調(diào)函數(shù)中。原因是什么呢,原因是在created()鉤子函數(shù)執(zhí)行的時候DOM 其實并未進行任何渲染,而此時進行DOM操作無異于徒勞,所以此處一定要將DOM操作的js代碼放進Vue.nextTick()的回調(diào)函數(shù)中。與之對應的就是mounted鉤子函數(shù),因為該鉤子函數(shù)執(zhí)行時所有的DOM掛載和渲染都已完成,此時在該鉤子函數(shù)中進行任何DOM操作都不會有問題 。 - (2).在數(shù)據(jù)變化后要執(zhí)行的某個操作,而這個操作需要使用隨數(shù)據(jù)改變而改變的DOM結構的時候,這個操作都應該放進
Vue.nextTick()的回調(diào)函數(shù)中。
四、實例如下
//點擊獲取元素寬度
<div id="app">
<p ref="myWidth" v-if="showMe">{{ message }}</p>
<button @click="getMyWidth">獲取p元素寬度</button>
</div>
new Vue({
el: '#app',
data:{
showMe:false,
message:''
},
methods:{
getMyWidth() {
this.showMe = true;
//this.message = this.$refs.myWidth.offsetWidth;
//報錯 TypeError: this.$refs.myWidth is undefined
this.$nextTick(()=>{
//dom元素更新后執(zhí)行,此時能拿到p元素的屬性
this.message = this.$refs.myWidth.offsetWidth;
})
}
}
})
//點擊按鈕顯示原本以 v-show = false 隱藏起來的輸入框,并獲取焦點。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>獲取焦點</title>
<style type="text/css">
.closesou{font-size:30px;color:red;cursor: pointer;}
</style>
<script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
<div class="soubox">
<button class="showsearch" @click="showsou">搜索</button>
<div v-if="showit">
<input type="text" name="" id="keywords">
<div class="closesou" @click="hidesou">×</div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
// created:function(){
// this.$nextTick(function () {
// // DOM 更新了
// document.getElementById("keywords").focus()
// })
// },
data:{
showit:true
},
methods:{
showsou(){
this.showit = true
this.$nextTick(function () {
// DOM 更新了
document.getElementById("keywords").focus()
})
},
hidesou(){
this.showit = false
}
}
})
</script>
</body>
</html>