前言
setTimeout()
我們在日常工作中經常使用,最近做了一個功能是關于setTimeout()
的,總結了一些用法。
在這篇文章中,你將了解 setTimeout()
方法——它是什么以及如何在你的程序中使用它。
以下是我在這篇快速指南中要介紹的內容:
- JavaScript 中的
setTimeout()
基本語法 - 進階語法:防抖、代碼邏輯執行時間可能比定時器時間間隔要長如何處理
setTimeout()
的定時器是否精準- 定時器在非激活tab或者熄屏的時候還會按照預期去執行嗎
- 定時器如何進行時間糾正
- 簡單比較下scheduler.yield
基本的語法
scss
代碼解讀
復制代碼setTimeout(code)
setTimeout(code, delay)
setTimeout(functionRef)
setTimeout(functionRef, delay)
setTimeout(functionRef, delay, param1)
setTimeout(functionRef, delay, param1, param2)
setTimeout(functionRef, delay, param1, param2, /* …, */ paramN)
例如我們寫個最簡單的
我們來添加一個參數進行測試 如下,可以打印出來獲取到的參數params1和params2
返回值
返回值 timeoutID
是一個正整數,表示由 setTimeout()
調用創建的定時器的標識符??梢詫⑦@個值傳遞給 clearTimeout()
來取消該定時器。
我們嘗試在1秒的時候對timerId進行清除,測試下是否可以正常打印,如你所料,不會打印timerId內的數據
進階用法
1 防抖(常用于表單提交)
我們在表單提交的時候,比如希望表單的提交按鈕在1秒內之內只生效一次,可以利用settimeout來實現。
如下,假如在頁面上有一個id為submitBtn的按鈕,添加了一個點擊事件,當1秒內每次點擊都會清除之前的timeoutId,不會執行提交的邏輯。從而確保只有在最后一次點擊后的1秒內沒有再次點擊時,才會執行實際的操作。
javascript
代碼解讀
復制代碼 let submitBtn = document.getElementById("submitBtn");
let timeoutId;
function onTest() {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(function() {
// 在這里編寫提交邏輯
alert("按鈕被點擊了!");
}, 1000);
}
submitBtn.addEventListener("click", onTest);
2 優先展示用戶希望看到的內容(利用JS事件機制)
假如我們有一個人員的新增表單,部門樹的下拉框有10K條數據,假如我們還沒有虛擬下拉樹,數據的渲染會很慢, 打開這個新增的表單可能會有3到5S的延遲后表單才會打開。
我們可以將這個部門樹的下拉值綁定寫在了settimeout內,等主線程的任務執行后,再執行綁定部門樹的操作,可以優先將其他的內容展示出來后再進行部門樹的綁定。
3 替代setInterval防止請求阻塞
比如我們有個需求: 1秒的間隔輪詢服務器,頁面展示內容
正常的話可以用setInterval沒什么問題,
假如請求的接口可能因網絡延遲、服務器無響應以及許多其他的問題而導致請求無法在分配的時間內完成(假如服務器處理了3秒)
我們把請求的間隔設置為1秒,實際的接口在2秒多,就會造成請求的阻塞,其實我們想實現的是在一次接口請求結束后再發起下一次請求
如下方的截圖
接口固定在大約2秒返回,就會導致永遠有幾個請求在阻塞,跟我們的預想不同
我們對上面的代碼進行改造如下
實現如果如下,我們保證有一個請求在pending,會在一個請求發出后,再進行發送下一次請求
代碼解釋如下
在上面的代碼片段中,聲明了一個具名函數 loop()
,并被立即執行。loop()
在完成代碼邏輯的執行后,會在內部遞歸調用 setTimeout()
。雖然該模式不保證以固定的時間間隔執行,但它保證了上一次定時任務在遞歸前已經完成。
可能遇到的場景
1 settimeout這個定時器準嗎
答案:正常場景下是準的,某些場景下是不準的,
有很多因素會導致超時比設定的預期值更久 如需查看更多的原因點擊我
嵌套多次的時候會有4ms的延遲(可以點擊下方的例子進行測試)
當嵌套多次的結果如下,就不做詳細介紹了,了解就行。目前我沒想到哪些場景下settimeout會被嵌套調用5次,就不做繼續研究,我們只需要知道這個概念就行,等出現這個問題了有個排查方向就行
主線程有耗時的任務
我們做個實驗,我們對id為main對dom進行2萬次的修改innerHtml,在控制臺打印時間,
原本2秒變為了4秒,所以settimeout 有時候并不會按照預期的時間間隔來執行
這里其實就涉及到了js的事件為單線程機制,我們用performance簡單分析下,看到有個3865ms的parse HTML的,然后執行Run Microtasks也就是微任務,也就是等我們的同步任務執行后,再去執行settimeout內的東西
在非活動標簽的tab,待機下的settimeout還會按照預期執行嗎?
以下的測試是在chrome的mac版本進行測試
代碼很簡單,寫了個2秒的定時器
待機狀態
tab非激活
過幾分鐘后,定時器會從2秒變為1分鐘
由此我們得知,在瀏覽器激活的時候,settimeout 會按照我們的預期去執行,在非激活(tab不選中過段時間、電腦處于待機狀態)下,定時器會按照1min一次去執行,
但是有種特殊場景,audio假如正在播放,此時頁面的settimeout會被當作激活狀態
settimeout 時間糾正
當然,因為js的事件機制,settimeout存在時間偏差,就會存在時間糾正,下面介紹了兩個js的時間糾正方式,雖然我試用下來不太理想,也可能是我的姿勢不對,有更好的方式歡迎和我討論
計算時間差(并不能完全避免,只能糾正)
直接在網上抄寫了個例子。 使用 setTimeout 進行計時,每次計時都會用系統時間修復時間差
js
代碼解讀
復制代碼 function timer_setTimeout() {
const speed = 1000; // 設置定時器的間隔速度為1000毫秒(1秒)
let countTime = 0; // 初始化計時器計數變量
let start = new Date().getTime(); // 記錄計時開始時的時間戳
// 定義計時器的執行函數
function run() {
countTime++; // 每次執行時遞增計時器的計數
// 計算按照計時器當前速度實際經過的時間(countTime * 速度)
let realTime = (countTime * speed);
// 計算從計時開始到現在系統經過的時間
let sysTime = (Date.now() - start);
// 計算實際時間和理想時間之間的差異
let patch = (sysTime - realTime);
// 使用系統時間進行修復,調整下一次setTimeout的延遲時間
// 通過設置speed - diff,嘗試校正setTimeout的延遲,以補償偏差
window.setTimeout(run, (speed - patch));
// 更新頁面元素timeoutDom的文本內容,顯示當前計時器的值
timeoutDom.innerText = `setTimeout: ${countTime}`;
}
// 啟動計時器,初始調用run函數,并設置延遲為speed
window.setTimeout(run, speed);
}
// 調用函數,創建并啟動setTimeout計時器
timer_setTimeout();
webworker(測試下來效果不好)
javascript
代碼解讀
復制代碼 console.log(new Date());
for (let index = 0; index < 20000; index++) {
document.getElementById('main').innerHTML += index;
}
function timer_worker() {
// 創建一個Blob對象,用于生成一個可以在Web Worker中執行的JavaScript代碼URL
const blob = new Blob(
[
`let countTime = 0;
self.setInterval(() => { // 在Web Worker的上下文中設置一個定時器
countTime++; // 每次定時器觸發時遞增計數器
self.postMessage(countTime); // 使用postMessage方法發送計數器的值到主線程
}, 5000);` // 定時器的時間間隔設置為1秒
],
{ type: 'application/javascript' }
); // 指定Blob的內容類型為JavaScript
// 使用URL.createObjectURL方法創建一個可以被Web Worker使用的URL
const worker = new Worker(URL.createObjectURL(blob));
// 設置Web Worker的onmessage事件處理器
// 當Web Worker使用postMessage發送消息時,該處理器會被觸發
worker.onmessage = (ev) => {
// 更新DOM元素workerDom的文本內容,顯示從Web Worker接收到的計時器值
console.log(new Date() + ` worker: ${ev.data}`);
};
}
timer_worker();
遇到長的任務的時候,還是會延遲執行
scheduler.yield
在查找 setTimeout
的資料的時候,發現了有一個比較好的東西,scheduler.yield
, 感興趣的老板可以點擊我進行體驗,目前兼容性不好(24年8月21日的才支持),不做詳細介紹。
兼容性如下
總結一句話
使用 setTimeout
是將事件插入在隊列的結尾。yield而是發送到隊列的前面。這樣,既可以讓步以提高網站上的輸入響應速度,又可以確保在讓步后完成的工作不會延遲。
參考文檔
- developer.chrome.com/blog/introd…
- developer.chrome.com/blog/timer-…
- developer.mozilla.org/zh-CN/docs/…
總結
setTimeout
可以實現延遲的一些事件,比如多少秒后返回首頁等setTimeout
可以通過一定時間內事件只執行一次實現防抖setTimeout
可以替代setInterval
實現更好的輪詢setTimeout
可以通過js事件的異步機制,優先展示用戶關注的內容setTimeout
的事件定時器在某些場景下定時器不準,大部分場景下是準的,如果要求比較精確需要可以進行時間差的修正setTimeout
在熄屏或者非激活tab的場景下,計時器會延長變為1min執行一次setTimeout
是把事件插入到尾部,未來我們可以使用scheduler.yield
的暫停頁面的操作,提升用戶體驗 、