js定時器

從JS執(zhí)行機制說起

瀏覽器(或者說JS引擎)執(zhí)行JS的機制是基于事件循環(huán)兔跌。

由于JS是單線程亿虽,所以同一時間只能執(zhí)行一個任務,其他任務就得排隊绸栅,后續(xù)任務必須等到前一個任務結束才能開始執(zhí)行。

為了避免因為某些長時間任務造成的無意義等待页屠,JS引入了異步的概念粹胯,用另一個線程來管理異步任務。

同步任務直接在主線程隊列中順序執(zhí)行辰企,而異步任務會進入另一個任務隊列风纠,不會阻塞主線程。等到主線程隊列空了(執(zhí)行完了)的時候蟆豫,就會去異步隊列查詢是否有可執(zhí)行的異步任務了(異步任務通常進入異步隊列之后還要等一些條件才能執(zhí)行议忽,如ajax請求、文件讀寫)十减,如果某個異步任務可以執(zhí)行了便加入主線程隊列栈幸,以此循環(huán)。

JS定時器

JS的定時器目前有三個:setTimeout帮辟、setInterval和setImmediate速址。

定時器也是一種異步任務,通常瀏覽器都有一個獨立的定時器模塊由驹,定時器的延遲時間就由定時器模塊來管理芍锚,當某個定時器到了可執(zhí)行狀態(tài),就會被加入主線程隊列蔓榄。

JS定時器非常實用并炮,做動畫的肯定都用到過,也是最常用的異步模型之一甥郑。

有時候一些奇奇怪怪的問題逃魄,加一個setTimeout(fn, 0)(以下簡寫setTimeout(0))就解決了。不過澜搅,如果對定時器本身不熟悉伍俘,也會產(chǎn)生一些奇奇怪怪的問題邪锌。

setTimeout

setTimeout(fn, x)表示延遲x毫秒之后執(zhí)行fn。

使用的時候千萬不要太相信預期癌瘾,延遲的時間嚴格來說總是大于x毫秒的觅丰,至于大多少就要看當時JS的執(zhí)行情況了。

另外妨退,多個定時器如不及時清除(clearTimeout)妇萄,會存在干擾,使延遲時間更加捉摸不透碧注。所以嚣伐,不管定時器有沒有執(zhí)行完,及時清除已經(jīng)不需要的定時器是個好習慣萍丐。

HTML5規(guī)范規(guī)定最小延遲時間不能小于4ms轩端,即x如果小于4,會被當做4來處理逝变。 不過不同瀏覽器的實現(xiàn)不一樣基茵,比如,Chrome可以設置1ms壳影,IE11/Edge是4ms拱层。

setTimeout注冊的函數(shù)fn會交給瀏覽器的定時器模塊來管理,延遲時間到了就將fn加入主進程執(zhí)行隊列宴咧,如果隊列前面還有沒有執(zhí)行完的代碼根灯,則又需要花一點時間等待才能執(zhí)行到fn,所以實際的延遲時間會比設置的長掺栅。如在fn之前正好有一個超級大循環(huán)烙肺,那延遲時間就不是一丁點了。

1

2

3

4

5

6

7

8(functiontestSetTimeout(){

constlabel='setTimeout';

console.time(label);

setTimeout(()=>{

console.timeEnd(label);

},10);

for(leti=0;i<100000000;i++){}

})();

結果是:setTimeout: 335.187ms氧卧,遠遠不止10ms桃笙。

setInterval

setInterval的實現(xiàn)機制跟setTimeout類似,只不過setInterval是重復執(zhí)行的沙绝。

對于setInterval(fn, 100)容易產(chǎn)生一個誤區(qū):并不是上一次fn執(zhí)行完了之后再過100ms才開始執(zhí)行下一次fn搏明。 事實上,setInterval并不管上一次fn的執(zhí)行結果闪檬,而是每隔100ms就將fn放入主線程隊列星著,而兩次fn之間具體間隔多久就不一定了,跟setTimeout實際延遲時間類似粗悯,和JS執(zhí)行情況有關虚循。

1

2

3

4

5

6

7

8

9

10

11(functiontestSetInterval(){

leti=0;

conststart=Date.now();

consttimer=setInterval(()=>{

i+=1;

i===5&&clearInterval(timer);

console.log(`第${i}次開始`,Date.now()-start);

for(leti=0;i<100000000;i++){}

console.log(`第${i}次結束`,Date.now()-start);

},100);

})();

