Event Loop是個什麼玩意:從 Vue 的 nextTick 提及

熟悉 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 執行的全部任務都須要排隊,一個任務必需要等它前面的一個任務執行完以後才能執行。若是前一個任務須要花費大量的時間來計算,那麼後一個任務就必須一直等它執行完纔會輪到它執行,這就是單線程的特性。 而 js 的任務分爲兩種,同步任務和異步任務:node

  • 同步任務就是按照順序一個一個的執行任務,後一個任務要執行必須等它前一個任務完成
  • 異步任務(好比回調)不會佔用主線程,會被塞到一個任務隊列,等主線程的任務執行完畢,就會把這個異步任務隊列裏的任務放回主線程依次執行

用一個醜但易懂的圖來表示:git

因此結果輸出是這樣就很好理解了:

Event Loop(事件循環)

被稱做事件循環的緣由在於,同步的任務可能會生成新的任務,所以它一直在不停的查找新的事件並執行。一次循環的執行稱之爲 tick,在這個循環裏執行的代碼被稱做 task,而整個過程是不斷重複的。github

console.log(1);

setTimeout(()=>{
  console.log(2);
},1000);

while (true){}
複製代碼

上面代碼在輸出 1 以後(謹慎使用!個人瀏覽器就被卡死了~),定時器被塞到任務隊列裏,而後主線程繼續往下執行,碰到一個死循環,致使任務隊列裏的任務永遠不會被執行,所以不會輸出 2數組

事件隊列

除了咱們的主線程以外,任務隊列分爲 microtaskmacrotask,一般咱們會稱之爲微任務和宏任務。 microtask 這一名詞在js中是個比較新的概念,咱們一般是在學習 ES6 的 Promise 時才初次接觸到。promise

  • 執行優先級上,主線程任務 > microtask > macrotask。
  • 典型的 macrotask 有 setTimeout 和 setInterval,以及只有 IE 支持的 setImmediate,還有 MessageChannel等,ES6的 Promise 則是屬於 microtask
console.log(1)

setTimeout(function(){
	console.log(2)
})

Promise.resolve().then(function(){
	console.log('promise1')
}).then(function(){
	console.log('promise2')
})

console.log(4)

複製代碼

根據執行順序,上面代碼的輸出結果很容易就能得出了:瀏覽器

nextTick

讓咱們回到上面的主題,Vue 的 nextTick方法, 從 源碼 不難發現,Vue 在內部嘗試對異步隊列使用原生的setImmediate Promise.thenMessageChannel,若是當前執行環境不支持,就採用setTimeout(fn, 0)代替。

Nodejs

node原生就支持 process.nextTick(fn)setImmediate(fn)方法,而且process.nextTick(fn)會被當作microtask順序執行。

相關文章
相關標籤/搜索