一、前言
在前端,我們有很多功能需要用到定時器。譬如輪詢,譬如定時關閉彈框,譬如實現秒表,譬如一段時間后跳轉頁面等等。因此,我們需要掌握定時器的用法。
二、設置定時器
目前window對象提供有兩個方法來實現定時器的效果,分別是window.setTimeout()和window.setInterval()。
其中setInterval()的作用是:使一段代碼每過指定時間就運行一次;常用於輪詢。
setInterval(function(){ console.log("這是一個setInterval定時器!"); }, 1000);//1000ms=1s,設定的代碼循環運行時的間隔時間,單位為ms
而setTimeout()的作用是:使一段代碼在指定時間后運行;常用於一次性定時器及輪詢。注意,setTimeout()是只運行一次代碼的,若要用setTimeout()進行輪詢,需要在setTimeout()的代碼里再調起當前setTimeout()所屬函數,才能達到循環的效果。
setTimeout(function(){ console.log("這是一個setTimeout定時器!"); }, 1000);//1000ms=1s,設定的代碼等待運行的時間,單位為ms
三、定時器的缺陷
1.定時器最大的優點,同時也是它最容易出錯的一點:定時器所有任務都是由同一個線程來調度。注意:定時器是異步執行的,它會放到所有同步任務都執行完之后再開始執行。
正是因此,定時器簡單易用。同時這也導致了定時器所有任務都是串行執行的,同一時間只能有一個任務在執行,前一個任務的延遲或異常都將會影響到之后的任務,即定時器的重疊。
2.定時器不會被自動銷毀,即它所占內存無法被自動回收。如果不手動清除定時器,它會一直占用內存資源。更可怕的是,一旦使用定時器進行輪詢,定時器所占的內存資源將會不斷上升,若與定時器重疊問題一起出現,常導致頁面卡頓。
所以,我們必須學會清除定時器。
四、清除定時器
定時器在調用時,都會返回一個整型的數字,該數字代表定時器的序號,即第多少個定時器,我們可以利用定時器的序號對定時器進行清除。
因此在清除定時器時,我們常在設置定時器時,定義一個變量來記錄定時器返回的定時器序號,然后在定時器完成后,調用該序號清除對應定時器。
目前有兩個方法來清除定時器,分別對應兩種定時器的方法。
其一是clearInterval(obj),對應setInterval()定時器;
var intervalBox = setInterval(function(){ console.log(intervalBox);//打印interval定時器,查看interval定時器效果 }, 1000); clearInterval(intervalBox);//清除interval定時器
其二是clearTimeout(obj),對應setTimeout()定時器;
var timeoutBox = setTimeout(function(){ console.log(timeoutBox);//打印interval定時器,查看interval定時器效果 }, 1000); clearTimeout(timeoutBox);//清除timeout定時器
注:事實上,我們在使用定時器時,常遇到這樣的情況:頁面跳轉時,上一個頁面的定時器未被清除,重新回到該頁面時,觸發定時器,造成定時器重疊。
要解決這個問題,需要分情況。若你的項目使用了vue或react等框架,頁面存在鈎子函數的話,在銷毀頁面的鈎子里清除定時器即可。若是單頁面,沒有鈎子函數,可以在定時器中去判斷該頁面的dom元素是否存在來確定是清除定時器還是繼續跑定時器:
<div id="pageId"></div> <script type="text/javascript"> var timeBox; function test() { clearTimeout(timeBox); if(document.getElementById('pageId') === null){ return; } timeBox = setTimeout(()=>{ console.log(timeBox); test(); },1000); }; test(); </script>
五、常用setTimeout()代替setInterval()定時器的原因
setTimeout()只執行一次,而setInterval()會循環調用。從理論上來說,我們應該使用setInterval()來執行輪詢,但實際上,我們會用setTimeout()調用自身實現循環來代替setInterval(),原因很簡單,setInterval()存在兩個重大缺陷:
一、setInterval()無視代碼錯誤
與setTimeout()不同,setInterval()中調用的代碼報錯並不會阻塞setInterval()的循環。如果setInterval()執行的代碼由於某種原因出了錯,它依然會繼續循環調用該代碼。
二、setInterval()無視內部代碼延遲
不管setInterval()內部的代碼需要多久才能完成,哪怕這段代碼沒有運行完成,setInterval()都會按照設置的時間不斷循環該段代碼。這就導致setInterval()內部的代碼執行不准確或者執行出錯。
例如,若使用setInterval()執行ajax輪詢,遇到服務器過載、網絡差等原因,ajax請求時間過長,到了setInterval()設置的時間,請求依舊未完成,setInterval()依然會發出一個新的ajax請求,最后,你的客戶端網絡隊列會塞滿ajax請求。
因此,我們常用setTimeout()調用自身實現循環來代替setInterval()。
六、總結示例
為實現相應功能,定時器不可或缺性,但是若不規范使用定時器,將會造成巨大困擾,輕則內存資源被嚴重占用,頁面卡頓,重則邏輯斷裂,出現重大bug。因此我們需要對定時器規范使用,及時清除。
我對定時器的使用有八字總結:隨意使用,即時清除。
簡單來說,每設置一個定時器,都要對應清除,示例代碼如下:
<html> <head> <meta charset="utf-8"> <title>定時器</title> </head> <body> <button onclick="startInterval()">開始Interval</button> <button onclick="stopInterval()">停止Interval</button> <button onclick="startTimeout()">正常Timeout定時器</button> <button onclick="startTimeoutTwo()">循環的Timeout定時器</button> <button onclick="stopTimeout()">停止Timeout</button> <script> var intervalBox;//interval定時器存儲器 //設置interval定時器 function startInterval() { clearInterval(intervalBox);//初始化interval定時器,防止定時器重疊 intervalBox = setInterval(function(){//設置interval定時器 console.log(intervalBox);//打印interval定時器,查看interval定時器效果 }, 1000);//定時器間隔時間1000ms } //結束interval定時器 function stopInterval() { clearInterval(intervalBox);//清除interval定時器 } var timeoutBox;//timeout定時器存儲器 //設置正常的timeout定時器 function startTimeout() { clearTimeout(timeoutBox);//初始化timeout定時器,防止定時器重疊 timeoutBox = setTimeout(function(){//設置timeout定時器 console.log(timeoutBox);//打印timeout定時器,查看timeout定時器效果 clearTimeout(timeoutBox);//清除當前timeout定時器,timeout定時器只運行一次代碼,直接清掉它 // location.href="timer.html";//一段時間后跳轉頁面是setTimeout的常用場景之一 }, 1000); } //設置循環的timeout定時器 function startTimeoutTwo() { clearTimeout(timeoutBox);//初始化timeout定時器,防止定時器重疊 timeoutBox = setTimeout(function(){ console.log(timeoutBox);//打印timeout定時器,查看timeout定時器效果 startTimeoutTwo();//循環調用函數自身,以達到循環的效果 }, 1000); } // 結束循環的timeout定時器 function stopTimeout() { clearTimeout(timeoutBox); } </script> </body> </html>