Movatterモバイル変換


[0]ホーム

URL:


  1. 面向开发者的 Web 技术
  2. Web API
  3. Window
  4. Window:setTimeout() 方法

此页面由社区从英文翻译而来。了解更多并加入 MDN Web Docs 社区。

View in EnglishAlways switch to English

Window:setTimeout() 方法

Baseline Widely available *

This feature is well established and works across many devices and browser versions. It’s been available across browsers since 2015年7月.

* Some parts of this feature may have varying levels of support.

Window 接口的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

当定时器到期后要执行的函数

code

这是一个代替语法,允许你包含在定时器到期后编译和执行的字符串而非函数。这个语法因为与eval() 存在相同的安全风险所以不推荐使用。

delay可选

定时器在执行指定的函数或代码之前应该等待的时间,单位是毫秒。如果省略该参数,则使用值 0,意味着“立即”执行,或者更准确地说,在下一个事件循环执行。

注意,无论是哪种情况,实际延迟可能会比预期长一些,参见下方延时比指定值更长的原因一节的叙述。

还要注意的是,如果值不是数字,隐式的类型强制转换会静默地将其转换为数字——这可能导致意想不到的、令人惊讶的结果;参见非数字延迟值被静默地强制转化为数字以获取示例。

param1、……、paramN可选

会被传递给由functionRef 指定的函数的附加参数。

返回值

返回值timeoutID 是一个正整数,表示由setTimeout() 调用创建的定时器的标识符。可以将这个值传递给clearTimeout() 来取消该定时器。

在定时器仍然激活的情况下,保证timeoutID 值不会被同一窗口(window)中的后续setTimeout()setInterval() 调用重复使用。然而,不同的对象使用不同的 ID 池。

描述

使用clearTimeout() 取消超时。

如果要重复调用某个函数(如每N 毫秒调用一次),考虑使用setInterval()

非数字延迟值被静默地强制转化为数字

如果调用setTimeout()delay 值不是数字,隐式的类型强制转换会静默地将其转换为数字。例如,下面的代码在delay 值中错误地使用了字符串"1000",而不是数字1000——但它仍然有效,因为当代码运行时,字符串被强制转换成数字1000,所以代码在 1 秒后执行。

js
setTimeout(() => {  console.log("延迟了 1 秒。");}, "1000");

但在许多情况下,隐式类型强制转换会导致意想不到的、令人惊讶的结果。例如,当下面的代码运行时,字符串"1 秒" 最终被强制转换为数字0——因此,代码立即执行,没有延迟。

js
setTimeout(() => {  console.log("延迟了 1 秒。");}, "1 秒");

因此,不要使用字符串来表示delay 值,而要始终使用数字:

js
setTimeout(() => {  console.log("延迟了 1 秒。");}, 1000);

与异步函数配合

setTimeout() 是异步函数,意味着计时器函数将不会暂停函数栈中其他函数的执行。也就是说,你不能使用setTimeout() 来在函数栈中下一个函数执行前执行“暂停”操作。

查看以下示例:

js
setTimeout(() => {  console.log("这是第一条消息");}, 5000);setTimeout(() => {  console.log("这是第二条消息");}, 3000);setTimeout(() => {  console.log("这是第三条消息");}, 1000);// 输出:// 这是第三条消息// 这是第二条消息// 这是第一条消息

请注意,第一个函数在调用第二个函数之前没有建立一个 5 秒钟的“暂停”。相反,第一个函数被调用,但等待 5 秒后执行。当第一个函数等待执行时,第二个函数被调用,在第二个函数执行之前,对其进行了 3 秒的等待。由于第一个和第二个函数的定时器都没有完成,第三个函数被调用并首先完成其执行。第二个函数紧随其后。在第一个函数的定时器最终完成后,最后执行该函数。

要创建一个进程,其中一个函数只有在另一个函数完成后才会触发,请参见关于Promise 的文档。

关于“this”的问题

当你向setTimeout() 传递一个函数时,该函数中的this 指向跟你的期望可能不同,这个问题在JavaScript 参考中进行了详细解释。

