js實(shí)現(xiàn)0ms延時(shí)定時(shí)器的幾種方式
這兩天看到一篇介紹《如何實(shí)現(xiàn)準(zhǔn)時(shí)的 setTimeout?》的文章,文章起源于一道面試題:有什么辦法讓setTimeout準(zhǔn)時(shí)呀?具體文章內(nèi)容可查看附錄【1】,看完之后,引起了我對(duì)setTimeout這個(gè)函數(shù)的探究興趣,因此在MDN上重新查閱了相關(guān)文檔,其中提到【最小延時(shí) >=4ms】的一點(diǎn),因此使用setTimeout不能實(shí)現(xiàn)0ms延時(shí)的定時(shí)器,如果要實(shí)現(xiàn)的話,提供了一個(gè)參考鏈接【2】,作者的實(shí)現(xiàn)思路是通過postMessage來模擬,繞過setTimeout的限制,從而實(shí)現(xiàn)0ms延時(shí)的定時(shí)器,說簡(jiǎn)單來講就是起了一個(gè)宏任務(wù)去執(zhí)行回調(diào),先具體看下是怎么實(shí)現(xiàn)的:
(function() {
var timeouts = [];
var messageName = "zero-timeout-message";
// Like setTimeout, but only takes a function argument. There's
// no time argument (always zero) and no arguments (you have to use a closure)
function setZeroTimeout(fn) {
timeouts.push(fn);
window.postMessage(messageName, "*");
}
function handleMessage(event) {
if (event.source == window && event.data == messageName) {
event.stopPropagation();
if (timeouts.length > 0) {
var fn = timeouts.shift();
fn();
}
}
}
window.addEventListener("message", handleMessage, true);
// Add the one thing we want added to the window object.
window.setZeroTimeout = setZeroTimeout;
})();
作者還提供了一個(gè)demo頁(yè)面【3】,通過于setTimeout(0)進(jìn)行對(duì)比,在我瀏覽器的執(zhí)行結(jié)果如下:
100 iterations of setZeroTimeout took 15 milliseconds.
100 iterations of setTimeout(0) took 488 milliseconds.
根據(jù)結(jié)果對(duì)比來看,setZeroTimeout執(zhí)行比setTimeout快了上百倍,這是一個(gè)巨大的提升。今天想討論的是除了上述這種方式,還可以通過哪些方式來實(shí)現(xiàn)一個(gè)0ms延時(shí)的定時(shí)器呢,首先,我們要確定一下我們自定義的定時(shí)器是異步的,其次是盡可能早的被執(zhí)行。說起異步,js提供了好幾種解決方案,我們可以逐一去驗(yàn)證。
在深入討論各種實(shí)現(xiàn)方式之前,約定提供的setTimeout對(duì)比版本如下,后面自定義實(shí)現(xiàn)的方案都將和setTimeout版本的執(zhí)行時(shí)間進(jìn)行對(duì)比,代碼比較簡(jiǎn)單:
(function() {
let i = 0;
const start = Date.now();
function test() {
if(i++ < 100) {
setTimeout(test);
} else {
console.log('setTimeout執(zhí)行時(shí)間:', Date.now() - start);
}
}
setTimeout(test);
})();
queueMicrotask
queueMicrotask這個(gè)api可以添加一個(gè)微任務(wù),使用比較簡(jiǎn)單,直接傳遞一個(gè)回調(diào)函數(shù)即可,具體實(shí)現(xiàn)如下:
(function() {
function setZeroTimeout(fn) {
queueMicrotask(fn);
}
let i = 0;
const start = Date.now();
function test() {
if(i++ < 100) {
setZeroTimeout(test);
} else {
console.log('setZeroTimeout執(zhí)行時(shí)間:', Date.now() - start);
}
}
setZeroTimeout(test);
})();
通過和setTimeout版本進(jìn)行對(duì)比,最終結(jié)果如下:
setZeroTimeout執(zhí)行時(shí)間: 2
setTimeout執(zhí)行時(shí)間: 490
關(guān)于這個(gè)API的介紹在MDN上有詳細(xì)的說明,就不展開介紹了,這里多說一點(diǎn),根據(jù)規(guī)范文檔的說明,大多數(shù)情況下,推薦使用requestAnimationFrame()和requestIdleCallback()等api,因?yàn)閝ueueMicrotask會(huì)阻塞渲染,在很多時(shí)候都不是一種好的實(shí)踐。
async/await
async/await對(duì)于前端開發(fā)人員來說已經(jīng)是必不可少的了,這里我們也可以用來實(shí)現(xiàn):
(function() {
async function setAsyncTimeout(fn) {
Promise.resolve().then(fn);
}
let i = 0;
const start = Date.now();
async function test() {
if (i++ < 100) {
await setAsyncTimeout(test);
} else {
console.log('setAsyncTimeout執(zhí)行時(shí)間:', Date.now() - start);
}
}
setAsyncTimeout(test);
})();
通過和setTimeout版本進(jìn)行對(duì)比,最終結(jié)果如下:
setAsyncTimeout執(zhí)行時(shí)間: 2
setTimeout執(zhí)行時(shí)間: 490
如果不嫌麻煩,還可以通過Promise來實(shí)現(xiàn),其實(shí)都是大同小異,無非多些點(diǎn)代碼,這里就省略了。
MessageChannel
MessageChannel允許我們創(chuàng)建一個(gè)新的消息通道,并通過它的兩個(gè)MessagePort屬性發(fā)送數(shù)據(jù),MessageChannel提供端口的概念,實(shí)現(xiàn)端口之間的通信,比如worker/iframe之間的通信。
(function() {
const channel = new MessageChannel();
function setMessageChannelTimeout(fn) {
channel.port2.postMessage(null);
}
channel.port1.onmessage = function() {
test();
};
let i = 0;
const start = Date.now();
function test() {
if(i++ < 100) {
setMessageChannelTimeout(test);
} else {
console.log('setMessageChannelTimeout執(zhí)行時(shí)間:', Date.now() - start);
}
}
setMessageChannelTimeout(test);
})();
通過和setTimeout版本進(jìn)行對(duì)比,最終結(jié)果如下:
setMessageChannelTimeout執(zhí)行時(shí)間: 4
setTimeout執(zhí)行時(shí)間: 490
第三種方式運(yùn)行時(shí)間比前面兩種更長(zhǎng)些,因?yàn)橥ㄟ^MessageChannel產(chǎn)生的是宏任務(wù),其他兩種是微任務(wù),微任務(wù)執(zhí)行靠前,且會(huì)阻塞主線程,因此時(shí)間會(huì)長(zhǎng)一點(diǎn)。
最后
本文提供了三種實(shí)現(xiàn)方式,都是圍繞js提供異步解決方案來實(shí)現(xiàn)的,實(shí)現(xiàn)本身并不復(fù)雜。
附錄
【1】https://mp.weixin.qq.com/s/QRIXBoKr2dMgLob3Atq9-g
【2】https://dbaron.org/log/20100309-faster-timeouts
【3】https://dbaron.org/mozilla/zero-timeout
到此這篇關(guān)于js實(shí)現(xiàn)0ms延時(shí)定時(shí)器的幾種方式的文章就介紹到這了,更多相關(guān)js 延時(shí)定時(shí)器 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
整理JavaScript創(chuàng)建對(duì)象的八種方法
JavaScript創(chuàng)建對(duì)象的方法有很多種,本文給大家介紹javascript創(chuàng)建對(duì)象的八種方法,對(duì)javascript創(chuàng)建對(duì)象感興趣的朋友可以參考下本篇文章2015-11-11
Javascript根據(jù)指定下標(biāo)或?qū)ο髣h除數(shù)組元素
刪除數(shù)組元素在工作中經(jīng)常會(huì)用到,本文講解一下Javascript根據(jù)下標(biāo)刪除數(shù)組元素的方法,需要了解的朋友可以參考下2012-12-12
layui-table獲得當(dāng)前行的上/下一行數(shù)據(jù)的例子
今天小編就為大家分享一篇layui-table獲得當(dāng)前行的上/下一行數(shù)據(jù)的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-09-09
ES6擴(kuò)展運(yùn)算符的用途實(shí)例詳解
這篇文章主要介紹了ES6擴(kuò)展運(yùn)算符的用途 ,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-08-08
BootStrap modal模態(tài)彈窗使用小結(jié)
這篇文章主要為大家詳細(xì)介紹了BootStrap modal模態(tài)彈窗使用小結(jié),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
使用JavaScript?將數(shù)據(jù)網(wǎng)格綁定到?GraphQL?服務(wù)的操作方法
GraphQL是管理JavaScript應(yīng)用程序中數(shù)據(jù)的優(yōu)秀工具,本教程展示了GraphQL和SpreadJS如何簡(jiǎn)單地構(gòu)建應(yīng)用程序,?GraphQL?和?SpreadJS都有更多功能可供探索,因此您可以做的事情遠(yuǎn)遠(yuǎn)超出了這個(gè)示例,感興趣的朋友一起看看吧2023-11-11
TypeScript Type Innference(類型判斷)
TypeScript 是微軟開發(fā)的 JavaScript 的超集,TypeScript兼容JavaScript,可以載入JavaScript代碼然后運(yùn)行。接下來通過本文給大家介紹TypeScript Type Innference(類型判斷)的相關(guān)知識(shí),需要的朋友參考下2016-03-03
正則表達(dá)式校驗(yàn)身份證號(hào)碼完整代碼示例
在做用戶實(shí)名驗(yàn)證時(shí),常會(huì)用到身份證號(hào)碼的正則表達(dá)式及校驗(yàn)方案,下面這篇文章主要給大家介紹了關(guān)于正則表達(dá)式校驗(yàn)身份證號(hào)碼的相關(guān)資料,需要的朋友可以參考下2024-04-04
bootstrap select2插件用ajax來獲取和顯示數(shù)據(jù)的實(shí)例
今天小編就為大家分享一篇bootstrap select2插件用ajax來獲取和顯示數(shù)據(jù)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-08-08

