新聞中心
setTimeout,它就是一個定時器,用來指定某個函數(shù)在多少毫秒之后執(zhí)行。

專注于為中小企業(yè)提供網(wǎng)站設(shè)計制作、成都網(wǎng)站制作服務(wù),電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)霞浦免費做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了上千企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。
setTimeout用法
- var timeoutID = setTimeout(function[, delay, arg1, arg2, ...]);
- var timeoutID = setTimeout(function[, delay]);
- var timeoutID = setTimeout(code[, delay]);
- 第一個參數(shù)為函數(shù)或可執(zhí)行的字符串(比如alert('test'),此法不建議使用)
- 第二個參數(shù)為延遲毫秒數(shù),可選的,默認值為0.
- 第三個及后面的參數(shù)為函數(shù)的入?yún)ⅰ?/li>
- setTimeout 的返回值是一個數(shù)字,這個值為timeoutID,可以用于取消該定時器。
setTimeout在瀏覽器中的實現(xiàn)
瀏覽器渲染進程中所有運行在主線程上的任務(wù)都需要先添加到消息隊列,然后事件循環(huán)系統(tǒng)再按照順序執(zhí)行消息隊列中的任務(wù)。
在 Chrome 中除了正常使用的消息隊列之外,還有另外一個消息隊列(我們可以稱為延遲隊列),這個隊列中維護了需要延遲執(zhí)行的任務(wù)列表,包括了定時器和 Chromium 內(nèi)部一些需要延遲執(zhí)行的任務(wù)。所以當(dāng)通過 JavaScript 創(chuàng)建一個定時器時,渲染進程會將該定時器的回調(diào)任務(wù)添加到延遲隊列中。
比如這樣的一段代碼:
- function foo(){
- console.log("test")
- }
- var timeoutID = setTimeout(foo,100);
當(dāng)通過 JavaScript 調(diào)用 setTimeout 設(shè)置回調(diào)函數(shù)的時候,渲染進程將會創(chuàng)建一個回調(diào)任務(wù),包含了回調(diào)函數(shù)foo、當(dāng)前發(fā)起時間、延遲執(zhí)行時間等,其模擬代碼如下所示:
- struct DelayTask{
- int64 id;
- CallBackFunction cbf;
- int start_time;
- int delay_time;
- };
- DelayTask timerTask;
- timerTask.cbf = foo;
- timerTask.start_time = getCurrentTime(); //獲取當(dāng)前時間
- timerTask.delay_time = 100;//設(shè)置延遲執(zhí)行時間
創(chuàng)建好回調(diào)任務(wù)之后,就會將該任務(wù)添加到延遲執(zhí)行隊列中。那這個回調(diào)任務(wù),什么時候會被執(zhí)行呢?
瀏覽器中有個函數(shù)是專門用來處理延遲執(zhí)行任務(wù)的,暫且稱為ProcessDelayTask,它的主要邏輯如下:
- void ProcessTimerTask(){
- //從delayed_incoming_queue中取出已經(jīng)到期的定時器任務(wù)
- //依次執(zhí)行這些任務(wù)
- }
- TaskQueue task_queue;
- void ProcessTask();
- bool keep_running = true;
- void MainTherad(){
- for(;;){
- //執(zhí)行消息隊列中的任務(wù)
- Task task = task_queue.takeTask();
- ProcessTask(task);
- //執(zhí)行延遲隊列中的任務(wù)
- ProcessDelayTask()
- if(!keep_running) //如果設(shè)置了退出標(biāo)志,那么直接退出線程循環(huán)
- break;
- }
- }
其實就是,當(dāng)瀏覽器處理完消息隊列中的一個任務(wù)之后,就會開始執(zhí)行 ProcessDelayTask 函數(shù)。ProcessDelayTask 函數(shù)會根據(jù)發(fā)起時間和延遲時間計算出到期的任務(wù),然后依次執(zhí)行這些到期的任務(wù)。等到期的任務(wù)執(zhí)行完成之后,再繼續(xù)下一個循環(huán)過程。這樣定時器就實現(xiàn)了,從這個過程也可以明顯看出,定時器并不一定是準時延后執(zhí)行的。
注意事項
- 如果當(dāng)前任務(wù)執(zhí)行時間過久,會延遲到期定時器任務(wù)的執(zhí)行
在使用 setTimeout 的時候,有很多因素會導(dǎo)致回調(diào)函數(shù)執(zhí)行比設(shè)定的預(yù)期值要久,其中一個就是上文說到的,如果處理的當(dāng)前任務(wù)耗時過長,定時器設(shè)置的任務(wù)就會被延后執(zhí)行。
比如在瀏覽器中執(zhí)行這樣一段代碼,并打印執(zhí)行時間:
- function bar() {
- console.log('bar')
- const endTime = Date.now()
- console.log('cost time',endTime - startTime)
- }
- function foo() {
- setTimeout(bar, 0);
- for (let i = 0; i < 5000; i++) {
- let i = 5+8+8+8
- console.log(i)
- }
- }
- foo()
執(zhí)行結(jié)果如圖:
從結(jié)果可以看到,執(zhí)行 foo 函數(shù)所消耗的時長是 365 毫秒,這也就意味著通過 setTimeout 設(shè)置的任務(wù)被推遲了 365 毫秒才執(zhí)行,而設(shè)置 setTimeout 的回調(diào)延遲時間是 0。
2 . 使用 setTimeout 設(shè)置的回調(diào)函數(shù)中的 this 環(huán)境不是指向回調(diào)函數(shù)
比如這段代碼:
- var name= 1;
- var MyObj = {
- name: 2,
- test:1,
- showName: function(){
- console.log(this.name,this.test);
- }
- }
- setTimeout(MyObj.showName,1000)
- MyObj.showName()
- //先輸出 2 1
- // 1s后輸出 1 undefined
這里其實認真分析一下,也很好理解這個 this 的指向。按照 this 的規(guī)定,如果是對象調(diào)用(obj.fn()),那么this指向該對象,因此MyObj.showName()輸出的是 MyObj 里面的值。在 setTimeout 中,入?yún)⑹荕yObj.showName,這里是把這個值傳了進去,可以理解為:
- const fn = MyObj.showName
- setTimeout(fn,1000)
這樣看,在setTimeout里面,當(dāng)執(zhí)行到的時候,實際上就是在window下執(zhí)行fn,此時的this,就指向了window,而不是原來的函數(shù)。
3 . setTimeout 存在嵌套調(diào)用問題
如果 setTimeout 存在嵌套調(diào)用,調(diào)用超過5次后,系統(tǒng)會設(shè)置最短執(zhí)行時間間隔為 4 毫秒。
我們可以在瀏覽器粗略測試一下,有如下代碼:
- let startTime = Date.now()
- function cb() {
- const endTime = Date.now()
- console.log('cost time',endTime - startTime)
- startTimestartTime = startTime
- setTimeout(cb, 0);
- }
- setTimeout(cb, 0);
執(zhí)行結(jié)果:
從結(jié)果可以看出,前面五次調(diào)用的時間間隔比較小,嵌套調(diào)用超過五次以上,后面每次的調(diào)用最小時間間隔是 4 毫秒(我運行的結(jié)果,間隔基本是 5ms,考慮有代碼執(zhí)行的計算誤差)。
之所以出現(xiàn)這樣的情況,是因為在 Chrome 中,定時器被嵌套調(diào)用 5 次以上,系統(tǒng)會判斷該函數(shù)方法被阻塞了,如果定時器的調(diào)用時間間隔小于 4 毫秒,那么瀏覽器會將每次調(diào)用的時間間隔設(shè)置為 4 毫秒。可以看下源碼(https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/frame/dom_timer.cc)
- static const int kMaxTimerNestingLevel = 5;
- // Chromium uses a minimum timer interval of 4ms. We'd like to go
- // lower; however, there are poorly coded websites out there which do
- // create CPU-spinning loops. Using 4ms prevents the CPU from
- // spinning too busily and provides a balance between CPU spinning and
- // the smallest possible interval timer.
- static constexpr base::TimeDelta kMinimumInterval = base::TimeDelta::FromMilliseconds(4);
所以,一些實時性較高的需求就不太適合使用 setTimeout 了,比如你用 setTimeout 來實現(xiàn) JavaScript 動畫就不一定是一個很好的主意。
4 . 未激活的頁面,setTimeout 執(zhí)行最小間隔是 1000 毫秒
如果標(biāo)簽不是當(dāng)前的激活標(biāo)簽,那么定時器最小的時間間隔是 1000 毫秒,目的是為了優(yōu)化后臺頁面的加載損耗以及降低耗電量。這一點你在使用定時器的時候要注意。
5 . 延時執(zhí)行時間有最大值
Chrome、Safari、Firefox 都是以 32 個 bit 來存儲延時值的,32bit 最大只能存放的數(shù)字是 2147483647 毫秒,這就意味著,如果 setTimeout 設(shè)置的延遲值大于 2147483647 毫秒(大約 24.8 天)時就會溢出,這導(dǎo)致定時器會被立即執(zhí)行。如:
- let startTime = Date.now()
- function foo(){
- const endTime = Date.now()
- console.log('cost time',endTime - startTime)
- console.log("test")
- }
- var timerID = setTimeout(foo,2147483648);//會被立即調(diào)用執(zhí)行
執(zhí)行結(jié)果:
運行后可以看到,這段代碼是立即被執(zhí)行的。但如果將延時值修改為小于 2147483647 毫秒的某個值,那么執(zhí)行時就沒有問題了。
網(wǎng)站標(biāo)題:setTimeout的實現(xiàn)原理和使用注意
文章分享:http://www.5511xx.com/article/cdosggg.html


咨詢
建站咨詢
