JS魔法堂:初探傳說中的setImmediate函數
一、前言 由於JavaScript程式為單線程,因此在執行長時間的操作時(如迴圈和遞迴操作)到導致UI線程長期被阻塞,無法響應使用者操作請求(如點擊按鈕等),讓使用者體驗大打折扣。於是想到將一個長時間操作切片成N個小操作並非同步執行,例如jsDeferred中的 Deferred.repeat函數 就提供這樣的解決辦法,其實質就是通過 setTimeout事件 或 script元素 的 onerror/onload事件 來非同步呼叫這些小操作,從而儘早釋放UI線程。 從IE10開始引入了setImmediate介面來代替setTimeout來完成上述功能,下面將記錄該介面的資訊,由於內容會涉及到event loop、調用棧等知識,而我對相關內容瞭解仍不全面,因此下面的內容若有紕漏請各位指正,謝謝! 二、同步和非同步呼叫 由於JavaScript是通過非同步呼叫來儘早釋放UI線程,因此我們先要瞭解同步和非同步執行的具體含義: 任務的執行實質上分為兩步:①.執行,②.擷取執行結果。 同步執行:執行後等待直到擷取執行結果; 非同步執行:執行後不等待,而是通過一系列手段(輪詢、事件監聽和event loop等)擷取執行結果,而在執行後和擷取結果前的那段時間可以介入其他任務操作。 二、setTimeout(handler, 0)的問題 由於setTimeout存在時間精度,因此setTimeout(handler,0)中setTimeout事件插入事件隊列的延時必定大於0ms,而handler的執行延時則更大了。具體為IE5~8和不插電源的IE9的時間精度為15.6ms,插電源的IE9和其他瀏覽器則為4ms。 經微軟和Chrome團隊實驗所得降低時間精度將會大大縮短筆記本的續航時間,也是就說更耗電,因此即使瀏覽器廠商有能力縮短時間精度,但基於多方面的考慮,依然保持上述的精度值。 三、setImmediate介面 對於通過非同步執行的手段對任務切片,由於UI線程得到釋放從而提高使用者體驗,但相對於採用同步執行,整體的任務執行時間較被拉長,因此我們希望切片的小操作越快執行越好。而setImmediate介面則是為此而生的。 setImmediate(handler) 並不像 setTimeout(handler, 0) 由event loop檢測系統時間是否到點然後向事件隊列插入一個事件,然後呼叫事件的回調方法handler。而是監控UI線程的調用棧,一旦調用棧為空白則將handler壓棧。 理論上通過setImmediate執行非同步呼叫的延時一定比通過setTimeout的短,但事實又是如何呢? 我在IE11上操作測試,setTimout的延時為4ms左右,而setImmediate的延時為2ms左右。 注意: 1. 通過setImmediate的非同步呼叫的延時不是0ms哦! 2. 而且有時候setImmediate的延時比setTimeout的多1~2ms哦! 3. 而且setImmediate和setTimeout的延時均比img元素onerror事件的延時間長度哦! 推測: 1. 對於setImmediate的延時有時比setTimeout的要長,由於setImmediate要先監控調用棧,若調用棧為空白才壓棧,那麼在壓棧之前event loop已經將setTimeout事件的回呼函數壓棧了。 2. 對於兩者均比img元素onerror事件的長,由於設定img.src後馬上發起請求(不一定為網路IO)當載入失敗時onerror事件則會比setTimeout事件先加入事件隊列。