1. 8.6 定时器
    2. 8.7 微任务队列
    3. 8.8 用户提示
      1. 8.8.1 简单对话框
      2. 8.8.2 打印

8.6 定时器

setTimeout()setInterval() 方法允许作者安排基于定时器的回调。

id = self.setTimeout(handler [, timeout [, ...arguments ] ])

setTimeout

所有当前引擎都支持。

Firefox1+Safari1+Chrome1+
Opera4+Edge79+
Edge (旧版)12+Internet Explorer4+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android10.1+

安排一个超时,在 timeout 毫秒后运行 handler。任何 arguments 都直接传递给 handler

id = self.setTimeout(code [, timeout ])

安排一个超时,在 timeout 毫秒后编译并运行 code

self.clearTimeout(id)

clearTimeout

所有当前引擎都支持。

Firefox1+Safari4+Chrome1+
Opera4+Edge79+
Edge (旧版)12+Internet Explorer4+
Firefox Android?Safari iOS1+Chrome Android?WebView Android37+Samsung Internet?Opera Android10.1+

取消由 setTimeout()setInterval() 设置的,由 id 标识的超时。

id = self.setInterval(handler [, timeout [, ...arguments ] ])

setInterval

所有当前引擎都支持。

Firefox1+Safari1+Chrome1+
Opera4+Edge79+
Edge (旧版)12+Internet Explorer4+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android10.1+

安排一个超时,每隔 timeout 毫秒运行 handler 一次。任何 arguments 都直接传递给 handler

id = self.setInterval(code [, timeout ])

安排一个超时,每隔 timeout 毫秒编译并运行 code 一次。

self.clearInterval(id)

clearInterval

所有当前引擎都支持。

Firefox1+Safari1+Chrome1+
Opera4+Edge79+
Edge (旧版)12+Internet Explorer4+
Firefox Android?Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android10.1+

取消由 setInterval()setTimeout() 设置的,由 id 标识的超时。

定时器可以嵌套;但是,在五个这样的嵌套定时器之后,间隔将被强制为至少四毫秒。

此 API 不保证定时器会完全按计划运行。由于 CPU 负载、其他任务等原因造成的延迟是预期的。

实现 WindowOrWorkerGlobalScope 混合类的对象具有一个 setTimeout 和 setInterval ID 的映射,它是一个 有序映射,最初为空。此映射中的每个 都是一个正整数,对应于 setTimeout()setInterval() 调用的返回值。每个 都是一个 唯一的内部值,对应于对象 活动定时器映射 中的一个键。


setTimeout(handler, timeout, ...arguments) 方法的步骤是返回给定 thishandlertimeoutarguments 和 false 的 定时器初始化步骤 的运行结果。

setInterval(handler, timeout, ...arguments) 方法的步骤是返回给定 thishandlertimeoutarguments 和 true 的 定时器初始化步骤 的运行结果。

clearTimeout(id)clearInterval(id) 方法的步骤是 移除 thissetTimeout 和 setInterval ID 的映射[id]

因为 clearTimeout()clearInterval() 清除同一映射中的条目,所以任何一种方法都可以用来清除由 setTimeout()setInterval() 创建的定时器。


