一、定義[nextTick、事件循環]
nextTick的由來:
由於VUE的數據驅動視圖更新,是異步的,即修改數據的當下,視圖不會立刻更新,而是等同一事件循環中的所有數據變化完成之后,再統一進行視圖更新。
nextTick的觸發時機:
在同一事件循環中的數據變化后,DOM完成更新,立即執行nextTick(callback)內的回調。
應用場景:
需要在視圖更新之后,基於新的視圖進行操作。
以上出現了事件循環的概念,其涉及到JS的運行機制,包括主線程的執行棧、異步隊列、異步API、事件循環的協作,此處不展開之后再總結。大致理解:主線程完成同步環境執行,查詢任務隊列,提取隊首的任務,放入主線程中執行;執行完畢,再重復該操作,該過程稱為事件循環。而主線程的每次讀取任務隊列操作,是一個事件循環的開始。異步callback不可能處在同一事件循環中。
簡單總結事件循環:
同步代碼執行 -> 查找異步隊列,推入執行棧,執行callback1[事件循環1] ->查找異步隊列,推入執行棧,執行callback2[事件循環2]...
即每個異步callback,最終都會形成自己獨立的一個事件循環。
結合nextTick的由來,可以推出每個事件循環中,nextTick觸發的時機:
同一事件循環中的代碼執行完畢 -> DOM 更新 -> nextTick callback觸發
tips:本文的任務隊列、消息隊列、異步隊列指同一個東西,均指macrotask queue。
二、實例理解nextTick的使用,並給出在頁面渲染上的優化巧用
(tips:代碼的正確閱讀方式:看template組成、跳過script代碼、看代碼后面的用例設計、看之后的代碼分析、同時結合回頭結合script代碼理解)
<template> <div> <ul> <li v-for="item in list1">{{item}}</li> </ul> <ul> <li v-for="item in list2">{{item}}</li> </ul> <ol> <li v-for="item in list3">{{item}}</li> </ol> <ol> <li v-for="item in list4">{{item}}</li> </ol> <ol> <li v-for="item in list5">{{item}}</li> </ol> </div> </template> <script type="text/javascript"> export default { data() { return { list1: [], list2: [], list3: [], list4: [], list5: [] } }, created() { this.composeList12() this.composeList34() this.composeList5() this.$nextTick(function() { // DOM 更新了 console.log('finished test ' + new Date().toString()) console.log(document.querySelectorAll('li').length) }) }, methods: { composeList12() { let me = this let count = 10000 for (let i = 0; i < count; i++) { Vue.set(me.list1, i, 'I am a 測試信息~~啦啦啦' + i) } console.log('finished list1 ' + new Date().toString()) for (let i = 0; i < count; i++) { Vue.set(me.list2, i, 'I am a 測試信息~~啦啦啦' + i) } console.log('finished list2 ' + new Date().toString()) this.$nextTick(function() { // DOM 更新了 console.log('finished tick1&2 ' + new Date().toString()) console.log(document.querySelectorAll('li').length) }) }, composeList34() { let me = this let count = 10000 for (let i = 0; i < count; i++) { Vue.set(me.list3, i, 'I am a 測試信息~~啦啦啦' + i) } console.log('finished list3 ' + new Date().toString()) this.$nextTick(function() { // DOM 更新了 console.log('finished tick3 ' + new Date().toString()) console.log(document.querySelectorAll('li').length) }) setTimeout(me.setTimeout1, 0) }, setTimeout1() { let me = this let count = 10000 for (let i = 0; i < count; i++) { Vue.set(me.list4, i, 'I am a 測試信息~~啦啦啦' + i) } console.log('finished list4 ' + new Date().toString()) me.$nextTick(function() { // DOM 更新了 console.log('finished tick4 ' + new Date().toString()) console.log(document.querySelectorAll('li').length) }) }, composeList5() { let me = this let count = 10000 this.$nextTick(function() { // DOM 更新了 console.log('finished tick5-1 ' + new Date().toString()) console.log(document.querySelectorAll('li').length) }) setTimeout(me.setTimeout2, 0) }, setTimeout2() { let me = this let count = 10000 for (let i = 0; i < count; i++) { Vue.set(me.list5, i, 'I am a 測試信息~~啦啦啦' + i) } console.log('finished list5 ' + new Date().toString()) me.$nextTick(function() { // DOM 更新了 console.log('finished tick5 ' + new Date().toString()) console.log(document.querySelectorAll('li').length) }) } } }
</script>
2.1、用例設計
用例1:通過list1、2、3驗證,處在同步代碼中的DOM更新情況及nextTick的觸發時機;
用例2:通過list3、list4驗證,同步代碼及異步代碼中Dom更新及nextTick觸發的區別;
用例3:通過list4、list5對比驗證,多個異步代碼中nextTick觸發的區別;
用例4:通過在視圖更新后獲取DOM中<li>的數量,判斷nextTick序列渲染的時間點。
2.2、代碼分析
函數執行步驟:
事件循環1:
step1: this.composeList12() -> update list1, update list2 -> 綁定tick’1&2’
step2: this.composeList34() -> update list3, 設置異步1setTimeout1 -> 綁定tick’3’
step3: this.composeList5() -> 綁定tick’5-1’ -> 設置異步2setTimeout2
step4: 綁定tick’test’
事件循環2:
將setTimeout1的callback推入執行棧 -> update list4 -> 綁定tick’4’
事件循環3:
將setTimeout2的callback推入執行棧 -> update list5 -> 綁定tick’5’
2.3、推斷輸出消息
由於同一事件循環中的tick按執行順序,因此消息輸出為即:
[同步環境]update list1 -> update list2 -> update list3 -> tick‘1&2’ -> tick‘3’ -> tick’5-1’ -> tick’test'
[事件循環1]->update list4 -> tick’4’
[事件循環2] ->update list5 -> tick’5’
2.4、實際運行結果如下圖

該demo中,設置了5個size為10000的數組,從而能從時間及消息輸出兩個維度來了解nextTick的執行情況。另外,額外增加了一個參數,即更新后的視圖中<li>的數量,從這個數量,可以考察出同一事件循環中的nextTick執行情況。由運行結果圖可以看出實際的輸出與推導的輸出結果相符合。
2.5、總結
從用例1得出:
a、在同一事件循環中,只有所有的數據更新完畢,才會調用nextTick;
b、僅在同步執行環境數據完全更新完畢,DOM才開始渲染,頁面才開始展現;
c、在同一事件循環中,如果存在多個nextTick,將會按最初的執行順序進行調用;
從用例1+用例4得出:
d、從同步執行環境中的四個tick對應的‘li’數量均為30000可看出,同一事件循環中,nextTick所在的視圖是相同的;
從用例2得出:
e、只有同步環境執行完畢,DOM渲染完畢之后,才會處理異步callback
從用例3得出:
f、每個異步callback最后都會處在一個獨立的事件循環中,對應自己獨立的nextTick;
從用例1結論中可得出:
g、這個事件環境中的數據變化完成,在進行渲染[視圖更新],可以避免DOM的頻繁變動,從而避免了因此帶來的瀏覽器卡頓,大幅度提升性能;
從b可以得出:
h、在首屏渲染、用戶交互過程中,要巧用同步環境及異步環境;首屏展現的內容,盡量保證在同步環境中完成;其他內容,拆分到異步中,從而保證性能、體驗。
tips:
1、可產生異步callback的有:promise(microtask queue)、setTimeout、MutationObserver、DOM事件、Ajax等;
2、 vue DOM的視圖更新實現,,使用到了ES6的Promise及HTML5的
MutationObserver,當環境不支持時,使用setTimeout(fn, 0)替代。上述的三種方法,均為異步API。其中MutationObserver類似事件,又有所區別;事件是同步觸發,其為異步觸發,即DOM發生變化之后,不會立刻觸發,等當前所有的DOM操作都結束后觸發。關於異步API、事件循環將在以后補充。
事件循環、任務隊列詳解:http://www.cnblogs.com/hity-tt/p/6733062.html