setTimeout 實現原理, 機制
JS 執行機制說起
瀏覽器(或者說 JS 引擎)執行 JS 的機制是基於事件循環。
由於 JS 是單線程,所以同一時間只能執行一個任務,其他任務就得排隊,后續任務必須等到前一個任務結束才能開始執行。
為了避免因為某些長時間任務造成的無意義等待,JS 引入了異步的概念,用另一個線程來管理異步任務。
同步任務直接在主線程隊列中順序執行,而異步任務會進入另一個任務隊列,不會阻塞主線程;
等到主線程隊列空了(執行完了)的時候,就會去異步隊列查詢是否有可執行的異步任務了(異步任務通常進入異步隊列之后還要等一些條件才能執行,如 ajax 請求、文件讀寫),如果某個異步任務可以執行了便加入主線程隊列,以此循環;
定時器也是一種異步任務,通常瀏覽器都有一個獨立的定時器模塊,定時器的延遲時間就由定時器模塊來管理,當某個定時器到了可執行狀態,就會被加入主線程隊列。
setTimeout 注冊的函數 fn 會交給瀏覽器的定時器模塊來管理,延遲時間到了就將 fn 加入主進程執行隊列,如果隊列前面還有沒有執行完的代碼,則又需要花一點時間等待才能執行到 fn,所以實際的延遲時間會比設置的長;
如在 fn 之前正好有一個超級大循環,那延遲時間就不是一丁點了。
(function testSetTimeout() {
const label = 'setTimeout';
console.time(label);
setTimeout(() => {
console.timeEnd(label);
}, 0);
for(let i = 0; i < 1000; i++) {
console.log(i);
}
})();
// setTimeout: 133.2880859375ms
setInterval 的實現機制跟 setTimeout 類似,只不過 setInterval 是重復執行的。
對於 setInterval(fn, 100) 容易產生一個誤區:
並不是上一次 fn 執行完了之后再過 100ms 才開始執行下一次 fn。 事實上,setInterval 並不管上一次 fn 的執行結果,而是每隔 100ms 就將 fn 放入主線程隊列;
而兩次 fn 之間具體間隔多久就不一定了,跟 setTimeout 實際延遲時間類似,和 JS 執行情況有關。
(function testSetInterval() {
let i = 0;
const start = Date.now();
const timer = setInterval(() => {
i += 1;
i === 5 && clearInterval(timer);
console.log(`第${i}次開始`, Date.now() - start);
for(let i = 0; i < 10000; i++) {}
console.log(`第${i}次結束`, Date.now() - start);
}, 100);
})();
- Stack 棧
- Queue 隊列
- Heap 堆
http://www.alloyteam.com/2016/05/javascript-timer/
requestAnimationFrame
window.requestAnimationFrame(callback);
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
// animation
var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.transform = 'translateX(' + Math.min(progress / 10, 200) + 'px)';
if (progress < 2000) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
https://caniuse.com/#feat=requestanimationframe
https://css-tricks.com/using-requestanimationframe/
function repeatOften() {
// Do whatever
requestAnimationFrame(repeatOften);
}
requestAnimationFrame(repeatOften);
requestAnimationFrame demos
https://codepen.io/xgqfrms/pen/ZEzeaEL
https://codepen.io/xgqfrms/pen/zYOZPYd
(function testRequestAnimationFrame() {
window.count = window.count || 0;
window.count++;
if(count < 100) {
const label = 'requestAnimationFrame';
console.time(label);
requestAnimationFrame(() => {
console.timeEnd(label);
testRequestAnimationFrame()
});
}
})();
setImmediate (IE / Edge)
do not use it!
https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate
next.tick()
https://stackoverflow.com/questions/15349733/setimmediate-vs-nexttick
-
如果要將函數排在事件隊列中已有的任何I / O事件回調之后,請使用setImmediate;
-
使用process.nextTick有效地將函數排在事件隊列頭部,以便在當前函數完成后立即執行;
https://www.haorooms.com/post/js_setTimeout
setTimeout和setinterval的最主要區別是:
-
setTimeou t只運行一次,也就是說設定的時間到后就觸發運行指定代碼,運行完后即結束;
如果運行的代碼中再次運行同樣的setTimeout命令,則可循環運行。(即 要循環運行,需函數自身再次調用 setTimeout()) -
setinterval是循環運行的,即每到設定時間間隔就觸發指定代碼; 這是真正的定時器。
setinterval使用簡單,而setTimeout則比較靈活,可以隨時退出循環,而且可以設置為按不固定的時間間隔來運行,比如第一次1秒,第二次2秒,第三次3秒。
事件循環模型
在單線程的 Javascript 引擎中,setTimeout() 是如何運行的呢,這里就要提到瀏覽器內核中的事件循環模型了;
簡單的講,在 Javascript 執行引擎之外,有一個任務隊列,當在代碼中調用 setTimeout() 方法時,注冊的延時方法會交由瀏覽器內核其他模塊(以 webkit 為例,是 webcore 模塊)處理,當延時方法到達觸發條件,即到達設置的延時時間時,這一延時方法被添加至任務隊列里;
這一過程由瀏覽器內核其他模塊處理,與執行引擎主線程獨立,執行引擎在主線程方法執行完畢,到達空閑狀態時,會從任務隊列中順序獲取任務來執行;
這一過程是一個不斷循環的過程,稱為事件循環模型。
Javascript 執行引擎的主線程運行的時候,產生堆(heap)和棧(stack);
程序中代碼依次進入棧中等待執行,當調用 setTimeout() 方法時,即圖中右側 WebAPIs 方法時,瀏覽器內核相應模塊開始延時方法的處理,
當延時方法到達觸發條件時,方法被添加到用於回調的任務隊列,只要執行引擎棧中的代碼執行完畢,主線程就會去讀取任務隊列,依次執行那些滿足觸發條件的回調函數。
https://www.ruanyifeng.com/blog/2014/10/event-loop.html
©xgqfrms 2012-2020
www.cnblogs.com 發布文章使用:只允許注冊用戶才可以訪問!