看了這麼久JS,事件隊列你真的懂嗎?

關於JS事件隊列的一些總結

關於任務隊列

其實之所以我們要去關心JS的任務隊列,主要還是因爲JS的單線程的特質決定。

爲什麼JavaScript是單線程?

本段來自阮老師的博客中對JS單線程的介紹。

JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。那麼,爲什麼JavaScript不能有多個線程呢?這樣能提高效率啊。

JavaScript的單線程,與它的用途有關。作爲瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很複雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器應該以哪個線程爲準?

所以,爲了避免複雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特徵,將來也不會改變。

爲了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準並沒有改變JavaScript單線程的本質。

任務隊列的本質

  • 所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
  • 主線程之外,還存在一個”任務隊列”(task queue)。只要異步任務有了運行結果,就在”任務隊列”之中放置一個事件。
  • 一旦”執行棧”中的所有同步任務執行完畢,系統就會讀取”任務隊列”,看看裏面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
  • 主線程不斷重複上面的第三步。

關於 setTimeOut、setImmediate、process.nextTick()的比較

setTimeout()

  • 將事件插入到了事件隊列,必須等到當前代碼(執行棧)執行完,主線程纔會去執行它指定的回調函數。
  • 當主線程時間執行過長,無法保證回調會在事件指定的時間執行。
  • 瀏覽器端每次setTimeout會有4ms的延遲,當連續執行多個setTimeout,有可能會阻塞進程,造成性能問題。

setImmediate()

  • 事件插入到事件隊列尾部,主線程和事件隊列的函數執行完成之後立即執行。和setTimeout(fn,0)的效果差不多。
  • 服務端node提供的方法。瀏覽器端最新的api也有類似實現:window.setImmediate,但支持的瀏覽器很少。

process.nextTick()

  • 插入到事件隊列尾部,但在下次事件隊列之前會執行。也就是說,它指定的任務總是發生在所有異步任務之前,當前主線程的末尾。
  • 大致流程:當前”執行棧”的尾部–>下一次Event Loop(主線程讀取”任務隊列”)之前–>觸發process指定的回調函數。
  • 服務器端node提供的辦法。用此方法可以用於處於異步延遲的問題。
  • 可以理解爲:此次不行,預約下次優先執行。

關於消除 setTimeout 延遲的實踐:soon.js

why?

setTimeout 的介紹所言,瀏覽器端每次setTimeout會有4ms的延遲,當連續執行多個setTimeout,有可能會阻塞進程,造成性能問題。

soon.js就是關於這個問題的一個好的實踐。但其實大多數情況我們不必爲這4ms的延遲計較,除非你在一次執行中setTimeout的次數足夠多。代碼很短,可以用來學習下。

使用方法

可以參考示例

源碼:

// See http://www.bluejava.com/4NS/Speed-up-your-Websites-with-a-Faster-setTimeout-using-soon
// 使用 soon.js 處理在瀏覽器端 settimeout(大量調用),4ms * n 的延遲問題


var soon = (function() {

        var fq = []; // 事件隊列;

        function callQueue()
        {
            while(fq.length) // 執行隊列中事件
            {
                var fe = fq[0];
                fe.f.apply(fe.m,fe.a) // 執行隊列中事件
                fq.shift(); 
            }
        }

        // 異步執行隊列事件,最大效率
        var cqYield = (function() {

                // 通過 MutationObserver 來監聽 Dom 來執行回調,此法最快
                if(typeof MutationObserver !== "undefined")
                {
                    var dd = document.createElement("div");
                    var mo = new MutationObserver(callQueue);
                    mo.observe(dd, { attributes: true });

                    return function(fn) { dd.setAttribute("a",0); } // trigger callback to
                }

                // 如果支持 setImmediate ,採取此策略,其實 setImmediate 和 setTimeout(callQueue,0) 差不多
                if(typeof setImmediate !== "undefined")
                    return function() { setImmediate(callQueue) }

                // 沒辦法了,就用 setTimeOut 的辦法
                return function() { setTimeout(callQueue,0) }
            })();

        return function(fn) {
                // 隊列事件裝載進一個數組
                fq.push({f:fn,a:[].slice.apply(arguments).splice(1),m:this});

                if(fq.length == 1) // 在添加第一個條目時,啓動回調函數
                    cqYield();
            };

    })();

分析

其實,值得分析就是一個新的東西–MutationObserver

MutationObserver給開發者們提供了一種能在某個範圍內的DOM樹發生變化時作出適當反應的能力.該API設計用來替換掉在DOM3事件規範中引入的Mutation事件.

簡而言之,就是這個東西比setTimeOutsetImmediate快,瀏覽器支持就用它就行了。

關於soon.js的更詳細的介紹可以查看這篇文章:Speed up your Websites with a Faster setTimeout using soon()

MutationObserver給開發者們提供了一種能在某個範圍內的DOM樹發生變化時作出適當反應的能力.該API設計用來替換掉在DOM3事件規範中引入的Mutation事件.

參考博客:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章