自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

setTimeout的實(shí)現(xiàn)原理和使用注意

開(kāi)發(fā) 前端
setTimeout,它就是一個(gè)定時(shí)器,用來(lái)指定某個(gè)函數(shù)在多少毫秒之后執(zhí)行。

setTimeout,它就是一個(gè)定時(shí)器,用來(lái)指定某個(gè)函數(shù)在多少毫秒之后執(zhí)行。

setTimeout用法 

  1. var timeoutID = setTimeout(function[, delay, arg1, arg2, ...]);  
  2. var timeoutID = setTimeout(function[, delay]);  
  3. var timeoutID = setTimeout(code[, delay]); 
  •     第一個(gè)參數(shù)為函數(shù)或可執(zhí)行的字符串(比如alert('test'),此法不建議使用)
  •     第二個(gè)參數(shù)為延遲毫秒數(shù),可選的,默認(rèn)值為0.
  •     第三個(gè)及后面的參數(shù)為函數(shù)的入?yún)ⅰ?/li>
  •     setTimeout 的返回值是一個(gè)數(shù)字,這個(gè)值為timeoutID,可以用于取消該定時(shí)器。

setTimeout在瀏覽器中的實(shí)現(xiàn)

瀏覽器渲染進(jìn)程中所有運(yùn)行在主線程上的任務(wù)都需要先添加到消息隊(duì)列,然后事件循環(huán)系統(tǒng)再按照順序執(zhí)行消息隊(duì)列中的任務(wù)。 

在 Chrome 中除了正常使用的消息隊(duì)列之外,還有另外一個(gè)消息隊(duì)列(我們可以稱為延遲隊(duì)列),這個(gè)隊(duì)列中維護(hù)了需要延遲執(zhí)行的任務(wù)列表,包括了定時(shí)器和 Chromium 內(nèi)部一些需要延遲執(zhí)行的任務(wù)。所以當(dāng)通過(guò) JavaScript 創(chuàng)建一個(gè)定時(shí)器時(shí),渲染進(jìn)程會(huì)將該定時(shí)器的回調(diào)任務(wù)添加到延遲隊(duì)列中。