为了执行 定时器初始化步骤,给定一个 WindowOrWorkerGlobalScope global、一个字符串或 FunctionTrustedScript handler、一个数字 timeout、一个列表 arguments、一个布尔值 repeat,以及可选地(并且仅当 repeat 为 true 时)一个数字 previousId,执行以下步骤。它们返回一个数字。

  1. 如果 global 是一个 WorkerGlobalScope 对象,则令 thisArgglobal;否则,令 thisArg 为与 global 对应的 WindowProxy

  2. 如果给出了 previousId,则令 idpreviousId;否则,令 id 为一个 实现定义的 整数,该整数大于零,并且在 globalsetTimeout 和 setInterval ID 的映射 中尚不存在。

  3. 如果 周围代理事件循环当前正在运行的任务 是由此算法创建的任务,则令 嵌套级别任务定时器嵌套级别。否则,令 嵌套级别 为零。

    任务的 定时器嵌套级别 用于对 setTimeout() 的嵌套调用以及由 setInterval() 创建的重复定时器。(或者,实际上,对于这两者的任何组合。)换句话说,它表示此算法的嵌套调用,而不是特定方法的嵌套调用。

  4. 如果 timeout 小于 0,则将 timeout 设置为 0。

  5. 如果 nesting level 大于 5,并且 timeout 小于 4,则将 timeout 设置为 4。

  6. realmglobal相关领域

  7. 发起脚本活动脚本

  8. uniqueHandle 为 null。

  9. task 为一个 任务,它运行以下子步骤

    1. 断言uniqueHandle 是一个 唯一的内部值,而不是 null。

    2. 如果 idglobalsetTimeout 和 setInterval ID 的映射 中不存在,则中止这些步骤。

    3. 如果 globalsetTimeout 和 setInterval ID 的映射[id] 不等于 uniqueHandle,则中止这些步骤。

      这适应了 ID 被 clearTimeout()clearInterval() 调用清除,并被随后的 setTimeout()setInterval() 调用重复使用的情况。

    4. 记录定时器处理程序的时间信息,给定 handlerglobal相关设置对象repeat

    5. 如果 handler 是一个 Function,则 调用 handler,给定 arguments 和 "report",并且 回调 this 值 设置为 thisArg

    6. 否则

      1. 如果未给出 previousId

        1. 如果 this相关全局对象 是一个 Window 对象,则令 globalName 为 "Window";否则为 "Worker"。

        2. 如果 repeat 为 true,则令 methodName 为 "setInterval";否则为 "setTimeout"。

        3. sinkglobalName、U+0020 空格和 methodName 的连接。

        4. handler 设置为使用 获取受信任类型兼容字符串 算法以及 TrustedScriptthis相关全局对象handlersink 和 "script" 调用的结果。

      2. 断言handler 是一个字符串。

      3. 执行 EnsureCSPDoesNotBlockStringCompilation(realm, « », handlerhandler,timer, « », handler)。如果此操作抛出异常,则捕获该异常,报告global,并中止这些步骤。

      4. settings objectglobal相关设置对象

      5. fetch options默认脚本获取选项

      6. base URLsettings objectAPI 基础 URL

      7. 如果 initiating script 不为 null,则

        1. fetch options 设置为 脚本获取选项,其 加密 nonceinitiating script获取选项加密 nonce完整性元数据 为空字符串,解析器元数据 为 "not-parser-inserted",凭据模式initiating script获取选项凭据模式referrer 策略initiating script获取选项referrer 策略,以及 获取优先级 为 "auto"。

        2. base URL 设置为 initiating script基础 URL

        这些步骤的效果确保了由 setTimeout()setInterval() 执行的字符串编译行为与由 eval() 执行的字符串编译行为等效。也就是说,模块脚本 通过 import() 进行获取在两种上下文中都将表现相同。

      8. script 为给定 handlersettings objectbase URLfetch options创建经典脚本 的结果。

      9. 运行经典脚本 script

    7. 如果 idglobalsetTimeout 和 setInterval ID 映射不存在,则中止这些步骤。

    8. 如果 globalsetTimeout 和 setInterval ID 映射[id] 不等于 uniqueHandle,则中止这些步骤。

      该 ID 可能已通过 handler 中作者代码调用 clearTimeout()clearInterval() 而被移除。检查 uniqueHandle 是否不同,是为了考虑 ID 在被清除后,可能会被随后的 setTimeout()setInterval() 调用重用的可能性。

    9. 如果 repeat 为 true,则再次执行 定时器初始化步骤,给定 globalhandlertimeoutarguments、true 和 id

    10. 否则,移除 globalsetTimeout 和 setInterval ID 映射[id]。

  10. nesting level 加 1。

  11. task定时器嵌套级别 设置为 nesting level

  12. completionStep 为一个算法步骤,该步骤 在给定的 global 上的 定时器任务源 上排队一个全局任务 以运行 task

  13. uniqueHandle 设置为给定 global、"setTimeout/setInterval"、timeoutcompletionStep运行超时后的步骤 的结果。

  14. 设置 globalsetTimeout 和 setInterval ID 映射[id] 为 uniqueHandle

  15. 返回 id

Web IDL 中定义了参数转换(例如,对作为第一个参数传递的对象调用 toString() 方法),在调用此算法之前发生。

例如,以下相当愚蠢的代码将导致日志包含 "ONE TWO "

var log = '';
function logger(s) { log += s + ' '; }

setTimeout({ toString: function () {
  setTimeout("logger('ONE')", 100);
  return "logger('TWO')";
} }, 100);