輸出

1

2

3

4

5

6

7

8

9

10第1次開始100

第1次結束1089

第2次開始1091

第2次結束1396

第3次開始1396

第3次結束1701

第4次開始1701

第4次結束2004

第5次開始2004

第5次結束2307

可見,雖然每次fn執(zhí)行時間都很長为黎,但下一次并不是等上一次執(zhí)行完了再過100ms才開始執(zhí)行的邮丰,實際上早就已經(jīng)等在隊列里了。

另外可以看出铭乾,當setInterval的回調函數(shù)執(zhí)行時間超過了延遲時間剪廉,已經(jīng)完全看不出有時間間隔了。

如果setTimeout和setInterval都在延遲100ms之后執(zhí)行炕檩,那么誰先注冊誰就先執(zhí)行回調函數(shù)斗蒋。

setImmediate

這算一個比較新的定時器,目前IE11/Edge支持笛质、Nodejs支持泉沾,Chrome不支持,其他瀏覽器未測試妇押。

從API名字來看很容易聯(lián)想到setTimeout(0)跷究,不過setImmediate應該算是setTimeout(0)的替代版。

在IE11/Edge中敲霍,setImmediate延遲可以在1ms以內俊马,而setTimeout有最低4ms的延遲,所以setImmediate比setTimeout(0)更早執(zhí)行回調函數(shù)肩杈。不過在Nodejs中柴我,兩者誰先執(zhí)行都有可能,原因是Nodejs的事件循環(huán)和瀏覽器的略有差異扩然。

1

2

3

4

5

6

7

8(functiontestSetImmediate(){

constlabel='setImmediate';

console.time(label);

setImmediate(()=>{

console.timeEnd(label);

});

})();

Edge輸出:setImmediate: 0.555 毫秒

很明顯艘儒,setImmediate設計來是為保證讓代碼在下一次事件循環(huán)執(zhí)行,以前setTimeout(0)這種不可靠的方式可以丟掉了夫偶。

其他常用異步模型

requestAnimationFrame

requestAnimationFrame并不是定時器界睁,但和setTimeout很相似,在沒有requestAnimationFrame的瀏覽器一般都是用setTimeout模擬索守。

requestAnimationFrame跟屏幕刷新同步晕窑,大多數(shù)屏幕的刷新頻率都是60Hz,對應的requestAnimationFrame大概每隔16.7ms觸發(fā)一次卵佛,如果屏幕刷新頻率更高杨赤,requestAnimationFrame也會更快觸發(fā)〗赝簦基于這點疾牲,在支持requestAnimationFrame的瀏覽器還使用setTimeout做動畫顯然是不明智的。

在不支持requestAnimationFrame的瀏覽器衙解,如果使用setTimeout/setInterval來做動畫阳柔,最佳延遲時間也是16.7ms。 如果太小蚓峦,很可能連續(xù)兩次或者多次修改dom才一次屏幕刷新舌剂,這樣就會丟幀济锄,動畫就會卡;如果太大霍转,顯而易見也會有卡頓的感覺荐绝。

有趣的是,第一次觸發(fā)requestAnimationFrame的時機在不同瀏覽器也存在差異避消,Edge中低滩,大概16.7ms之后觸發(fā),而Chrome則立即觸發(fā)岩喷,跟setImmediate差不多恕沫。按理說Edge的實現(xiàn)似乎更符合常理。

1

2

3

4

5

6

7

8(functiontestRequestAnimationFrame(){

constlabel='requestAnimationFrame';

console.time(label);

requestAnimationFrame(()=>{

console.timeEnd(label);

});

})();

Edge輸出:requestAnimationFrame: 16.66 毫秒

Chrome輸出:requestAnimationFrame: 0.698ms

但相鄰兩次requestAnimationFrame的時間間隔大概都是16.7ms纱意,這一點是一致的婶溯。當然也不是絕對的,如果頁面本身性能就比較低妇穴,相隔的時間可能會變大爬虱,這就意味著頁面達不到60fps。

Promise

Promise是很常用的一種異步模型腾它,如果我們想讓代碼在下一個事件循環(huán)執(zhí)行跑筝,可以選擇使用setTimeout(0)、setImmediate瞒滴、requestAnimationFrame(Chrome)和Promise曲梗。

