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 ] ])

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

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

安排在 timeout 毫秒后编译并运行 code 的超时。

self.clearTimeout(id)

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

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

安排每隔 timeout 毫秒运行 handler 的超时。任何 arguments 都将直接传递给 handler

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

安排每隔 timeout 毫秒编译并运行 code 的超时。

self.clearInterval(id)

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

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

此 API 无法保证定时器将按计划准确运行。由于 CPU 负载、其他任务等原因造成的延迟是预料之中的。

要连续运行几个毫秒的任务而没有任何延迟,同时仍然向浏览器让出控制权以避免使用户界面饿死(并避免浏览器因占用 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

8.7 微任务队列

queueMicrotask

所有当前引擎均支持。

Firefox69+Safari12.1+Chrome71+
Opera?Edge79+
Edge (旧版)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
self.queueMicrotask(callback)

将一个 微任务 排队以运行给定的 callback

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)

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

result = window.confirm(message)

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

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

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

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

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()

提示用户打印页面。