setTimeout,它就是一個定時器,用來指定某個函數在多少毫秒以後執行。前端
setTimeout用法web
var timeoutID = setTimeout(function[, delay, arg1, arg2, ...]); var timeoutID = setTimeout(function[, delay]); var timeoutID = setTimeout(code[, delay]);
瀏覽器渲染進程中全部運行在主線程上的任務都須要先添加到消息隊列,而後事件循環系統再按照順序執行消息隊列中的任務。瀏覽器
在 Chrome 中除了正常使用的消息隊列以外,還有另一個消息隊列(咱們能夠稱爲延遲隊列),這個隊列中維護了須要延遲執行的任務列表,包括了定時器和 Chromium 內部一些須要延遲執行的任務。因此當經過 JavaScript 建立一個定時器時,渲染進程會將該定時器的回調任務添加到延遲隊列中。微信
好比這樣的一段代碼:markdown
function foo(){ console.log("test") } var timeoutID = setTimeout(foo,100);
當經過 JavaScript 調用 setTimeout 設置回調函數的時候,渲染進程將會建立一個回調任務,包含了回調函數foo、當前發起時間、延遲執行時間等,其模擬代碼以下所示:dom
struct DelayTask{ int64 id; CallBackFunction cbf; int start_time; int delay_time; }; DelayTask timerTask; timerTask.cbf = foo; timerTask.start_time = getCurrentTime(); //獲取當前時間 timerTask.delay_time = 100;//設置延遲執行時間
建立好回調任務以後,就會將該任務添加到延遲執行隊列中。那這個回調任務,何時會被執行呢?
瀏覽器中有個函數是專門用來處理延遲執行任務的,暫且稱爲ProcessDelayTask,它的主要邏輯以下:ide
void ProcessTimerTask(){ //從delayed_incoming_queue中取出已經到期的定時器任務 //依次執行這些任務 } TaskQueue task_queue; void ProcessTask(); bool keep_running = true; void MainTherad(){ for(;;){ //執行消息隊列中的任務 Task task = task_queue.takeTask(); ProcessTask(task); //執行延遲隊列中的任務 ProcessDelayTask() if(!keep_running) //若是設置了退出標誌,那麼直接退出線程循環 break; } }
其實就是,當瀏覽器處理完消息隊列中的一個任務以後,就會開始執行 ProcessDelayTask 函數。ProcessDelayTask 函數會根據發起時間和延遲時間計算出到期的任務,而後依次執行這些到期的任務。等到期的任務執行完成以後,再繼續下一個循環過程。這樣定時器就實現了,從這個過程也能夠明顯看出,定時器並不必定是準時延後執行的。函數
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()
執行結果如圖:oop
從結果能夠看到,執行 foo 函數所消耗的時長是 365 毫秒,這也就意味着經過 setTimeout 設置的任務被推遲了 365 毫秒才執行,而設置 setTimeout 的回調延遲時間是 0。學習
好比這段代碼: 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 的規定,若是是對象調用(obj.fn()),那麼this指向該對象,所以MyObj.showName()輸出的是 MyObj 裏面的值。在 setTimeout 中,入參是MyObj.showName,這裏是把這個值傳了進去,能夠理解爲:
const fn = MyObj.showName setTimeout(fn,1000)
這樣看,在setTimeout裏面,當執行到的時候,實際上就是在window下執行fn,此時的this,就指向了window,而不是原來的函數。
let startTime = Date.now() function cb() { const endTime = Date.now() console.log('cost time',endTime - startTime) startTime = startTime setTimeout(cb, 0); } setTimeout(cb, 0);
執行結果:
從結果能夠看出,前面五次調用的時間間隔比較小,嵌套調用超過五次以上,後面每次的調用最小時間間隔是 4 毫秒(我運行的結果,間隔基本是 5ms,考慮有代碼執行的計算偏差)。
之因此出現這樣的狀況,是由於在 Chrome 中,定時器被嵌套調用 5 次以上,系統會判斷該函數方法被阻塞了,若是定時器的調用時間間隔小於 4 毫秒,那麼瀏覽器會將每次調用的時間間隔設置爲 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 來實現 JavaScript 動畫就不必定是一個很好的主意。
未激活的頁面,setTimeout 執行最小間隔是 1000 毫秒
若是標籤不是當前的激活標籤,那麼定時器最小的時間間隔是 1000 毫秒,目的是爲了優化後臺頁面的加載損耗以及下降耗電量。這一點你在使用定時器的時候要注意。
let startTime = Date.now() function foo(){ const endTime = Date.now() console.log('cost time',endTime - startTime) console.log("test") } var timerID = setTimeout(foo,2147483648);//會被當即調用執行
執行結果:
運行後能夠看到,這段代碼是當即被執行的。但若是將延時值修改成小於 2147483647 毫秒的某個值,那麼執行時就沒有問題了。