Window: setTimeout() 方法
Baseline 廣泛可用 *
Window 介面的 setTimeout() 方法設定一個計時器,該計時器在計時器到期後執行一個函式或一段指定的程式碼。
語法
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)
引數
functionRef-
計時器到期後要執行的
function。 code-
一種替代語法,允許你包含一個字串而不是函式,該字串在計時器到期時被編譯並執行。由於使用
eval()存在安全風險,因此不推薦使用此語法。 delay可選-
計時器在執行指定函式或程式碼之前應等待的時間,以毫秒為單位。如果省略此引數,則使用值 0,表示“立即”執行,或者更準確地說,在下一個事件迴圈中執行。
請注意,在任何一種情況下,實際延遲可能比預期的要長;請參閱下面的延遲時間長於指定時間的原因。
另請注意,如果該值不是數字,則會對該值進行隱式型別強制轉換,以將其轉換為數字——這可能導致意想不到和令人驚訝的結果;請參閱非數字延遲值被隱式強制轉換為數字以獲取示例。
param1, …,paramN可選-
傳遞給
functionRef指定函式的其他引數。
返回值
setTimeout() 方法返回一個正整數(通常在 1 到 2,147,483,647 之間),它唯一地標識由呼叫建立的計時器。此識別符號,通常稱為“超時 ID”,可以傳遞給 clearTimeout() 以取消計時器。
在同一個全域性環境中(例如,特定的視窗或 Worker),只要原始計時器保持活動狀態,超時 ID 就保證不會用於任何新的計時器。但是,單獨的全域性環境維護自己的獨立計時器 ID 池。
描述
超時使用 Window.clearTimeout() 取消。
要重複呼叫一個函式(例如,每 N 毫秒),請考慮使用 setInterval()。
非數字延遲值被隱式強制轉換為數字
如果 setTimeout() 使用非數字的delay 值呼叫,則會對該值進行隱式型別強制轉換以將其轉換為數字。例如,以下程式碼錯誤地將字串 "1000" 用於 delay 值,而不是數字 1000——但它仍然有效,因為當代碼執行時,該字串被強制轉換為數字 1000,因此程式碼在 1 秒後執行。
setTimeout(() => {
console.log("Delayed for 1 second.");
}, "1000");
但在許多情況下,隱式型別強制轉換可能導致意想不到和令人驚訝的結果。例如,當以下程式碼執行時,字串 "1 second" 最終被強制轉換為數字 0——因此,程式碼立即執行,沒有延遲。
setTimeout(() => {
console.log("Delayed for 1 second.");
}, "1 second");
因此,不要將字串用於 delay 值,而應始終使用數字。
setTimeout(() => {
console.log("Delayed for 1 second.");
}, 1000);
使用非同步函式
setTimeout() 是一個非同步函式,這意味著計時器函式不會暫停函式棧中其他函式的執行。換句話說,您不能使用 setTimeout() 在函式棧中下一個函式觸發之前建立“暫停”。
請看以下示例
setTimeout(() => {
console.log("this is the first message");
}, 5000);
setTimeout(() => {
console.log("this is the second message");
}, 3000);
setTimeout(() => {
console.log("this is the third message");
}, 1000);
// Output:
// this is the third message
// this is the second message
// this is the first message
請注意,第一個函式在呼叫第二個函式之前不會建立 5 秒的“暫停”。相反,第一個函式被呼叫,但等待 5 秒才執行。當第一個函式等待執行時,第二個函式被呼叫,並在執行之前對第二個函式應用 3 秒的等待。由於第一個和第二個函式的計時器都沒有完成,第三個函式被呼叫並首先完成其執行。然後是第二個。最後,第一個函式在其計時器最終完成後才執行。
要建立一個只有在一個函式完成之後才觸發另一個函式的程序,請參閱Promise 的文件。
“this”問題
當您將方法傳遞給 setTimeout() 時,它將以可能與您的預期不同的 this 值呼叫。一般問題在 JavaScript 參考中有詳細解釋。
由 setTimeout() 執行的程式碼是從與呼叫 setTimeout 的函式分離的執行上下文中呼叫的。適用於被呼叫函式設定 this 關鍵字的通常規則適用,如果您沒有在呼叫中或使用 bind 設定 this,它將預設為 window(或 global)物件,即使在嚴格模式下也是如此。它將與呼叫 setTimeout 的函式的 this 值不同。
請看以下示例
const myArray = ["zero", "one", "two"];
myArray.myMethod = function (sProperty) {
console.log(arguments.length > 0 ? this[sProperty] : this);
};
myArray.myMethod(); // prints "zero,one,two"
myArray.myMethod(1); // prints "one"
上述程式碼之所以有效,是因為當呼叫 myMethod 時,其 this 被呼叫設定為 myArray,因此在函式內部,this[sProperty] 等同於 myArray[sProperty]。然而,在以下程式碼中
setTimeout(myArray.myMethod, 1.0 * 1000); // prints "[object Window]" after 1 second
setTimeout(myArray.myMethod, 1.5 * 1000, "1"); // prints "undefined" after 1.5 seconds
myArray.myMethod 函式被傳遞給 setTimeout,然後當它被呼叫時,它的 this 未設定,因此它預設為 window 物件。
也沒有選項像 Array 方法(例如 forEach() 和 reduce())那樣將 thisArg 傳遞給 setTimeout。如下所示,使用 call 設定 this 也不起作用。
setTimeout.call(myArray, myArray.myMethod, 2.0 * 1000); // error
setTimeout.call(myArray, myArray.myMethod, 2.5 * 1000, 2); // same error
解決方案
使用包裝函式
解決此問題的一種常見方法是使用包裝函式將 this 設定為所需的值。
setTimeout(function () {
myArray.myMethod();
}, 2.0 * 1000); // prints "zero,one,two" after 2 seconds
setTimeout(function () {
myArray.myMethod("1");
}, 2.5 * 1000); // prints "one" after 2.5 seconds
包裝函式可以是箭頭函式。
setTimeout(() => {
myArray.myMethod();
}, 2.0 * 1000); // prints "zero,one,two" after 2 seconds
setTimeout(() => {
myArray.myMethod("1");
}, 2.5 * 1000); // prints "one" after 2.5 seconds
使用 bind()
或者,您可以使用 bind() 為給定函式的所有呼叫設定 this 的值。
const myArray = ["zero", "one", "two"];
const myBoundMethod = function (sProperty) {
console.log(arguments.length > 0 ? this[sProperty] : this);
}.bind(myArray);
myBoundMethod(); // prints "zero,one,two" because 'this' is bound to myArray in the function
myBoundMethod(1); // prints "one"
setTimeout(myBoundMethod, 1.0 * 1000); // still prints "zero,one,two" after 1 second because of the binding
setTimeout(myBoundMethod, 1.5 * 1000, "1"); // prints "one" after 1.5 seconds
傳遞字串字面量
將字串而不是函式傳遞給 setTimeout() 具有與使用 eval() 相同的問題。
// Don't do this
setTimeout("console.log('Hello World!');", 500);
// Do this instead
setTimeout(() => {
console.log("Hello World!");
}, 500);
傳遞給 setTimeout() 的字串是在全域性上下文中評估的,因此當字串作為程式碼評估時,呼叫 setTimeout() 的上下文中的區域性符號將不可用。
延遲時間長於指定時間的原因
超時可能比預期時間長有許多原因。本節描述了最常見的原因。
巢狀超時
正如 HTML 標準中指定的那樣,一旦巢狀呼叫 setTimeout 安排了 5 次,瀏覽器將強制執行 4 毫秒的最小超時時間。
這可以在以下示例中看到,其中我們嵌套了一個延遲為 0 毫秒的 setTimeout 呼叫,並在每次呼叫處理程式時記錄延遲。前四次,延遲大約為 0 毫秒,之後大約為 4 毫秒。
<button id="run">Run</button>
<table>
<thead>
<tr>
<th>Previous</th>
<th>This</th>
<th>Actual delay</th>
</tr>
</thead>
<tbody id="log"></tbody>
</table>
let last = 0;
let iterations = 10;
function timeout() {
// log the time of this call
log(new Date().getMilliseconds());
// if we are not finished, schedule the next call
if (iterations-- > 0) {
setTimeout(timeout, 0);
}
}
function run() {
// clear the log
const log = document.querySelector("#log");
while (log.lastElementChild) {
log.removeChild(log.lastElementChild);
}
// initialize iteration count and the starting timestamp
iterations = 10;
last = new Date().getMilliseconds();
// start timer
setTimeout(timeout, 0);
}
function log(now) {
// log the last timestamp, the new timestamp, and the difference
const tableBody = document.getElementById("log");
const logRow = tableBody.insertRow();
logRow.insertCell().textContent = last;
logRow.insertCell().textContent = now;
logRow.insertCell().textContent = now - last;
last = now;
}
document.querySelector("#run").addEventListener("click", run);
非活動選項卡中的超時
為了減少後臺選項卡的負載(以及相關的電池使用),瀏覽器將強制執行非活動選項卡中的最小超時延遲。如果頁面使用 Web Audio API AudioContext 播放聲音,也可能會取消。
具體情況因瀏覽器而異。
-
Firefox Desktop 對非活動選項卡的最小超時時間為 1 秒。
-
Firefox for Android 對非活動選項卡的最小超時時間為 15 分鐘,並且可能完全解除安裝它們。
-
如果選項卡包含
AudioContext,Firefox 不會限制非活動選項卡。 -
Chrome 根據選項卡活動使用不同級別的限制。
-
最小限制:適用於當頁面可見、最近發出聲音或被 Chrome 視為活動狀態時的計時器。計時器接近請求的間隔執行。
-
限制:適用於當不滿足最小限制條件並且以下任何條件為真時的計時器
- 巢狀計數(即,鏈式計時器呼叫的數量)小於 5。
- 頁面不可見時間少於 5 分鐘。
- WebRTC 處於活動狀態。
此狀態下的計時器每秒檢查一次,可以與其他具有類似超時的計時器批次處理。
- 強化限制:在 Chrome 88(2021 年 1 月)中引入。適用於當既不滿足最小限制也不滿足限制條件,並且滿足以下所有條件時的計時器
- 巢狀計數為 5 或更高。
- 頁面不可見時間超過 5 分鐘。
- 頁面靜默時間超過 30 秒。
- WebRTC 不活動。
此狀態下的計時器每分鐘檢查一次,可以與其他具有類似超時的計時器批次處理。
-
跟蹤指令碼的限制
Firefox 對其識別為跟蹤指令碼的指令碼強制執行額外的限制。在前臺執行時,限制最小延遲仍為 4ms。然而,在後臺選項卡中,限制最小延遲為 10,000 毫秒,即 10 秒,這在文件首次載入後 30 秒生效。
有關更多詳細資訊,請參閱 跟蹤保護。
延遲超時
如果頁面(或作業系統/瀏覽器)忙於其他任務,超時也可能比預期時間晚。需要注意的一個重要情況是,在呼叫 setTimeout() 的執行緒終止之前,無法執行函式或程式碼片段。例如
function foo() {
console.log("foo has been called");
}
setTimeout(foo, 0);
console.log("After setTimeout");
將寫入控制檯
After setTimeout foo has been called
這是因為即使 setTimeout 以零延遲呼叫,它也被放置在佇列中並安排在下一個機會執行;而不是立即執行。當前正在執行的程式碼必須在佇列中的函式執行之前完成,因此結果執行順序可能與預期不同。
頁面載入期間的超時延遲
在當前選項卡載入期間,Firefox 將推遲觸發 setTimeout() 計時器。觸發會延遲,直到主執行緒被視為空閒(類似於 Window.requestIdleCallback()),或者直到載入事件觸發。
WebExtension 後臺頁面和計時器
在 WebExtensions 中,setTimeout() 不可靠。擴充套件作者應改為使用 alarms API。
最大延遲值
瀏覽器在內部將延遲儲存為 32 位有符號整數。當使用大於 2,147,483,647 毫秒(約 24.8 天)的延遲時,會導致整數溢位。例如,此程式碼
setTimeout(() => console.log("hi!"), 2 ** 32 - 5000);
...導致超時立即執行(因為 2**32 - 5000 溢位為負數),而以下程式碼
setTimeout(() => console.log("hi!"), 2 ** 32 + 5000);
...導致超時在大約 5 秒後執行。
注意:這與 Node.js 中的 setTimeout 行為不符,在 Node.js 中,任何大於 2,147,483,647 毫秒的超時都會導致立即執行。
示例
設定和清除超時
以下示例在網頁中設定了兩個簡單的按鈕,並將它們連線到 setTimeout() 和 clearTimeout() 例程。按下第一個按鈕將設定一個超時,該超時在兩秒後顯示一條訊息,並存儲超時 ID 以供 clearTimeout() 使用。您可以選擇透過按下第二個按鈕來取消此超時。
HTML
<button id="show">Show a message after two seconds</button>
<button id="cancel">Cancel message before it happens</button>
<div id="output"></div>
JavaScript
let timeoutID;
function setOutput(outputContent) {
document.querySelector("#output").textContent = outputContent;
}
function delayedMessage() {
setOutput("");
timeoutID = setTimeout(setOutput, 2 * 1000, "That was really slow!");
}
function clearMessage() {
clearTimeout(timeoutID);
}
document.getElementById("show").addEventListener("click", delayedMessage);
document.getElementById("cancel").addEventListener("click", clearMessage);
結果
另請參閱 clearTimeout() 示例。
規範
| 規範 |
|---|
| HTML # dom-settimeout-dev |
瀏覽器相容性
載入中…