比如這樣的一段代碼: 

  1. function foo(){  
  2.   console.log("test")  
  3.  
  4. var timeoutID = setTimeout(foo,100); 

當(dāng)通過(guò) JavaScript 調(diào)用 setTimeout 設(shè)置回調(diào)函數(shù)的時(shí)候,渲染進(jìn)程將會(huì)創(chuàng)建一個(gè)回調(diào)任務(wù),包含了回調(diào)函數(shù)foo、當(dāng)前發(fā)起時(shí)間、延遲執(zhí)行時(shí)間等,其模擬代碼如下所示: 

  1. struct DelayTask{  
  2.   int64 id;  
  3.   CallBackFunction cbf;  
  4.   int start_time;  
  5.   int delay_time;  
  6. };  
  7. DelayTask timerTask;  
  8. timerTask.cbf = foo 
  9. timerTask.start_time = getCurrentTime(); //獲取當(dāng)前時(shí)間  
  10. timerTask.delay_time = 100;//設(shè)置延遲執(zhí)行時(shí)間 

創(chuàng)建好回調(diào)任務(wù)之后,就會(huì)將該任務(wù)添加到延遲執(zhí)行隊(duì)列中。那這個(gè)回調(diào)任務(wù),什么時(shí)候會(huì)被執(zhí)行呢?

瀏覽器中有個(gè)函數(shù)是專門(mén)用來(lái)處理延遲執(zhí)行任務(wù)的,暫且稱為ProcessDelayTask,它的主要邏輯如下: 

  1. void ProcessTimerTask(){  
  2.   //從delayed_incoming_queue中取出已經(jīng)到期的定時(shí)器任務(wù)  
  3.   //依次執(zhí)行這些任務(wù)  
  4.  
  5. TaskQueue task_queue;  
  6. void ProcessTask();  
  7. bool keep_running = true 
  8. void MainTherad(){  
  9.   for(;;){  
  10.     //執(zhí)行消息隊(duì)列中的任務(wù)  
  11.     Task task = task_queue.takeTask();  
  12.     ProcessTask(task);  
  13.     //執(zhí)行延遲隊(duì)列中的任務(wù)  
  14.     ProcessDelayTask()  
  15.     if(!keep_running) //如果設(shè)置了退出標(biāo)志,那么直接退出線程循環(huán)  
  16.         break;   
  17.   }  

其實(shí)就是,當(dāng)瀏覽器處理完消息隊(duì)列中的一個(gè)任務(wù)之后,就會(huì)開(kāi)始執(zhí)行 ProcessDelayTask 函數(shù)。ProcessDelayTask 函數(shù)會(huì)根據(jù)發(fā)起時(shí)間和延遲時(shí)間計(jì)算出到期的任務(wù),然后依次執(zhí)行這些到期的任務(wù)。等到期的任務(wù)執(zhí)行完成之后,再繼續(xù)下一個(gè)循環(huán)過(guò)程。這樣定時(shí)器就實(shí)現(xiàn)了,從這個(gè)過(guò)程也可以明顯看出,定時(shí)器并不一定是準(zhǔn)時(shí)延后執(zhí)行的。

注意事項(xiàng)

  1.  如果當(dāng)前任務(wù)執(zhí)行時(shí)間過(guò)久,會(huì)延遲到期定時(shí)器任務(wù)的執(zhí)行

在使用 setTimeout 的時(shí)候,有很多因素會(huì)導(dǎo)致回調(diào)函數(shù)執(zhí)行比設(shè)定的預(yù)期值要久,其中一個(gè)就是上文說(shuō)到的,如果處理的當(dāng)前任務(wù)耗時(shí)過(guò)長(zhǎng),定時(shí)器設(shè)置的任務(wù)就會(huì)被延后執(zhí)行。

比如在瀏覽器中執(zhí)行這樣一段代碼,并打印執(zhí)行時(shí)間: 

  1. function bar() {  
  2.     console.log('bar')  
  3.     const endTime = Date.now()  
  4.     console.log('cost time',endTime - startTime)  
  5.  
  6. function foo() {  
  7.     setTimeout(bar, 0);  
  8.     for (let i = 0; i < 5000; i++) {  
  9.         let i = 5+8+8+8  
  10.         console.log(i)  
  11.     }  
  12.  
  13. foo() 

執(zhí)行結(jié)果如圖:

從結(jié)果可以看到,執(zhí)行 foo 函數(shù)所消耗的時(shí)長(zhǎng)是 365 毫秒,這也就意味著通過(guò) setTimeout 設(shè)置的任務(wù)被推遲了 365 毫秒才執(zhí)行,而設(shè)置 setTimeout 的回調(diào)延遲時(shí)間是 0。

      2 .  使用 setTimeout 設(shè)置的回調(diào)函數(shù)中的 this 環(huán)境不是指向回調(diào)函數(shù)

比如這段代碼: 

  1. var name1 
  2. var MyObj = {  
  3.   name: 2,  
  4.   test:1,  
  5.   showName: function(){  
  6.     console.log(this.name,this.test);  
  7.   }  
  8.  
  9. setTimeout(MyObj.showName,1000)  
  10. MyObj.showName()  
  11. //先輸出 2 1  
  12. // 1s后輸出 1 undefined  

這里其實(shí)認(rèn)真分析一下,也很好理解這個(gè) this 的指向。按照 this 的規(guī)定,如果是對(duì)象調(diào)用(obj.fn()),那么this指向該對(duì)象,因此MyObj.showName()輸出的是 MyObj 里面的值。在 setTimeout 中,入?yún)⑹荕yObj.showName,這里是把這個(gè)值傳了進(jìn)去,可以理解為: 

  1. const fn = MyObj.showName  
  2. setTimeout(fn,1000) 

這樣看,在setTimeout里面,當(dāng)執(zhí)行到的時(shí)候,實(shí)際上就是在window下執(zhí)行fn,此時(shí)的this,就指向了window,而不是原來(lái)的函數(shù)。

      3 .  setTimeout 存在嵌套調(diào)用問(wèn)題

如果 setTimeout 存在嵌套調(diào)用,調(diào)用超過(guò)5次后,系統(tǒng)會(huì)設(shè)置最短執(zhí)行時(shí)間間隔為 4 毫秒。

我們可以在瀏覽器粗略測(cè)試一下,有如下代碼: 

  1. let startTime = Date.now()  
  2. function cb() {   
  3.   const endTime = Date.now()  
  4.   console.log('cost time',endTime - startTime)  
  5.   startTimestartTime = startTime  
  6.   setTimeout(cb, 0);   
  7.  
  8. setTimeout(cb, 0); 

執(zhí)行結(jié)果:

從結(jié)果可以看出,前面五次調(diào)用的時(shí)間間隔比較小,嵌套調(diào)用超過(guò)五次以上,后面每次的調(diào)用最小時(shí)間間隔是 4 毫秒(我運(yùn)行的結(jié)果,間隔基本是 5ms,考慮有代碼執(zhí)行的計(jì)算誤差)。

之所以出現(xiàn)這樣的情況,是因?yàn)樵?Chrome 中,定時(shí)器被嵌套調(diào)用 5 次以上,系統(tǒng)會(huì)判斷該函數(shù)方法被阻塞了,如果定時(shí)器的調(diào)用時(shí)間間隔小于 4 毫秒,那么瀏覽器會(huì)將每次調(diào)用的時(shí)間間隔設(shè)置為 4 毫秒??梢钥聪略创a(https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/frame/dom_timer.cc) 

  1. static const int kMaxTimerNestingLevel = 5 
  2. // Chromium uses a minimum timer interval of 4ms. We'd like to go  
  3. // lower; however, there are poorly coded websites out there which do  
  4. // create CPU-spinning loops.  Using 4ms prevents the CPU from  
  5. // spinning too busily and provides a balance between CPU spinning and  
  6. // the smallest possible interval timer.  
  7. static constexpr base::TimeDelta kMinimumInterval = base::TimeDelta::FromMilliseconds(4); 

所以,一些實(shí)時(shí)性較高的需求就不太適合使用 setTimeout 了,比如你用 setTimeout 來(lái)實(shí)現(xiàn) JavaScript 動(dòng)畫(huà)就不一定是一個(gè)很好的主意。

      4 .  未激活的頁(yè)面,setTimeout 執(zhí)行最小間隔是 1000 毫秒

如果標(biāo)簽不是當(dāng)前的激活標(biāo)簽,那么定時(shí)器最小的時(shí)間間隔是 1000 毫秒,目的是為了優(yōu)化后臺(tái)頁(yè)面的加載損耗以及降低耗電量。這一點(diǎn)你在使用定時(shí)器的時(shí)候要注意。

      5 .   延時(shí)執(zhí)行時(shí)間有最大值

Chrome、Safari、Firefox 都是以 32 個(gè) bit 來(lái)存儲(chǔ)延時(shí)值的,32bit 最大只能存放的數(shù)字是 2147483647 毫秒,這就意味著,如果 setTimeout 設(shè)置的延遲值大于 2147483647 毫秒(大約 24.8 天)時(shí)就會(huì)溢出,這導(dǎo)致定時(shí)器會(huì)被立即執(zhí)行。如: 

  1. let startTime = Date.now()  
  2. function foo(){  
  3.   const endTime = Date.now()  
  4.   console.log('cost time',endTime - startTime)  
  5.   console.log("test")  
  6.  
  7. var timerID = setTimeout(foo,2147483648);//會(huì)被立即調(diào)用執(zhí)行 

執(zhí)行結(jié)果:

運(yùn)行后可以看到,這段代碼是立即被執(zhí)行的。但如果將延時(shí)值修改為小于 2147483647 毫秒的某個(gè)值,那么執(zhí)行時(shí)就沒(méi)有問(wèn)題了。 

 

責(zé)任編輯:龐桂玉 來(lái)源: 前端大全
相關(guān)推薦

2012-01-12 12:08:02

Java

2022-10-24 00:48:58

Go語(yǔ)言errgroup

2024-02-01 09:39:02

asyncawaitPromise

2022-06-21 14:02:29

MongoDB數(shù)據(jù)庫(kù)存儲(chǔ)

2022-05-06 09:22:25

Go泛型

2022-03-17 08:55:43

本地線程變量共享全局變量

2023-03-29 10:19:44

異步編程AsyncPromise

2020-11-13 07:11:23

MySQL復(fù)制日志

2022-07-05 09:44:25

服務(wù)治理熔斷限流

2021-02-19 08:20:42

JWT網(wǎng)絡(luò)原理

2014-12-23 13:50:46

多播組播

2024-10-16 08:36:03

2021-04-21 09:28:17

字節(jié)面試官SetTimeout

2020-09-28 15:00:19

Linux容器虛擬化

2024-06-27 08:26:10

LooperAndroid內(nèi)存

2021-06-30 10:32:33

反射多態(tài)Java

2024-01-19 12:48:00

Redis存儲(chǔ)數(shù)據(jù)庫(kù)

2020-03-27 22:18:55

JavaScript編程語(yǔ)言代碼

2011-05-31 14:33:53

settimeout

2024-11-27 08:15:50

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)