熟悉 Vue 的同窗們都知道,Vue 有個 nextTick 方法,用來異步更新數據。javascript
<body>
<div id="main">
<ul class="list">
<li class="item" v-for="item in list">{{ item }}</li>
</ul>
</div>
<script> new Vue({ el: '#main', data: { list: [ 'AAAAAAAAAA', 'BBBBBBBBBB', 'CCCCCCCCCC' ] }, mounted: function () { this.list.push('DDDDD') } }) </script>
</body>
複製代碼
隨便給了點樣式以後,頁面是這樣的:html
看起來彷佛一切正常,咱們在給數組添加了一條數據以後,頁面也確實對應的更新了。但是,當咱們在打印這個 ul 元素裏 li 的 length 時,問題出現了:mounted: function () {
this.list.push('DDDDD')
console.log(this.$el.querySelectorAll('.item').length) // 3
}
複製代碼
這時候若是咱們有需求須要經過 li 的個數來計算出 ul 容器的高度來進行佈局,顯然就有問題了。而這時候 Vue 的 nextTick 就能夠幫助咱們解決這個問題:
mounted: function () {
this.list.push('DDDDD')
Vue.nextTick(function() {
console.log(this.$el.querySelectorAll('.item').length) // 4
// ... 計算
})
複製代碼
關於 Vue 的異步更新隊列,官網是這麼說的:
當你設置 vm.someData = 'new value' ,該組件不會當即從新渲染。當刷新隊列時,組件會在事件循環隊列清空時的下一個「tick」更新。多數狀況咱們不須要關心這個過程,可是若是你想在 DOM 狀態更新後作點什麼,這就可能會有些棘手。雖然 Vue.js 一般鼓勵開發人員沿着「數據驅動」的方式思考,避免直接接觸 DOM,可是有時咱們確實要這麼作。爲了在數據變化以後等待 Vue 完成更新 DOM ,能夠在數據變化以後當即使用 Vue.nextTick(callback) 。這樣回調函數在 DOM 更新完成後就會調用。
簡單說,由於 DOM 至少會在當前線程裏面的代碼所有執行完畢再更新。因此不可能作到在修改數據後而且 DOM 更新後再執行,要保證在 DOM 更新之後再執行某一塊代碼,就必須把這塊代碼放到下一次事件循環裏面,好比 setTimeout(fn, 0),這樣 DOM 更新後,就會當即執行這塊代碼。vue
劃重點: 隊列、事件循環java
咱們都知道,js 執行的全部任務都須要排隊,一個任務必需要等它前面的一個任務執行完以後才能執行。若是前一個任務須要花費大量的時間來計算,那麼後一個任務就必須一直等它執行完纔會輪到它執行,這就是單線程的特性。 而 js 的任務分爲兩種,同步任務和異步任務:node
用一個醜但易懂的圖來表示:git
因此結果輸出是這樣就很好理解了:被稱做事件循環的緣由在於,同步的任務可能會生成新的任務,所以它一直在不停的查找新的事件並執行。一次循環的執行稱之爲 tick,在這個循環裏執行的代碼被稱做 task,而整個過程是不斷重複的。github
console.log(1);
setTimeout(()=>{
console.log(2);
},1000);
while (true){}
複製代碼
上面代碼在輸出 1 以後(謹慎使用!個人瀏覽器就被卡死了~),定時器被塞到任務隊列裏,而後主線程繼續往下執行,碰到一個死循環,致使任務隊列裏的任務永遠不會被執行,所以不會輸出 2數組
除了咱們的主線程以外,任務隊列分爲 microtask 和 macrotask,一般咱們會稱之爲微任務和宏任務。 microtask 這一名詞在js中是個比較新的概念,咱們一般是在學習 ES6 的 Promise 時才初次接觸到。promise
console.log(1)
setTimeout(function(){
console.log(2)
})
Promise.resolve().then(function(){
console.log('promise1')
}).then(function(){
console.log('promise2')
})
console.log(4)
複製代碼
根據執行順序,上面代碼的輸出結果很容易就能得出了:瀏覽器
讓咱們回到上面的主題,Vue 的 nextTick方法, 從 源碼 不難發現,Vue 在內部嘗試對異步隊列使用原生的setImmediate
Promise.then
和MessageChannel
,若是當前執行環境不支持,就採用setTimeout(fn, 0)
代替。
node原生就支持 process.nextTick(fn)
和setImmediate(fn)
方法,而且process.nextTick(fn)
會被當作microtask
順序執行。