setTimeout() 全域性函式

注意:此功能在Web Workers中可用。

全域性setTimeout()方法設定一個計時器,當計時器到期時,該計時器將執行一個函式或指定的程式碼段。

語法

js
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指定的函式的其他引數。

返回值

返回的timeoutID是一個正整數,用於標識透過呼叫setTimeout()建立的計時器。此值可以傳遞給clearTimeout()以取消超時。

保證在計時器仍處於活動狀態時,後續對同一物件(視窗或工作執行緒)呼叫setTimeout()setInterval()永遠不會重用timeoutID值。但是,不同的物件使用單獨的 ID 池。

描述

超時使用clearTimeout()取消。

要重複呼叫函式(例如,每N毫秒),請考慮使用setInterval()

非數字延遲值會靜默強制轉換為數字

如果使用delay值為非數字的值呼叫setTimeout(),則會對該值進行隱式型別強制以將其轉換為數字。例如,以下程式碼錯誤地將字串"1000"用於delay值,而不是數字1000——但它仍然有效,因為程式碼執行時,字串會被強制轉換為數字1000,因此程式碼會在 1 秒後執行。

js
setTimeout(() => {
  console.log("Delayed for 1 second.");
}, "1000");

但在許多情況下,隱式型別強制可能導致意外和令人驚訝的結果。例如,當以下程式碼執行時,字串"1 second"最終會被強制轉換為數字0——因此,程式碼會立即執行,延遲為零。

js
setTimeout(() => {
  console.log("Delayed for 1 second.");
}, "1 second");

因此,請勿將字串用於delay值,而應始終使用數字

js
setTimeout(() => {
  console.log("Delayed for 1 second.");
}, 1000);

使用非同步函式

setTimeout()是一個非同步函式,這意味著計時器函式不會暫停函式棧中其他函式的執行。換句話說,您不能使用setTimeout()在函式棧中下一個函式觸發之前建立“暫停”。

請參閱以下示例

js
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值不同。

請參閱以下示例

js
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]。但是,在以下情況下

js
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物件。

forEach()reduce()等陣列方法不同,setTimeout沒有傳遞thisArg的選項。如下所示,使用call設定this也不起作用。

js
setTimeout.call(myArray, myArray.myMethod, 2.0 * 1000); // error
setTimeout.call(myArray, myArray.myMethod, 2.5 * 1000, 2); // same error

解決方案

使用包裝函式

解決此問題的一種常用方法是使用一個包裝函式,將this設定為所需的值

js
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

包裝函式可以是箭頭函式

js
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的值

js
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()存在相同的問題。

js
// Don't do this
setTimeout("console.log('Hello World!');", 500);
js
// Do this instead
setTimeout(() => {
  console.log("Hello World!");
}, 500);

傳遞給setTimeout()的字串在全域性上下文中進行評估,因此在呼叫setTimeout()的上下文中區域性符號在字串作為程式碼進行評估時將不可用。

指定延遲時間過長的原因

超時觸發時間可能比預期時間長,原因有很多。本節介紹了最常見的原因。

巢狀超時

HTML 標準 中所述,瀏覽器會在setTimeout被巢狀呼叫5次後強制執行至少4毫秒的超時。

以下示例可以說明這一點,其中我們將對setTimeout的呼叫巢狀,延遲時間為0毫秒,並在每次處理程式被呼叫時記錄延遲。前四次,延遲大約為0毫秒,之後大約為4毫秒。

html
<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>
js
let last = 0;
let iterations = 10;

function timeout() {
  // log the time of this call
  logline(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 logline(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 桌面版和 Chrome 都對非活動選項卡設定了至少1秒的超時。
  • Firefox for Android 對非活動選項卡設定了至少15分鐘的超時,並且可能會完全解除安裝它們。
  • 如果選項卡包含 AudioContext,則 Firefox 不會限制非活動選項卡。

跟蹤指令碼的限流

Firefox 對其識別為跟蹤指令碼的指令碼實施了額外的限流。在前臺執行時,限流的最小延遲仍然是4毫秒。但在後臺選項卡中,限流的最小延遲為10,000毫秒,即10秒,並在文件首次載入30秒後生效。

有關更多詳細資訊,請參閱 跟蹤保護

延遲超時

如果頁面(或作業系統/瀏覽器)忙於其他任務,超時也可能比預期晚觸發。需要注意的一個重要情況是,在呼叫setTimeout()的執行緒終止之前,無法執行該函式或程式碼片段。例如

js
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 天)的延遲時,會導致整數溢位。因此,例如,以下程式碼

js
setTimeout(() => console.log("hi!"), 2 ** 32 - 5000);

…會導致超時立即執行(因為2**32 - 5000溢位為負數),而以下程式碼

js
setTimeout(() => console.log("hi!"), 2 ** 32 + 5000);

…會導致超時在大約 5 秒後執行。

注意:這與 Node.js 中的 setTimeout 行為不符,在 Node.js 中,任何大於 2,147,483,647 毫秒的超時都會導致立即執行。

示例

設定和清除超時

以下示例在網頁中設定了兩個簡單的按鈕,並將它們掛接到setTimeout()clearTimeout()例程。按下第一個按鈕將設定一個超時,並在兩秒後顯示一條訊息,並存儲超時 ID 以供clearTimeout()使用。您可以選擇透過按下第二個按鈕來取消此超時。

HTML

html
<button onclick="delayedMessage();">Show a message after two seconds</button>
<button onclick="clearMessage();">Cancel message before it happens</button>

<div id="output"></div>

JavaScript

js
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);
}

結果

另請參閱 clearTimeout() 示例

規範

規範
HTML 標準
# dom-settimeout-dev

瀏覽器相容性

BCD 表格僅在啟用 JavaScript 的瀏覽器中載入。

另請參閱