js中的兩種定時器區(qū)別是什么以及怎么清除定時器詳解
一、開篇:定時器 ——JavaScript 實(shí)現(xiàn)異步延遲的核心工具
在 JavaScript 中,作為一門單線程語言,實(shí)現(xiàn)異步操作和延遲執(zhí)行邏輯的核心工具之一就是定時器(Timer)。無論是實(shí)現(xiàn)頁面倒計時、輪播圖自動切換、接口定時輪詢,還是避免同步代碼阻塞頁面渲染,都離不開定時器的加持。
JavaScript 提供了兩種核心定時器方法:setTimeout() 和 setInterval(),很多初學(xué)者在使用時容易混淆二者的執(zhí)行邏輯,出現(xiàn) “定時器無法停止”“執(zhí)行時機(jī)不符合預(yù)期” 等問題 —— 比如用setInterval()實(shí)現(xiàn)倒計時出現(xiàn)時間跳變、清除定時器后仍有殘留執(zhí)行邏輯。
本文將從兩種定時器的核心定義、執(zhí)行機(jī)制、核心區(qū)別,到定時器的清除方法、實(shí)戰(zhàn)避坑指南,結(jié)合完整示例深度拆解,幫你徹底掌握 JS 定時器的使用精髓,寫出健壯、可控的異步延遲代碼。
二、第一部分:認(rèn)識 JS 的兩種核心定時器
1. 一次性定時器:setTimeout ()
(1)核心定義
setTimeout() 是用于 ** 在指定的延遲時間后,一次性執(zhí)行某段代碼(回調(diào)函數(shù))** 的定時器,執(zhí)行完畢后定時器自動失效,不會重復(fù)觸發(fā),因此也被稱為 “一次性定時器” 或 “延遲定時器”。
(2)語法格式
// 基礎(chǔ)語法:返回定時器ID(用于后續(xù)清除定時器) const timeoutId = setTimeout(callback, delay, [param1, param2, ...]);
- 關(guān)鍵參數(shù)說明:
callback:必填,延遲后要執(zhí)行的回調(diào)函數(shù)(可以是普通函數(shù)、箭頭函數(shù),也可以是字符串形式的代碼片段,不推薦后者,存在安全風(fēng)險且性能較差);delay:可選,延遲執(zhí)行的時間,單位為毫秒(ms),默認(rèn)值為0(注意:不是立即執(zhí)行,后續(xù)會詳解);param1, param2, ...:可選,傳遞給回調(diào)函數(shù)的參數(shù),在 ES5 及以上環(huán)境中支持;返回值
timeoutId:非負(fù)整數(shù)(定時器唯一標(biāo)識 ID),每個定時器對應(yīng)一個唯一 ID,用于后續(xù)清除該定時器。
(3)實(shí)戰(zhàn)示例
// 示例1:基礎(chǔ)使用——3秒后一次性執(zhí)行回調(diào)
const timeoutId1 = setTimeout(() => {
console.log("3秒后執(zhí)行,僅執(zhí)行一次");
}, 3000);
console.log("定時器已創(chuàng)建,ID為:", timeoutId1); // 輸出:定時器已創(chuàng)建,ID為:1(不同環(huán)境ID可能不同)
// 示例2:傳遞參數(shù)給回調(diào)函數(shù)
const timeoutId2 = setTimeout((name, age) => {
console.log(`你好,我是${name},今年${age}歲`);
}, 2000, "張三", 25); // 2秒后輸出:你好,我是張三,今年25歲
// 示例3:延遲時間為0(非立即執(zhí)行,進(jìn)入任務(wù)隊(duì)列等待執(zhí)行)
setTimeout(() => {
console.log("延遲0毫秒,最后執(zhí)行");
}, 0);
console.log("同步代碼,先執(zhí)行");
// 輸出順序:同步代碼,先執(zhí)行 → 延遲0毫秒,最后執(zhí)行
(4)核心執(zhí)行機(jī)制
setTimeout() 的延遲時間delay并非 “絕對準(zhǔn)確”,也不是 “到點(diǎn)立即執(zhí)行”,核心原因是 JavaScript 的單線程特性:
- 當(dāng)調(diào)用
setTimeout()時,JS 引擎會將回調(diào)函數(shù)放入宏任務(wù)隊(duì)列,并記錄延遲時間; - 主線程優(yōu)先執(zhí)行完當(dāng)前所有同步代碼,再去處理宏任務(wù)隊(duì)列;
- 只有當(dāng)主線程空閑,且延遲時間已到,回調(diào)函數(shù)才會被執(zhí)行;
- 若主線程被其他耗時同步代碼阻塞,回調(diào)函數(shù)的執(zhí)行時間會大于
delay設(shè)定的時間。
2. 周期性定時器:setInterval ()
(1)核心定義
setInterval() 是用于 ** 按照指定的時間間隔,周期性、重復(fù)執(zhí)行某段代碼(回調(diào)函數(shù))** 的定時器,除非手動清除,否則會一直持續(xù)執(zhí)行,因此也被稱為 “周期性定時器” 或 “重復(fù)定時器”。
(2)語法格式
// 基礎(chǔ)語法:返回定時器ID(用于后續(xù)清除定時器) const intervalId = setInterval(callback, delay, [param1, param2, ...]);
- 關(guān)鍵參數(shù)說明(與
setTimeout()一致,核心差異在執(zhí)行邏輯):callback:必填,每次間隔時間到后要執(zhí)行的回調(diào)函數(shù);delay:可選,兩次回調(diào)執(zhí)行之間的時間間隔,單位為毫秒(ms),默認(rèn)值為0;param1, param2, ...:可選,傳遞給回調(diào)函數(shù)的參數(shù);返回值
intervalId:非負(fù)整數(shù)(定時器唯一標(biāo)識 ID),用于后續(xù)清除該周期性定時器。
(3)實(shí)戰(zhàn)示例
// 示例1:基礎(chǔ)使用——每隔2秒重復(fù)執(zhí)行回調(diào)
let count = 0;
const intervalId1 = setInterval(() => {
count++;
console.log(`已執(zhí)行${count}次,每隔2秒執(zhí)行一次`);
}, 2000);
// 示例2:傳遞參數(shù)給回調(diào)函數(shù),實(shí)現(xiàn)倒計時
let remainingTime = 10;
const intervalId2 = setInterval((title) => {
remainingTime--;
console.log(`${title}:剩余${remainingTime}秒`);
if (remainingTime <= 0) {
console.log("倒計時結(jié)束");
// 后續(xù)會詳解:倒計時結(jié)束后清除定時器
clearInterval(intervalId2);
}
}, 1000, "秒殺活動"); // 每隔1秒輸出一次倒計時
(4)核心執(zhí)行機(jī)制
setInterval() 的執(zhí)行機(jī)制與setTimeout() 類似,同樣受 JS 單線程和宏任務(wù)隊(duì)列影響,但存在一個關(guān)鍵差異:
- 首次調(diào)用
setInterval()時,JS 引擎會將回調(diào)函數(shù)放入宏任務(wù)隊(duì)列,等待延遲時間delay后執(zhí)行; - 回調(diào)函數(shù)執(zhí)行完畢后,
setInterval()會再次將下一次的回調(diào)函數(shù)放入宏任務(wù)隊(duì)列,保持delay時間間隔的周期性; - 若某一次回調(diào)函數(shù)執(zhí)行耗時超過
delay時間間隔,后續(xù)回調(diào)會出現(xiàn) “堆積”,導(dǎo)致執(zhí)行間隔小于delay(這是setInterval()的常見坑); - 除非手動清除,否則該過程會一直循環(huán),直到頁面卸載或定時器被清除。
三、第二部分:兩種定時器的核心區(qū)別(5 大維度對比)
setTimeout() 和 setInterval() 雖然都是定時器,且語法格式相似,但在執(zhí)行邏輯、使用場景等方面存在本質(zhì)區(qū)別,通過以下 5 大維度可直觀區(qū)分:
| 對比維度 | 一次性定時器(setTimeout ()) | 周期性定時器(setInterval ()) |
|---|---|---|
| 執(zhí)行次數(shù) | 僅執(zhí)行一次,回調(diào)執(zhí)行完畢后定時器自動失效 | 重復(fù)執(zhí)行多次,直到手動清除或頁面卸載 |
| 核心用途 | 實(shí)現(xiàn)單次延遲執(zhí)行(如延遲提示、防抖兜底) | 實(shí)現(xiàn)周期性重復(fù)執(zhí)行(如倒計時、輪詢、輪播) |
| 定時器狀態(tài) | 執(zhí)行后自動失效,無需手動清除(除非提前終止) | 持續(xù)處于激活狀態(tài),必須手動清除否則一直執(zhí)行 |
| 時間特性 | 延遲delay毫秒后執(zhí)行單次回調(diào) | 每隔delay毫秒執(zhí)行一次回調(diào),存在執(zhí)行堆積風(fēng)險 |
| 底層邏輯 | 單次將回調(diào)放入宏任務(wù)隊(duì)列 | 周期性將回調(diào)放入宏任務(wù)隊(duì)列,形成循環(huán) |
補(bǔ)充:關(guān)鍵差異實(shí)戰(zhàn)驗(yàn)證
// 1. setTimeout():僅執(zhí)行一次
setTimeout(() => {
console.log("setTimeout:我只執(zhí)行一次");
}, 1000);
// 2. setInterval():重復(fù)執(zhí)行,需手動清除
let times = 0;
const intervalId = setInterval(() => {
times++;
console.log(`setInterval:我執(zhí)行了${times}次`);
if (times === 3) {
clearInterval(intervalId); // 執(zhí)行3次后手動清除
console.log("setInterval:我被清除了,停止執(zhí)行");
}
}, 1000);
輸出結(jié)果順序:
plaintext
setTimeout:我只執(zhí)行一次 setInterval:我執(zhí)行了1次 setInterval:我執(zhí)行了2次 setInterval:我執(zhí)行了3次 setInterval:我被清除了,停止執(zhí)行
四、第三部分:如何清除定時器?(核心方法與實(shí)戰(zhàn))
無論是setTimeout() 還是 setInterval(),創(chuàng)建時都會返回一個唯一的定時器 ID,清除定時器的核心就是通過這個 ID,調(diào)用對應(yīng)的清除方法,終止定時器的執(zhí)行(或周期性執(zhí)行)。
1. 對應(yīng)清除方法:一對一清除,不可混用
JS 提供了兩個專門的定時器清除方法,分別對應(yīng)兩種定時器,需一一對應(yīng)使用,不可混用(雖然混用在部分瀏覽器中可能生效,但不符合規(guī)范,存在兼容性風(fēng)險)。
(1)清除一次性定時器:clearTimeout ()
核心功能:用于清除由setTimeout() 創(chuàng)建的一次性定時器,終止回調(diào)函數(shù)的執(zhí)行(僅對未執(zhí)行的定時器有效,若回調(diào)已執(zhí)行,定時器已失效,調(diào)用該方法無任何效果)。
語法格式:
// timeoutId 是 setTimeout() 返回的定時器ID clearTimeout(timeoutId);
實(shí)戰(zhàn)示例:
// 1. 創(chuàng)建一次性定時器
const timeoutId = setTimeout(() => {
console.log("該回調(diào)不會被執(zhí)行,因?yàn)槎〞r器被提前清除了");
}, 3000);
console.log("創(chuàng)建定時器,準(zhǔn)備2秒后清除它");
// 2. 提前2秒清除定時器(此時回調(diào)還未執(zhí)行)
setTimeout(() => {
clearTimeout(timeoutId);
console.log("定時器已被清除,回調(diào)終止執(zhí)行");
}, 2000);
(2)清除周期性定時器:clearInterval ()
核心功能:用于清除由setInterval() 創(chuàng)建的周期性定時器,終止回調(diào)函數(shù)的后續(xù)重復(fù)執(zhí)行(調(diào)用后,定時器立即失效,不再向宏任務(wù)隊(duì)列添加新的回調(diào))。
語法格式:
// intervalId 是 setInterval() 返回的定時器ID clearInterval(intervalId);
實(shí)戰(zhàn)示例:
// 1. 創(chuàng)建周期性定時器(實(shí)現(xiàn)倒計時)
let countdown = 5;
const intervalId = setInterval(() => {
countdown--;
if (countdown > 0) {
console.log(`倒計時剩余:${countdown}秒`);
} else {
console.log("倒計時結(jié)束,清除定時器");
// 2. 倒計時結(jié)束,清除周期性定時器
clearInterval(intervalId);
}
}, 1000);
輸出結(jié)果:
plaintext
倒計時剩余:4秒 倒計時剩余:3秒 倒計時剩余:2秒 倒計時剩余:1秒 倒計時結(jié)束,清除定時器
2. 清除定時器的關(guān)鍵注意事項(xiàng)
(1)必須保存定時器 ID,否則無法精準(zhǔn)清除
創(chuàng)建定時器時,一定要將返回的 ID 賦值給變量保存,若丟失 ID,將無法精準(zhǔn)清除該定時器,只能等待頁面卸載或使用clearTimeout()/clearInterval() 不帶參數(shù)清除所有定時器(不推薦,會清除其他無關(guān)定時器)。
// 錯誤示例:未保存定時器ID,無法精準(zhǔn)清除
setInterval(() => {
console.log("無法精準(zhǔn)清除,只能一直執(zhí)行到頁面卸載");
}, 1000);
// 正確示例:保存定時器ID,便于后續(xù)精準(zhǔn)清除
const intervalId = setInterval(() => {
console.log("可通過ID精準(zhǔn)清除");
}, 1000);
clearInterval(intervalId);
(2)避免 “定時器泄漏”:組件卸載 / 頁面關(guān)閉前清除定時器
在前端框架(React、Vue)開發(fā)中,若組件中創(chuàng)建了定時器,在組件卸載時未清除,會導(dǎo)致 “定時器泄漏”—— 組件已銷毀,但定時器仍在后臺執(zhí)行,不僅浪費(fèi)內(nèi)存,還可能引發(fā)報錯(如訪問已銷毀的組件 DOM)。
Vue 組件示例(組件卸載前清除定時器):
<template>
<div>組件倒計時:{{ countdown }}</div>
</template>
<script>
export default {
data() {
return {
countdown: 10,
intervalId: null // 保存定時器ID
};
},
mounted() {
// 創(chuàng)建周期性定時器
this.intervalId = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) {
clearInterval(this.intervalId);
}
}, 1000);
},
beforeUnmount() {
// 組件卸載前清除定時器,避免泄漏
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
};
</script>
(3)延遲時間為 0 的定時器,仍可被清除
即使setTimeout() 的延遲時間設(shè)為0,回調(diào)函數(shù)仍未立即執(zhí)行(處于宏任務(wù)隊(duì)列等待),此時調(diào)用clearTimeout() 仍可終止其執(zhí)行。
const timeoutId = setTimeout(() => {
console.log("該回調(diào)不會被執(zhí)行");
}, 0);
clearTimeout(timeoutId); // 立即清除,回調(diào)無法執(zhí)行
(4)清除已失效 / 不存在的定時器,不會報錯
若對已執(zhí)行完畢的setTimeout() 定時器、已清除的setInterval() 定時器,或不存在的 ID 調(diào)用清除方法,JS 引擎不會拋出錯誤,僅會靜默失敗,無需額外做判斷(若需嚴(yán)謹(jǐn),可先判斷 ID 是否存在)。
// 1. 清除已執(zhí)行完畢的setTimeout()定時器
const timeoutId = setTimeout(() => {
console.log("回調(diào)已執(zhí)行");
}, 1000);
// 2秒后回調(diào)已執(zhí)行,定時器已失效
setTimeout(() => {
clearTimeout(timeoutId); // 無報錯,靜默失敗
}, 2000);
// 3. 清除不存在的定時器ID
clearInterval(9999); // 無報錯,靜默失敗
五、第四部分:實(shí)戰(zhàn)避坑指南 —— 定時器的常見問題與解決方案
1. 坑 1:setInterval () 回調(diào)執(zhí)行堆積,導(dǎo)致間隔不準(zhǔn)
問題現(xiàn)象:當(dāng)setInterval() 的回調(diào)函數(shù)執(zhí)行耗時超過delay時間間隔,后續(xù)回調(diào)會在宏任務(wù)隊(duì)列中堆積,回調(diào)執(zhí)行間隔小于預(yù)期的delay。
解決方案:使用setTimeout() 嵌套調(diào)用,模擬周期性執(zhí)行,避免堆積(每次回調(diào)執(zhí)行完畢后,再創(chuàng)建下一個定時器,保證間隔準(zhǔn)確)。
// 推薦:setTimeout() 嵌套,實(shí)現(xiàn)精準(zhǔn)間隔的周期性執(zhí)行
let count = 0;
function periodicExecute() {
// 1. 執(zhí)行核心業(yè)務(wù)邏輯
count++;
console.log(`執(zhí)行第${count}次,間隔2秒`);
// 2. 回調(diào)執(zhí)行完畢后,創(chuàng)建下一個定時器,保證間隔準(zhǔn)確
if (count < 5) {
setTimeout(periodicExecute, 2000);
}
}
// 啟動周期性執(zhí)行
setTimeout(periodicExecute, 2000);
2. 坑 2:混淆 “延遲時間” 與 “執(zhí)行時間”,預(yù)期不符
問題現(xiàn)象:認(rèn)為setTimeout(fn, 1000) 會在 1 秒后 “絕對準(zhǔn)確” 執(zhí)行,但實(shí)際執(zhí)行時間可能大于 1 秒(受主線程同步代碼阻塞影響)。
解決方案:
- 理解 JS 單線程和宏任務(wù)隊(duì)列機(jī)制,不依賴定時器的絕對準(zhǔn)確時間;
- 若需高精度定時,可使用
requestAnimationFrame()(適合動畫場景,刷新率與屏幕同步)或Web Worker(避免主線程阻塞)。
3. 坑 3:清除定時器后,仍執(zhí)行一次回調(diào)
問題現(xiàn)象:清除周期性定時器后,回調(diào)函數(shù)仍執(zhí)行了一次,核心原因是:清除時,下一個回調(diào)已經(jīng)被放入宏任務(wù)隊(duì)列,等待執(zhí)行。
解決方案:
- 清除定時器前,增加狀態(tài)判斷,避免回調(diào)執(zhí)行無效邏輯;
- 使用
setTimeout()嵌套替代setInterval(),從根源上避免該問題。
let isActive = true; // 狀態(tài)標(biāo)記
let count = 0;
const intervalId = setInterval(() => {
// 增加狀態(tài)判斷,即使回調(diào)已入隊(duì),也不執(zhí)行無效邏輯
if (!isActive) return;
count++;
console.log(`執(zhí)行第${count}次`);
}, 1000);
// 2.5秒后清除定時器
setTimeout(() => {
isActive = false; // 先標(biāo)記狀態(tài)為無效
clearInterval(intervalId); // 再清除定時器
console.log("定時器已清除");
}, 2500);
六、總結(jié):核心知識點(diǎn)回顧與實(shí)戰(zhàn)準(zhǔn)則
- 兩種定時器的核心定位:
setTimeout()是 “單次延遲”,setInterval()是 “周期性重復(fù)”,根據(jù)業(yè)務(wù)場景選擇對應(yīng)工具; - 清除定時器的核心:保存定時器 ID,使用
clearTimeout()/clearInterval()一對一清除,組件卸載 / 頁面關(guān)閉前避免泄漏; - 實(shí)戰(zhàn)準(zhǔn)則:
- 優(yōu)先使用
setTimeout()嵌套實(shí)現(xiàn)精準(zhǔn)周期性執(zhí)行,避免setInterval()堆積問題; - 避免依賴定時器的絕對準(zhǔn)確時間,理解 JS 單線程執(zhí)行機(jī)制;
- 保存定時器 ID,精準(zhǔn)清除,杜絕 “定時器泄漏”。
- 優(yōu)先使用
定時器作為 JS 異步編程的基礎(chǔ)工具,看似簡單,實(shí)則蘊(yùn)含著單線程、任務(wù)隊(duì)列的核心思想。掌握兩種定時器的區(qū)別與清除方法,不僅能解決日常開發(fā)中的延遲、重復(fù)執(zhí)行需求,更能為后續(xù)深入學(xué)習(xí) Promise、async/await 等異步編程技術(shù)打下堅實(shí)基礎(chǔ)。
最后用一句話總結(jié):setTimeout() 是 “一次性的等待”,setInterval() 是 “無休止的循環(huán)”,清除定時器是 “及時止損的智慧”,選對、用對、清對,才能駕馭 JS 的異步延遲邏輯。
到此這篇關(guān)于js中的兩種定時器區(qū)別是什么以及怎么清除定時器的文章就介紹到這了,更多相關(guān)js兩種定時器區(qū)別及清除內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js+html+css實(shí)現(xiàn)簡單電子時鐘
這篇文章主要為大家詳細(xì)介紹了js+html+css實(shí)現(xiàn)簡單電子時鐘,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06
寫出更好的JavaScript程序之undefined篇(中)
前一篇我介紹了幾種廣為使用的利用undefined這個概念值的辦法,這一篇我會介紹一些不太常見的辦法,其中還包括一個很巧妙的,我個人覺得很值得推廣的辦法。2009-11-11
小議Function.apply() 之一------(函數(shù)的劫持與對象的復(fù)制)
小議Function.apply() 之一------(函數(shù)的劫持與對象的復(fù)制)...2006-11-11
文字溢出實(shí)現(xiàn)溢出的部分再放入一個新生成的div中具體代碼
說文字溢出,如何實(shí)現(xiàn)溢出的文字放入一個新生成的div中,原理就是判斷是否能在div里放下,如果不能,則在應(yīng)該斷開的地方,差入到新的div中2013-05-05
JavaScript作用域與作用域鏈?zhǔn)褂弥攸c(diǎn)講解
當(dāng)代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈,作用域鏈的用途是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問,下面這篇文章主要給大家介紹了關(guān)于JavaScript作用域與作用域鏈的相關(guān)資料,需要的朋友可以參考下2022-10-10
BootStrap框架中的data-[ ]自定義屬性理解(推薦)
這篇文章主要介紹了BootStrap框架中的data-[ ]自定義屬性理解(推薦),非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-02-02
[js高手之路]設(shè)計模式系列課程-發(fā)布者,訂閱者重構(gòu)購物車的實(shí)例
下面小編就為大家?guī)硪黄猍js高手之路]設(shè)計模式系列課程-發(fā)布者,訂閱者重構(gòu)購物車的實(shí)例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08