要连续运行多个毫秒的任务而没有任何延迟,同时仍然让浏览器恢复控制以避免饿死用户界面(并避免浏览器因占用 CPU 而终止脚本),只需在执行工作之前排队下一个定时器即可。

function doExpensiveWork() {
  var done = false;
  // ...
  // this part of the function takes up to five milliseconds
  // set done to true if we're done
  // ...
  return done;
}

function rescheduleWork() {
  var id = setTimeout(rescheduleWork, 0); // preschedule next iteration
  if (doExpensiveWork())
    clearTimeout(id); // clear the timeout if we don't need it
}

function scheduleWork() {
  setTimeout(rescheduleWork, 0);
}

scheduleWork(); // queues a task to do lots of work

实现 WindowOrWorkerGlobalScope 混合体的对象具有一个 活动定时器映射,它是一个 有序映射,最初为空。此映射中的每个 都是一个 表示定时器的唯一内部值,每个 都是一个 DOMHighResTimeStamp,表示该定时器的过期时间。

运行超时后的步骤,给定一个 WindowOrWorkerGlobalScope global、一个字符串 orderingIdentifier、一个数字 milliseconds 和一组步骤 completionSteps,执行以下步骤。它们返回一个 唯一内部值

  1. timerKey 为一个新的 唯一内部值

  2. startTime 为给定 global当前高分辨率时间

  3. 设置 global活动定时器映射[timerKey] 为 startTime 加上 milliseconds

  4. 并行 运行以下步骤

    1. 如果 global 是一个 Window 对象,则等待 global关联的 Document 进一步处于 完全活动状态 milliseconds 毫秒(不一定连续)。

      否则,global 是一个 WorkerGlobalScope 对象;等待 milliseconds 毫秒过去,并且工作线程未暂停(不一定连续)。

    2. 等待任何使用相同 globalorderingIdentifier、在此之前启动且 milliseconds 小于或等于此算法的 milliseconds 的此算法调用完成。

    3. 可选地,等待进一步 实现定义的 时间长度。

      这旨在允许用户代理根据需要填充超时,以优化设备的功耗。例如,某些处理器具有低功耗模式,其中定时器的粒度降低;在这些平台上,用户代理可以减慢定时器速度以适应此计划,而不是要求处理器使用其相关的更高功耗的更精确模式。

    4. 执行 completionSteps

    5. 移除 global活动定时器映射[timerKey]。

  5. 返回 timerKey

运行超时后的步骤 旨在供其他规范使用,这些规范希望以类似于 setTimeout() 的方式在开发者提供的超时后执行开发者提供的代码。(但是,请注意,它没有 setTimeout() 的嵌套和钳位行为。)此类规范可以选择一个 orderingIdentifier 以确保其规范的超时内的顺序,同时不限制相对于其他规范的超时的顺序。

8.7 微任务排队

queueMicrotask

所有当前引擎都支持。

Firefox69+Safari12.1+Chrome71+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
self.queueMicrotask(callback)

给定 callback微任务 排队

queueMicrotask(callback) 方法必须 排队一个微任务调用 callback,并传入 « » 和 "report"。

queueMicrotask() 方法允许作者在 微任务队列 上安排回调。这允许他们的代码在 JavaScript 执行上下文栈 下次为空时运行,这发生在所有当前正在执行的同步 JavaScript 运行完成之后。这不会将控制权交还给 事件循环,例如,使用 setTimeout(f, 0) 时的情况。

作者应该意识到,安排大量微任务与运行大量同步代码具有相同的性能缺点。两者都会阻止浏览器执行自己的工作,例如渲染。在许多情况下,requestAnimationFrame()requestIdleCallback() 是更好的选择。特别是,如果目标是在下一个渲染周期之前运行代码,那么这就是 requestAnimationFrame() 的用途。

从以下示例可以看出,理解 queueMicrotask() 的最佳方式是将其视为一种重新排列同步代码的机制,有效地将排队的代码放置在当前正在执行的同步 JavaScript 运行完成之后。

使用queueMicrotask()最常见的原因是创建一致的排序,即使在同步可用信息的情况下,也不会引入不必要的延迟。

例如,考虑一个自定义元素触发load事件,该事件还维护一个先前加载数据的内部缓存。一个简单的实现可能如下所示

MyElement.prototype.loadData = function (url) {
  if (this._cache[url]) {
    this._setData(this._cache[url]);
    this.dispatchEvent(new Event("load"));
  } else {
    fetch(url).then(res => res.arrayBuffer()).then(data => {
      this._cache[url] = data;
      this._setData(data);
      this.dispatchEvent(new Event("load"));
    });
  }
};

