生命不是安排,而是追求。——弗吉尼亚·伍尔芙

从线程和浏览器底层执行的角度来看,JavaScript 的延迟/休眠功能是如何实现的呢?

线程模型和事件循环

JavaScript 的执行环境(如浏览器或 Node.js)是单线程的,这意味着在任何给定的时间点,只有一个线程在执行 JavaScript 代码。为了管理并发操作,JavaScript 依赖于事件循环(Event Loop)。

事件循环

事件循环是一个不断检查和处理消息队列的机制。它会执行以下步骤:

  1. 检查调用栈(Call Stack),如果调用栈为空,则继续。
  2. 检查消息队列(Message Queue),如果消息队列中有待处理的任务,则取出队首的任务并执行。
  3. 重复以上步骤。

宏任务和微任务

在事件循环中,有两种类型的任务:宏任务(Macro Task)和微任务(Micro Task)。

  • 宏任务:包括整体脚本执行、setTimeoutsetIntervalsetImmediate(Node.js)等。
  • 微任务:包括 Promise 的回调、process.nextTick(Node.js)、MutationObserver 等。

事件循环会优先处理微任务队列中的任务,然后再处理宏任务队列中的任务。

延迟/休眠的实现

JavaScript 没有直接的 sleep 函数,但可以通过 setTimeoutPromise 来实现延迟/休眠功能。

setTimeout

setTimeout 是一种宏任务,会在指定的时间后将回调函数添加到事件队列中。

1
2
3
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

上面的 sleep 函数返回一个 Promise,该 Promise 在指定的毫秒数之后解决(resolve)。

async/await

结合 async/await,可以在异步函数中实现类似同步代码的延迟效果:

1
2
3
4
5
6
7
async function demo() {
console.log('Start');
await sleep(2000); // 延迟 2 秒
console.log('End');
}

demo();

在这个例子中,demo 函数会先打印 “Start”,然后等待 2 秒,最后打印 “End”。

浏览器底层执行

在浏览器中,JavaScript 引擎(如 V8 引擎)和浏览器内核(如 Chromium 内核)协同工作来处理异步操作。

任务调度

当调用 setTimeout 时,浏览器内核会将此任务调度到定时器队列中。当定时器到期时,回调函数会被添加到事件队列中等待执行。

事件循环与渲染

浏览器的事件循环还包括了渲染步骤。在每个事件循环迭代中,浏览器会在处理任务之前进行渲染更新。这意味着即使在延迟期间,浏览器也会继续绘制和更新用户界面。

结论

通过了解 JavaScript 的线程模型和事件循环机制,我们可以更好地理解如何实现延迟/休眠功能。利用 setTimeoutPromise,可以在不阻塞主线程的情况下实现延迟执行,从而保证用户界面依然响应迅速。