一、什么是定時器
JS提供了一些原生方法來實現延時去執(zhí)行某一段代碼狂魔,下面來簡單介紹一下
setTimeout
: 設置一個定時器榛搔,在定時器到期后執(zhí)行一次函數或代碼段
var timeoutId = window.setTimeout(func[, delay, param1, param2, ...]);
var timeoutId = window.setTimeout(code[, delay]);
- timeoutId: 定時器ID
- func: 延遲后執(zhí)行的函數
- code: 延遲后執(zhí)行的代碼字符串,不推薦使用原理類似
eval()
- delay: 延遲的時間(單位:毫秒)变屁,默認值為0
- param1,param2: 向延遲函數傳遞而外的參數,IE9以上支持
setInterval
: 以固定的時間間隔重復調用一個函數或者代碼段
var intervalId = window.setInterval(func, delay[, param1, param2, ...]);
var intervalId = window.setInterval(code, delay);
- intervalId: 重復操作的ID
- func: 延遲調用的函數
- code: 代碼段
- delay: 延遲時間意狠,沒有默認值
setImmediate
: 在瀏覽器完全結束當前運行的操作之后立即執(zhí)行指定的函數(僅IE10和Node 0.10+中有實現)粟关,類似setTimeout(func, 0)
var immediateId = setImmediate(func[, param1, param2, ...]);
var immediateId = setImmediate(func);
- immediateId: 定時器ID
- func: 回調
requestAnimationFrame
: 專門為實現高性能的幀動畫而設計的API,但是不能指定延遲時間环戈,而是根據瀏覽器的刷新頻率而定(幀)
var requestId = window.requestAnimationFrame(func);
- func: 回調
上面簡單的介紹了四種JS的定時器闷板,而本文將會主要介紹比較常用的兩種:setTimeout
和setInterval
澎灸。
二、舉個栗子
- 基本用法
// 下面代碼執(zhí)行之后會輸出什么?
var intervalId, timeoutId;
timeoutId = setTimeout(function () {
console.log(1);
}, 300);
setTimeout(function () {
clearTimeout(timeoutId);
console.log(2);
}, 100);
setTimeout('console.log("5")', 400);
intervalId = setInterval(function () {
console.log(4);
clearInterval(intervalId);
}, 200);
// 分別輸出: 2、4火焰、5
-
setInterval
和setTimeout
的區(qū)別甲棍?
// 執(zhí)行在面的代碼塊會輸出什么?
setTimeout(function () {
console.log('timeout');
}, 1000);
setInterval(function () {
console.log('interval')
}, 1000);
// 輸出一次 timeout涯曲,每隔1S輸出一次 interval
/*--------------------------------*/
// 通過setTimeout模擬setInterval 和 setInterval有啥區(qū)別么?
var callback = function () {
if (times++ > max) {
clearTimeout(timeoutId);
clearInterval(intervalId);
}
console.log('start', Date.now() - start);
for (var i = 0; i < 990000000; i++) {}
console.log('end', Date.now() - start);
},
delay = 100,
times = 0,
max = 5,
start = Date.now(),
intervalId, timeoutId;
function imitateInterval(fn, delay) {
timeoutId = setTimeout(function () {
fn();
if (times <= max) {
imitateInterval(fn ,delay);
}
}, delay);
}
imitateInterval(callback, delay);
intervalId = setInterval(callback, delay);
如果是setTimeout
和setInterval
的話,它倆僅僅在執(zhí)行次數上有區(qū)別括蝠,setTimeout
一次、setInterval
n次饭聚。
而通過setTimeout
模擬的setInterval
與setInterval
的區(qū)別則在于:setTimeout
只有在回調完成之后才會去調用下一次定時器忌警,而setInterval
則不管回調函數的執(zhí)行情況,當到達規(guī)定時間就會在事件隊列中插入一個執(zhí)行回調的事件秒梳,所以在選擇定時器的方式時需要考慮setInterval
的這種特性是否會對你的業(yè)務代碼有什么影響法绵?
-
setTimeout(func, 0)
和setImmediate(func)
誰更快?(僅僅是好奇酪碘,才寫的這段測試)
console.time('immediate');
console.time('timeout');
setImmediate(() => {
console.timeEnd('immediate');
});
setTimeout(() => {
console.timeEnd('timeout');
}, 0);
在Node.JS v6.7.0
中測試發(fā)現setTimeout
更早執(zhí)行
- 面試題
下面代碼運行后的結果是什么朋譬?
// 題目一
var t = true;
setTimeout(function(){
t = false;
}, 1000);
while(t){}
alert('end');
/*--------------------------------*/
// 題目二
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
/*--------------------------------*/
// 題目三
var obj = {
msg: 'obj',
shout: function () {
alert(this.msg);
},
waitAndShout: function() {
setTimeout(function () {
this.shout();
}, 0);
}
};
obj.waitAndShout();
問題答案會在后面解答
三、JS定時器的工作原理
在解釋上面問題的答案之前我們先來了解一下定時器的工作原理兴垦,這里將用引用How JavaScript Timers Work中的例子來解釋定時器的工作原理徙赢,該圖為一個簡單版的原理圖。
上圖中探越,左側數字代表時間狡赐,單位毫秒;左側文字代表某一個操作完成后钦幔,瀏覽器去詢問當前隊列中存在哪些正在等待執(zhí)行的操作枕屉;藍色方塊表示正在執(zhí)行的代碼塊;右側文字代表在代碼運行過程中鲤氢,出現哪些異步事件搀擂。該圖大致流程如下:
- 程序開始時,有一個JS代碼塊開始執(zhí)行卷玉,執(zhí)行時長約為18ms哨颂,在執(zhí)行過程中有3個異步事件觸發(fā),其中包括一個
setTimeout
揍庄、鼠標點擊事件咆蒿、setInterval
- 第一個
setTimeout
先運行,延遲時間為10ms,稍后鼠標事件出現沃测,瀏覽器在事件隊列中插入點擊的回調函數缭黔,稍后setInterval
運行,10ms到達之后蒂破,setTimeout
向事件隊列中插入setTimeout
的回調 - 當第一個代碼塊執(zhí)行完成后馏谨,瀏覽器查看隊列中有哪些事件在等待,他取出排在隊列最前面的代碼來執(zhí)行
- 在瀏覽器處理鼠標點擊回調時附迷,
setInterval
再次檢查到到達延遲時間惧互,他將再次向事件隊列中插入一個interval的回調,以后每隔指定的延遲時間之后都會向隊列中插入一個回調 - 后面瀏覽器將在執(zhí)行完當前隊頭的代碼之后喇伯,將再次取出目前隊頭的事件來執(zhí)行
這里只是對定時器的原理做一個簡單版的描述喊儡,實際的處理過程比這個復雜。
四稻据、題目答案
好啦艾猜,我們現在再來看看上面的面試題的答案。
第一題
alert
永遠都不會執(zhí)行捻悯,因為JS是單線程的匆赃,且定時器的回調將在等待當前正在執(zhí)行的任務完成后才執(zhí)行,而while(t) {}
直接就進入了死循環(huán)一直占用線程今缚,不給回調函數執(zhí)行機會
第二題
代碼會輸出
5 5 5 5 5
算柳,理由同上,當i = 0
時姓言,生成一個定時器瞬项,將回調插入到事件隊列中,等待當前隊列中無任務執(zhí)行時立即執(zhí)行事期,而此時for
循環(huán)正在執(zhí)行滥壕,所以回調被擱置纸颜。當for循環(huán)執(zhí)行完成后兽泣,隊列中存在著5個回調函數,他們的都將執(zhí)行console.log(i)
的操作胁孙,因為當前JS代碼上中并沒有使用塊級作用域唠倦,所以i的值在for
循環(huán)結束后一直為5,所以代碼將輸出5個5
第三題
這個問題涉及到
this
的指向問題涮较,由setTimeout()調用的代碼運行在與所在函數完全分離的執(zhí)行環(huán)境上. 這會導致這些代碼中包含的this
關鍵字會指向window
(或全局)對象稠鼻,window
對象中并不存在shout
方法,所以就會報錯狂票,修改方案如下:
var obj = {
msg: 'obj',
shout: function () {
alert(this.msg);
},
waitAndShout: function() {
var self = this; // 這里將this賦給一個變量
setTimeout(function () {
self.shout();
}, 0);
}
};
obj.waitAndShout();
五候齿、需要注意的點
-
setTimeout
有最小時間間隔限制,HTML5標準為4ms,小于4ms按照4ms處理慌盯,但是每個瀏覽器實現的最小間隔都不同 - 因為JS引擎只有一個線程周霉,所以它將會強制異步事件排隊執(zhí)行
- 如果
setInterval
的回調執(zhí)行時間長于指定的延遲,setInterval
將無間隔的一個接一個執(zhí)行 -
this
的指向問題可以通過bind
函數亚皂、定義變量俱箱、箭頭函數的方式來解決
六、參考
博客地址: ssh.today灭必,歡迎關注