然而,这个简单的实现存在问题,因为它会导致用户体验不一致的行为。例如,如下代码

element.addEventListener("load", () => console.log("loaded"));
console.log("1");
element.loadData();
console.log("2");

有时会记录“1, 2, loaded”(如果需要获取数据),有时会记录“1, loaded, 2”(如果数据已缓存)。类似地,在调用loadData()之后,元素上是否设置数据将是不一致的。

为了获得一致的排序,可以使用queueMicrotask()

MyElement.prototype.loadData = function (url) {
  if (this._cache[url]) {
    queueMicrotask(() => {
      this._setData(this._cache[url]);
      this.dispatchEvent(new Event("load"));
    });
  } else {
    fetch(url).then(res => res.arrayBuffer()).then(data => {
      this._cache[url] = data;
      this._setData(data);
      this.dispatchEvent(new Event("load"));
    });
  }
};

通过实质上重新安排排队的代码,使其位于JavaScript执行上下文栈清空之后,这确保了元素状态的一致排序和更新。

另一个有趣的queueMicrotask()用法是允许多个调用者进行不协调的“批量”处理。例如,考虑一个库函数,它希望尽快将数据发送到某个地方,但如果很容易避免,则不想发出多个网络请求。平衡这一点的一种方法如下所示

const queuedToSend = [];

function sendData(data) {
  queuedToSend.push(data);

  if (queuedToSend.length === 1) {
    queueMicrotask(() => {
      const stringToSend = JSON.stringify(queuedToSend);
      queuedToSend.length = 0;

      fetch("/endpoint", stringToSend);
    });
  }
}

通过这种架构,在当前正在执行的同步JavaScript中对sendData()的多次后续调用将被批量组合成一个fetch()调用,但没有中间的事件循环任务抢占获取操作(就像使用setTimeout()的类似代码那样会发生的情况)。

8.8 用户提示

8.8.1 简单对话框

window.alert(message)

Window/alert

所有当前引擎都支持。

Firefox1+Safari1+Chrome1+
Opera3+Edge79+
Edge (旧版)12+Internet Explorer4+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android10.1+

显示带有给定消息的模态警报,并等待用户将其关闭。

result = window.confirm(message)

Window/confirm

所有当前引擎都支持。

Firefox1+Safari1+Chrome1+
Opera3+Edge79+
Edge (旧版)12+Internet Explorer4+
Firefox Android?Safari iOS?Chrome Android?WebView Android1+Samsung Internet?Opera Android10.1+

显示带有给定消息的模态“确定/取消”提示,等待用户将其关闭,如果用户点击“确定”则返回true,如果用户点击“取消”则返回false。

result = window.prompt(message [, default])

Window/prompt

所有当前引擎都支持。

Firefox1+Safari1+Chrome1+
Opera3+Edge79+
Edge (旧版)12+Internet Explorer4+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android10.1+

显示带有给定消息的模态文本控件提示,等待用户将其关闭,并返回用户输入的值。如果用户取消提示,则返回null。如果存在第二个参数,则使用给定值作为默认值。

依赖于任务微任务的逻辑,例如媒体元素加载其媒体数据,在调用这些方法时会暂停。

The alert()alert(message) 方法步骤如下

  1. 如果我们无法显示简单对话框,则返回。

  2. 如果方法未带参数调用,则令message为空字符串;否则,令message为方法的第一个参数。

  3. message设置为给定message的结果规范化换行符

  4. message设置为可选截断message的结果。

  5. message显示给用户,将U+000A LF视为换行符。

  6. 使用this、"alert"和message调用WebDriver BiDi用户提示已打开

  7. 可选地,在等待用户确认消息时暂停

  8. 使用this和true调用WebDriver BiDi用户提示已关闭

出于历史原因,此方法使用两个重载来定义,而不是使用可选参数。这带来的实际影响是alert(undefined)被视为alert("undefined"),但alert()被视为alert("")

The confirm(message) 方法步骤如下

  1. 如果我们无法显示简单对话框,则返回false。

  2. message设置为给定message的结果规范化换行符

  3. message设置为可选截断message的结果。

  4. message显示给用户,将U+000A LF视为换行符,并要求用户以肯定或否定响应。

  5. 使用this、"confirm"和message调用WebDriver BiDi用户提示已打开

  6. 直到用户肯定或否定响应时暂停

  7. 使用this,如果用户肯定响应则为true,否则为false,调用WebDriver BiDi用户提示已关闭

  8. 如果用户肯定响应,则返回true;否则,用户否定响应:返回false。

