摘要:關(guān)于定時(shí)器的源碼在文件中,進(jìn)入就關(guān)于定時(shí)器的一些設(shè)計(jì)解釋,因?yàn)槭亲龇?wù)端代碼,在內(nèi)部等大部分事件都會創(chuàng)建一個(gè)定時(shí)器,任何時(shí)間都可能存在大量的定時(shí)器任務(wù),所以設(shè)計(jì)一個(gè)高效的定時(shí)器是很有必要的。
博客文章地址
setTimeout與setIntervalsetTimeout 和 setInterval 是我們在 javaScript 中經(jīng)常用到的定時(shí)器,setTimeout 方法用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式,setInterval 可按照指定的周期不停的調(diào)用函數(shù)或計(jì)算表達(dá)式。
但是當(dāng)我們要循環(huán)調(diào)用某任務(wù)時(shí)候,處了用 setInterval 指定周期外,我們也可以用函數(shù)中嵌套setTimeout 回掉自己來實(shí)現(xiàn), 可以看下面一段代碼
// A function myTimeout() { doStuff() setTimeout(myTimeout, 1000) } myTimeout() // B function myTimeout() { doStuff() } myTimeout() setInterval(myTimeout, 1000)
上面A, B 兩個(gè)方法都是在循環(huán)執(zhí)行 myTimeout 函數(shù),可是它們之間有什么不同呢。我們大部分都知道這其實(shí)取決與 doStuff 所消耗的時(shí)間, 如下圖所示如果 doStuff 消耗時(shí)間很短(實(shí)際中大部分消耗時(shí)間都很短很難有所察覺),兩個(gè)方法效果近似
當(dāng)doStuff是一個(gè)很復(fù)雜的計(jì)算,需要消耗很長時(shí)間時(shí)候,我們就可以分析出A 方法(用setTimeout回掉)能夠保障每一次任務(wù)結(jié)束到下一次任務(wù)開始的時(shí)間間隔為我們預(yù)期的值,但是B(setInterval)卻能保證任務(wù)開始到下一次任務(wù)開始之間的間隔為我們預(yù)期的值,(當(dāng)然如果doStuff執(zhí)行時(shí)間比我們預(yù)期間隔還長,setInterval 還有可能會直接放棄某次任務(wù),這種罕見情況我們暫不考慮)
為了感受其中的差異,這里定義一個(gè)模擬任務(wù)執(zhí)行的函數(shù)
function wait(time) { var start = Date.now() while(Date.now() - start < time){} }
wait什么也沒做,但是卻可以阻塞進(jìn)程time毫秒的時(shí)間,然后我們定義 doStuff,讓它每次執(zhí)行阻塞進(jìn)程500ms,而且可以輸出間隔時(shí)間信息,以及本次執(zhí)行結(jié)束到下次執(zhí)行開始的時(shí)間間隔
function doStuff() { console.log("doStuff___start", new Date().getSeconds()) //每次輸出當(dāng)前的秒數(shù) console.timeEnd("timeout") //每次輸出這次執(zhí)行與上一次執(zhí)行結(jié)束的時(shí)間間隔 wait(500) console.time("timeout") }
然后我們分別運(yùn)行A, B兩種方法
/* * A方法 setTimeout */ // doStuff___start 36 // timeout: 1002.865966796875ms // doStuff___start 37 // timeout: 1004.380859375ms // doStuff___start 39 // timeout: 1001.550048828125ms // doStuff___start 40 // timeout: 1001.051025390625ms // doStuff___start 42 // timeout: 1001.637939453125ms /* * B方法 setInterval */ // doStuff___start 50 // timeout: 500.412109375ms // doStuff___start 51 // timeout: 500.51806640625ms // doStuff___start 52 // timeout: 500.099853515625ms // doStuff___start 53 // timeout: 499.873291015625ms // doStuff___start 54 // timeout: 500.439697265625ms
可以看到 A 方法(用setTimeout回掉),我們保證了每次進(jìn)程結(jié)束到下一次進(jìn)程開始的間隔為預(yù)期值,但是從每次進(jìn)程開始的時(shí)間間隔(我們這里精確到了秒)是會改變的,而B 方法(setInterval)表現(xiàn)的和我們預(yù)期的相同,正好與A相反。
nodejs中的差異目前為止所以的表現(xiàn)都合理,至少很符合預(yù)期。可是當(dāng)我在 nodejs(v8.1.4) 中測試時(shí)候,卻發(fā)現(xiàn)不管我用 setTimeout 還是 setInterval ,他們總是能表現(xiàn)出同樣的效果(都是上面A方法的效果【用setTimeout回掉】)。這一點(diǎn)讓我很困惑,經(jīng)過一番探究,在 nodejs 關(guān)于 timers 的代碼中找到了答案。
nodejs 關(guān)于定時(shí)器的源碼在 node/lib/timer 文件中,進(jìn)入就關(guān)于定時(shí)器的一些設(shè)計(jì)解釋,因?yàn)?node 是做服務(wù)端代碼,在內(nèi)部 TCP, I/O.. 等大部分事件都會創(chuàng)建一個(gè)定時(shí)器,任何時(shí)間都可能存在大量的定時(shí)器任務(wù),所以設(shè)計(jì)一個(gè)高效的定時(shí)器是很有必要的。
nodejs實(shí)現(xiàn)定時(shí)器也很巧妙, 為了可以輕松取消添加事件,nodejs使用了雙向鏈表將 timer 插入和移除操作復(fù)雜度降低,具體實(shí)現(xiàn)在 node/lib/internal/linkedlist.js 文件中, 鏈表缺點(diǎn)自然是去查找元素,但是node ,把同一個(gè)時(shí)間間隔的 timer 維護(hù)在同一個(gè)雙向鏈表中,這樣就不需要去查找,因?yàn)橄炔迦氲目偸窍葓?zhí)行,具體的分析可以參考這篇文章 通過源碼解析 Node.js 中高效的 timer.
回歸主題,在 nodejs 關(guān)于 timer 的源碼下,我們可以找到執(zhí)行定時(shí)器的代碼
// setInterval 會返回 createRepeatTimeout 的返回值 exports.setInterval = function(callback, repeat, arg1, arg2, arg3) { ... return createRepeatTimeout(callback, repeat, args); } // createRepeatTimeout函數(shù)生成timer function createRepeatTimeout(callback, repeat, args) { repeat *= 1; // coalesce to number or NaN if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) repeat = 1; // 這里間隔如果小于1或者大于TIMEOUT_MAX(2^31-1)都會按照1計(jì)算 var timer = new Timeout(repeat, callback, args); timer._repeat = repeat; // 追加了_repeat屬性表示要循環(huán)調(diào)用 ... return timer; } // 函數(shù)回掉時(shí),可以看到執(zhí)行時(shí)在ontimeout函數(shù)中 function tryOnTimeout(timer, list) { ... try { ontimeout(timer); threw = false; } finally { if (timerAsyncId !== null) { if (!threw) ... } ... } // ontimeout執(zhí)行 function ontimeout(timer) { var args = timer._timerArgs; var callback = timer._onTimeout; if (typeof callback !== "function") return promiseResolve(callback, args[0]); if (!args) timer._onTimeout(); else { switch (args.length) { case 1: timer._onTimeout(args[0]); break; case 2: timer._onTimeout(args[0], args[1]); break; case 3: timer._onTimeout(args[0], args[1], args[2]); break; default: Function.prototype.apply.call(callback, timer, args); } } if (timer._repeat) // 追加timer rearm(timer); }
上面代碼分析,可以看到追加循環(huán)調(diào)用是在 ontimeout 函數(shù)中,它里面一大堆判斷參數(shù)個(gè)數(shù)的內(nèi)容可以不管,最后的if(timer._repeat) rearm(timer)判斷是否要循環(huán)調(diào)用,可以看到它是在上面 timer._onTimeout 執(zhí)行完之后才去執(zhí)行的。這和我們開始寫的A方法(用setTimeout回掉)基本類似,至此在 nodejs 表現(xiàn)出的不同就可以理解了。
看 issues , 關(guān)于這個(gè)問題也有很多討論,還是有不少人想把它改會我們熟悉的方式的
setTimeout interval should not include duration of callback
setInterval interval includes duration of callback
具體最后要怎樣還是要看后面的版本修改了。
參考資料nodejs源碼
setTimeout or setInterval?
JavaScript的setTimeout和setInterval的深入理解
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/88843.html
摘要:的單線程,與它的用途有關(guān)。特點(diǎn)的顯著特點(diǎn)異步機(jī)制事件驅(qū)動。隊(duì)列的讀取輪詢線程,事件的消費(fèi)者,的主角。它將不同的任務(wù)分配給不同的線程,形成一個(gè)事件循環(huán),以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給引擎。 這兩天跟同事同事討論遇到的一個(gè)問題,js中的event loop,引出了chrome與node中運(yùn)行具有setTimeout和Promise的程序時(shí)候執(zhí)行結(jié)果不一樣的問題,從而引出了Nodejs的...
摘要:主線程會暫時(shí)存儲等異步操作,直接向下執(zhí)行,當(dāng)某個(gè)異步事件觸發(fā)時(shí),再通知主線程執(zhí)行相應(yīng)的回調(diào)函數(shù),通過這種機(jī)制,避免了單線程中異步操作耗時(shí)對后續(xù)任務(wù)的影響。 背景 在研究js的異步的實(shí)現(xiàn)方式的時(shí)候,發(fā)現(xiàn)了JavaScript 中的 macrotask 和 microtask 的概念。在查閱了一番資料之后,對其中的執(zhí)行機(jī)制有所了解,下面整理出來,希望可以幫助更多人。 先了解一下js的任務(wù)執(zhí)...
摘要:事件觸發(fā)線程主要負(fù)責(zé)將準(zhǔn)備好的事件交給引擎線程執(zhí)行。它將不同的任務(wù)分配給不同的線程,形成一個(gè)事件循環(huán),以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給引擎。 Fundebug經(jīng)作者浪里行舟授權(quán)首發(fā),未經(jīng)同意請勿轉(zhuǎn)載。 前言 本文我們將會介紹 JS 實(shí)現(xiàn)異步的原理,并且了解了在瀏覽器和 Node 中 Event Loop 其實(shí)是不相同的。 一、線程與進(jìn)程 1. 概念 我們經(jīng)常說 JS 是單線程執(zhí)行的,...
我們講述的是關(guān)于 ahooks 源碼系列文章的第七篇,總結(jié)主要講述下面幾點(diǎn): 鞏固 React hooks 的理解。 學(xué)習(xí)如何抽象自定義 hooks。構(gòu)建屬于自己的 React hooks 工具庫。 培養(yǎng)閱讀學(xué)習(xí)源碼的習(xí)慣,工具庫是一個(gè)對源碼閱讀不錯的選擇。 注:本系列對 ahooks 的源碼解析是基于v3.3.13。自己 folk 了一份源碼,主要是對源碼做了一些解讀,可見詳情。 ...
摘要:概述本篇主要介紹的運(yùn)行機(jī)制單線程事件循環(huán)結(jié)論先在中利用運(yùn)行至完成和非阻塞完成單線程下異步任務(wù)的處理就是先處理主模塊主線程上的同步任務(wù)再處理異步任務(wù)異步任務(wù)使用事件循環(huán)機(jī)制完成調(diào)度涉及的內(nèi)容有單線程事件循環(huán)同步執(zhí)行異步執(zhí)行定時(shí)器的事件循環(huán)開始 1.概述 本篇主要介紹JavaScript的運(yùn)行機(jī)制:單線程事件循環(huán)(Event Loop). 結(jié)論先: 在JavaScript中, 利用運(yùn)行至...
閱讀 1660·2021-11-12 10:35
閱讀 1611·2021-08-03 14:02
閱讀 2655·2019-08-30 15:55
閱讀 2024·2019-08-30 15:54
閱讀 735·2019-08-30 14:01
閱讀 2421·2019-08-29 17:07
閱讀 2246·2019-08-26 18:37
閱讀 3028·2019-08-26 16:51