而且Promise的延遲比setImmediate更低,意味著Promise比setImmediate先執(zhí)行妓忍。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21functiontestSetImmediate(){

constlabel='setImmediate';

console.time(label);

setImmediate(()=>{

console.timeEnd(label);

});

}

functiontestPromise(){

constlabel='Promise';

console.time(label);

newPromise((resolve,reject)=>{

resolve();

}).then(()=>{

console.timeEnd(label);

});

}

testSetImmediate();

testPromise();

Edge輸出:Promise: 0.33 毫秒 setImmediate: 1.66 毫秒

盡管setImmediate的回調函數(shù)比Promise先注冊虏两,但還是Promise先執(zhí)行。

可以肯定的是世剖,在各JS環(huán)境中定罢,Promise都是最先執(zhí)行的,setTimeout(0)旁瘫、setImmediate和requestAnimationFrame順序不確定祖凫。

process.nextTick

process.nextTick是Nodejs的API,比Promise更早執(zhí)行酬凳。

事實上惠况,process.nextTick是不會進入異步隊列的,而是直接在主線程隊列尾強插一個任務宁仔,雖然不會阻塞主線程稠屠,但是會阻塞異步任務的執(zhí)行,如果有嵌套的process.nextTick,那異步任務就永遠沒機會被執(zhí)行到了权埠。

使用的時候要格外小心榨了,除非你的代碼明確要在本次事件循環(huán)結束之前執(zhí)行,否則使用setImmediate或者Promise更保險攘蔽。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末阻逮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子秩彤,更是在濱河造成了極大的恐慌,老刑警劉巖事哭,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漫雷,死亡現(xiàn)場離奇詭異,居然都是意外死亡鳍咱,警方通過查閱死者的電腦和手機降盹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谤辜,“玉大人蓄坏,你說我怎么就攤上這事〕竽睿” “怎么了涡戳?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脯倚。 經(jīng)常有香客問我渔彰,道長,這世上最難降的妖魔是什么推正? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任恍涂,我火速辦了婚禮,結果婚禮上植榕,老公的妹妹穿的比我還像新娘再沧。我一直安慰自己,他們只是感情好尊残,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布炒瘸。 她就那樣靜靜地躺著,像睡著了一般夜郁。 火紅的嫁衣襯著肌膚如雪什燕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天竞端,我揣著相機與錄音屎即,去河邊找鬼。 笑死,一個胖子當著我的面吹牛技俐,可吹牛的內容都是我干的乘陪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼雕擂,長吁一口氣:“原來是場噩夢啊……” “哼啡邑!你這毒婦竟也來了?” 一聲冷哼從身側響起井赌,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤谤逼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后仇穗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體流部,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年纹坐,在試婚紗的時候發(fā)現(xiàn)自己被綠了枝冀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡耘子,死狀恐怖果漾,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情谷誓,我是刑警寧澤绒障,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站捍歪,受9級特大地震影響端盆,放射性物質發(fā)生泄漏。R本人自食惡果不足惜费封,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一焕妙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弓摘,春花似錦焚鹊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锤窑,卻和暖如春璧针,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渊啰。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工探橱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留申屹,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓隧膏,卻偏偏與公主長得像哗讥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子胞枕,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容

  • 1腐泻、 單線程决乎、任務隊列的概念 單線程: JavaScript是一個單線程語言,瀏覽器只會分配一個javascrip...
    海山城閱讀 1,037評論 0 1
  • 在談js定時器以前,我覺得有必要了解下javascript的事件運行機制派桩,簡稱(javascript event ...
    JohnsonChe閱讀 904評論 0 2
  • 前言:在引用開發(fā)中瑞驱,我們經(jīng)常需要在頁面中執(zhí)行一些周期性的操作,比如每隔一段時間就執(zhí)行某一固定的操作窄坦。而對于這樣的操...
    帥帥噠小白閱讀 5,323評論 1 3
  • 定時器主要由setTimeout()和setInterval()這兩個函數(shù)完成。 1.setTimeout()函數(shù)...
    betterwlf閱讀 590評論 0 0
  • 于是幾人一起逛逛未名湖 昔日四人游 而今三人行 生活不容易 且行且珍惜 踏一場突如其來的雪凳寺, 約三...
    姣童閱讀 205評論 0 2