The prompt(message, default) 方法步骤如下

  1. 如果我们无法显示简单对话框,则返回null。

  2. message设置为给定message的结果规范化换行符

  3. message设置为可选截断message的结果。

  4. default设置为可选截断default的结果。

  5. message显示给用户,将U+000A LF视为换行符,并要求用户以字符串值或中止响应。响应必须默认为default给出的值。

  6. 使用this、"prompt"、messagedefault调用WebDriver BiDi用户提示已打开

  7. 在等待用户响应时暂停

  8. 如果用户中止,则令result为null,否则为用户响应的字符串。

  9. 使用this,如果result为null则为false,否则为true,以及result调用WebDriver BiDi用户提示已关闭

  10. 返回result

可选截断简单对话框字符串s,则返回s本身或从s派生的某个较短的字符串。用户代理不应提供用于显示s省略部分的UI,因为这使得滥用者很容易创建“重要安全警报!点击“显示更多”以查看完整详细信息!”形式的对话框。

例如,用户代理可能只想显示消息的前100个字符。或者,用户代理可能会用“…”替换字符串的中间部分。这些类型的修改对于限制不自然地大、看起来可信的系统对话框的滥用潜力很有用。

当以下算法返回true时,我们无法显示简单对话框

  1. 如果window关联的Document活动沙箱标志集具有沙箱模态标志,则返回true。

  2. 如果window相关设置对象window相关设置对象顶级源不是相同源域,则返回true。

  3. 如果window相关代理事件循环终止嵌套级别不为零,则可选返回true。
  4. 可选返回true。(例如,用户代理可能会让用户选择忽略所有模态对话框,因此无论何时调用该方法都会在此步骤中止。)

  5. 返回false。

8.8.2 打印

Window/print

所有当前引擎都支持。

Firefox1+Safari1.1+Chrome1+
Opera6+Edge79+
Edge (旧版)12+Internet Explorer5+
Firefox Android114+Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android10.1+
window.print()

提示用户打印页面。

The print() 方法的步骤如下:

  1. documentthis关联的 Document

  2. 如果 document 不是 完全激活 的,则返回。

  3. 如果 document卸载计数器 大于 0,则返回。

  4. 如果 document准备好执行加载后任务,则对 document 运行 打印步骤

  5. 否则,设置 document加载时打印 标志。

用户代理还应在用户请求获得 物理形式(例如打印副本) 或物理形式的表示(例如 PDF 副本)的机会时运行 打印步骤

Document document打印步骤 如下:

  1. 用户代理可能会向用户显示消息或返回(或两者都执行)。

    例如,自助服务终端浏览器可以静默忽略对 print() 方法的任何调用。

    例如,移动设备上的浏览器可以检测到附近没有打印机,并在继续提供“保存为 PDF”选项之前显示一条消息。

  2. 如果 document活动沙箱标志集 设置了 沙箱模式对话框标志,则返回。

    如果打印对话框被 Document 的沙箱阻止,则不会触发 beforeprintafterprint 事件。

  3. 用户代理必须在 document相关全局对象 以及其中的任何 子可导航对象触发一个名为 beforeprint 的事件。

    仅在子元素中触发在这里似乎不正确,并且某些任务可能需要排队。请参阅 问题 #5096

    beforeprint 事件可用于注释打印副本,例如添加文档打印时间。

  4. 用户代理应为用户提供获得 物理形式(或物理形式的表示) 的机会。用户代理可能会等待用户接受或拒绝后再返回;如果这样做,用户代理必须在方法等待时 暂停。即使用户代理此时没有等待,如果并且当它最终创建备用表单时,用户代理也必须使用相关文档在此算法中的状态。

  5. 用户代理必须在 document相关全局对象 以及其中的任何 子可导航对象触发一个名为 afterprint 的事件。

    仅在子元素中触发在这里似乎不正确,并且某些任务可能需要排队。请参阅 问题 #5096

    afterprint 事件可用于恢复在较早事件中添加的注释,以及显示打印后 UI。例如,如果页面正在引导用户完成申请住房贷款的步骤,则脚本可以在打印表单或其他内容后自动进入下一步。