setTimeout() 执行的代码是从一个独立于调用setTimeout 的函数的执行环境中调用的。为被调用的函数设置this 关键字的通常规则适用,如果你没有在调用中或用bind 设置this,它将默认为window(或global)对象(甚至是在严格模式中)。它将与调用setTimeout 的函数的this 值不一样。

请看以下示例:

js
const myArray = ["零", "一", "二"];myArray.myMethod = function (sProperty) {  console.log(arguments.length > 0 ? this[sProperty] : this);};myArray.myMethod(); // 输出“零,一,二”myArray.myMethod(1); // 输出“一”

上面这段代码正常工作,当调用myArray 时,它的this 设定为myArray,故在函数中this[sProperty]myArray[sProperty] 等价。然而,在以下示例中:

js
setTimeout(myArray.myMethod, 1.0 * 1000); // 在 1 秒后输出“[object Window]”setTimeout(myArray.myMethod, 1.5 * 1000, "1"); // 在 1.5 秒后输出“undefined”

传递给setTimeout 的是myArray.myMethod 函数,当调用它的时候,this 没有指向,故其默认指向window 对象。

setTimeout 中也没有传递thisArg 的选项,就像在forEach()reduce() 等数组方法中一样。如下方示例所示,使用call 来设置this 也不起作用。

js
setTimeout.call(myArray, myArray.myMethod, 2.0 * 1000); // 出错setTimeout.call(myArray, myArray.myMethod, 2.5 * 1000, 2); // 同样会出错

解决方案

使用包装函数

一个通用的方法是用包装函数来将this 设置为所需要的值:

js
setTimeout(function () {  myArray.myMethod();}, 2.0 * 1000); // 在 2 秒后输出“零,一,二”setTimeout(function () {  myArray.myMethod("1");}, 2.5 * 1000); // 在 2.5 秒后输出“一”

包装函数也可以是箭头函数:

js
setTimeout(() => {  myArray.myMethod();}, 2.0 * 1000); // 在 2 秒后输出“零,一,二”setTimeout(() => {  myArray.myMethod("1");}, 2.5 * 1000); // 在 2.5 秒后输出“一”
使用 bind()

或者,也可以使用bind() 来为所有对特定函数的调用设置this 的值:

js
const myArray = ["零", "一", "二"];const myBoundMethod = function (sProperty) {  console.log(arguments.length > 0 ? this[sProperty] : this);}.bind(myArray);myBoundMethod(); // 输出“零,一,二”。因为“this”在函数中绑定到了 myArraymyBoundMethod(1); // 输出“一”setTimeout(myBoundMethod, 1.0 * 1000); // 由于绑定问题,还是在 1 秒后输出“零,一,二”setTimeout(myBoundMethod, 1.5 * 1000, "1"); // 在 1.5 秒后输出“一”

传递字符串字面量

将字符串而不是函数传递给setTimeout() 与使用eval() 具有相同的问题。

js
// 不要这样做setTimeout("console.log('你好世界!');", 500);
js
// 这样做setTimeout(() => {  console.log("你好世界!");}, 500);

传递给setTimeout() 的字符串是在全局上下文中求值的,因此当字符串被求值为代码时,setTimeout() 被调用的上下文中的局部符号将不可用。

延时比指定值更长的原因

有很多因素会导致超时比设定的预期值更久,本节将讨论最常见的原因。

嵌套超时

正如HTML 标准中规定的那样,一旦对setTimeout 的嵌套调用被安排了 5 次,浏览器将强制执行 4 毫秒的最小超时。

这可以在下面的例子中看到,在这个例子中,我们嵌套了对setTimeout 的调用,延迟为0 毫秒,并记录每次调用处理程序时的延迟。前四次,延迟约为 0 毫秒,之后约为 4 毫秒:

html
<button>运行</button><table>  <thead>    <tr>      <th>之前</th>      <th>现在</th>      <th>实际延时</th>    </tr>  </thead>  <tbody></tbody></table>
js
let last = 0;let iterations = 10;function timeout() {  // 记录调用时间  logline(new Date().getMilliseconds());  // 如果还没结束,计划下次调用  if (iterations-- > 0) {    setTimeout(timeout, 0);  }}function run() {  // 清除日志  const log = document.querySelector("#log");  while (log.lastElementChild) {    log.removeChild(log.lastElementChild);  }  // 初始化迭代次数和开始时间戳  iterations = 10;  last = new Date().getMilliseconds();  // 开启定时器  setTimeout(timeout, 0);}function logline(now) {  // 输出上一个时间戳、新的时间戳及差值  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);
* {  font-family: monospace;}th,td {  padding: 0 10px 0 10px;  text-align: center;  border: 1px solid;}table {  border-collapse: collapse;  margin-top: 10px;}

非活动标签的超时

为了优化后台标签的加载损耗(以及降低耗电量),浏览器会在非活动标签中强制执行一个最小的超时延迟。如果一个页面正在使用 Web 音频 APIAudioContext 播放声音,也可以不执行该延迟。

这方面的具体情况与浏览器有关:

  • Firefox 桌面版和 Chrome 针对不活动标签都有一个 1 秒的最小超时值。
  • 安卓版 Firefox 浏览器对不活动的标签有一个至少 15 分钟的超时,并可能完全卸载它们。
  • 如果标签中包含AudioContext,Firefox 不会对非活动标签进行限流。

追踪型脚本的限流

Firefox 对它识别为追踪型脚本的脚本实施额外的限流。当在前台运行时,限流的最小延迟仍然是 4 毫秒。然而,在后台标签中,限流的最小延迟是 10000 毫秒(即 10 秒),在文档首次加载后 30 秒开始生效。

参见跟踪保护以了解更多信息。

超时延迟

如果页面(或操作系统/浏览器)正忙于其他任务,超时也可能比预期的晚。需要注意的一个重要情况是,在调用setTimeout() 的线程结束之前,函数或代码片段不能被执行。例如:

js
function foo() {  console.log("foo 被调用");}setTimeout(foo, 0);console.log("setTimeout 之后");

会在控制台输出:

setTimeout 之后foo 被调用

出现这个结果的原因是,尽管setTimeout 以零延迟来调用函数,但这个任务已经被放入了队列中并且等待下一次执行;并不是立即执行;队列中的等待函数被调用之前,当前代码必须全部运行完毕,因此这里运行结果并非预想的那样。

在加载页面时推迟超时

当前标签页正在加载时,Firefox 将推迟触发setTimeout() 计时器。直到主线程被认为是空闲的(类似于Window.requestIdleCallback()),或者直到加载事件触发完毕,才开始触发。

WebExtension 背景页面和定时器

WebExtension 中,setTimeout() 不会可靠工作。扩展开发者应当使用alarms API 作为替代。

最大延时值

浏览器内部以 32 位带符号整数存储延时。这就会导致如果一个延时大于 2147483647 毫秒(大约 24.8 天)时就会溢出,导致定时器将会被立即执行。

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

导致立刻执行超时(因为2**32 - 5000 溢出为负数),而以下代码:

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

导致大约在 5 秒后执行超时。

备注:这与 Node.js 中setTimeout 的行为不符,Node.js 中任何大于 2147483647 毫秒的超时都会立即执行。

示例

设定和清除超时

下面的例子在一个网页中设置了两个简单的按钮,并将它们与setTimeout()clearTimeout() 例程挂钩。按下第一个按钮将设置一个超时,在两秒后显示一条信息,并存储超时 ID 供clearTimeout() 使用。你可以选择按第二个按钮来取消这个超时。

HTML

html
<button>在两秒后显示一条消息</button><button>在显示前取消这条消息</button><div></div>

JavaScript

js
let timeoutID;function setOutput(outputContent) {  document.querySelector("#output").textContent = outputContent;}function delayedMessage() {  setOutput("");  timeoutID = setTimeout(setOutput, 2 * 1000, "太慢了!");}function clearMessage() {  clearTimeout(timeoutID);}
#output {  padding: 0.5rem 0;}

运行结果

参见clearTimeout() 的示例。

规范

Specification
HTML
# dom-settimeout-dev

浏览器兼容性

参见

Help improve MDN

Learn how to contribute

This page was last modified on byMDN contributors.


[8]ページ先頭

©2009-2026 Movatter.jp