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 的浏览器中加载。

另请参阅