setTimeout,它就是一個定時器,用來指定某個函數在多少毫秒以後執行。前端
var timeoutID1 = setTimeout(function[, delay, arg1, arg2, ...]);
var timeoutID2 = setTimeout(function[, delay]);
var timeoutID3 = setTimeout(code[, delay]);
複製代碼
alert('test')
,此法不建議使用)爲延遲毫秒數
,可選的,默認值爲0.瀏覽器渲染進程中全部運行在主線程上的任務都須要先添加到消息隊列,而後事件循環系統再按照順序執行消息隊列中的任務。web
在 Chrome 中除了正常使用的消息隊列以外,還有另一個消息隊列(咱們能夠稱爲延遲隊列
),這個隊列中維護了須要延遲執行的任務列表,包括了定時器和 Chromium 內部一些須要延遲執行的任務。因此當經過 JavaScript 建立一個定時器時,渲染進程會將該定時器的回調任務添加到延遲隊列中。瀏覽器
好比這樣的一段代碼:微信
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 函數會根據發起時間和延遲時間計算出到期的任務,而後依次執行這些到期的任務。等到期的任務執行完成以後,再繼續下一個循環過程。這樣定時器就實現了,從這個過程也能夠明顯看出,定時器並不必定是準時延後執行的
。函數
在使用 setTimeout 的時候,有不少因素會致使回調函數執行比設定的預期值要久,其中一個就是上文說到的,若是處理的當前任務耗時過長,定時器設置的任務就會被延後執行。 好比在瀏覽器中執行這樣一段代碼,並打印執行時間:oop
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()
複製代碼
執行結果如圖: 學習
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,而不是原來的函數。
若是 setTimeout 存在嵌套調用,調用超過5次後,系統會設置最短執行時間間隔爲 4 毫秒。 咱們能夠在瀏覽器粗略測試一下,有以下代碼:
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 動畫就不必定是一個很好的主意。
若是標籤不是當前的激活標籤,那麼定時器最小的時間間隔是 1000 毫秒,目的是爲了優化後臺頁面的加載損耗以及下降耗電量。這一點你在使用定時器的時候要注意。
Chrome、Safari、Firefox 都是以 32 個 bit 來存儲延時值的,32bit 最大隻能存放的數字是 2147483647 毫秒,這就意味着,若是 setTimeout 設置的延遲值大於 2147483647 毫秒(大約 24.8 天)時就會溢出,這致使定時器會被當即執行。如:
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 毫秒的某個值,那麼執行時就沒有問題了。