跳转到内容

事件循环机制

来自代码酷

模板:Note

事件循环机制[编辑 | 编辑源代码]

事件循环机制(Event Loop)是JavaScript实现异步编程的核心模型,它决定了代码的执行顺序和任务的调度方式。理解事件循环对于编写高效、无阻塞的JavaScript代码至关重要。

概述[编辑 | 编辑源代码]

JavaScript是单线程语言,意味着它一次只能执行一个任务。为了处理异步操作(如定时器、网络请求、用户交互等),JavaScript使用事件循环来协调任务执行顺序。事件循环由以下关键部分组成:

  • 调用栈(Call Stack):存储同步任务的执行上下文
  • 任务队列(Task Queue):存储待处理的异步回调
  • 微任务队列(Microtask Queue):存储优先级更高的异步回调
  • 事件循环线程:负责监控调用栈和队列

工作原理[编辑 | 编辑源代码]

事件循环的基本流程可以用以下伪代码表示:

while (true) {
    // 1. 执行调用栈中的任务
    executeCallStack();
    
    // 2. 检查并执行所有微任务
    while (microtaskQueue.length > 0) {
        executeMicrotask();
    }
    
    // 3. 执行一个宏任务
    if (taskQueue.length > 0) {
        executeTask();
    }
    
    // 4. 必要时进行渲染更新
    if (needsRendering()) {
        updateRendering();
    }
}

执行顺序规则[编辑 | 编辑源代码]

1. 同步代码优先执行(调用栈) 2. 当调用栈清空后,检查并执行所有微任务(如Promise回调) 3. 执行一个宏任务(如setTimeout回调) 4. 重复该循环

graph TD A[同步代码] --> B[调用栈空?] B -->|是| C[执行所有微任务] C --> D[执行一个宏任务] D --> B B -->|否| E[继续执行同步代码]

代码示例[编辑 | 编辑源代码]

基础示例[编辑 | 编辑源代码]

console.log('1. 同步代码开始');

setTimeout(() => {
    console.log('4. 宏任务执行');
}, 0);

Promise.resolve().then(() => {
    console.log('3. 微任务执行');
});

console.log('2. 同步代码结束');

输出结果:

1. 同步代码开始
2. 同步代码结束
3. 微任务执行
4. 宏任务执行

复杂示例[编辑 | 编辑源代码]

console.log('脚本开始');

setTimeout(() => {
    console.log('setTimeout1');
    Promise.resolve().then(() => console.log('Promise in setTimeout1'));
}, 0);

setTimeout(() => {
    console.log('setTimeout2');
}, 0);

Promise.resolve().then(() => {
    console.log('Promise1');
    return Promise.resolve();
}).then(() => {
    console.log('Promise2');
});

console.log('脚本结束');

可能的输出结果:

脚本开始
脚本结束
Promise1
Promise2
setTimeout1
Promise in setTimeout1
setTimeout2

任务类型分类[编辑 | 编辑源代码]

宏任务(Macrotasks)[编辑 | 编辑源代码]

  • setTimeout/setInterval
  • I/O操作
  • UI渲染
  • setImmediate(Node.js)
  • requestAnimationFrame(浏览器)

微任务(Microtasks)[编辑 | 编辑源代码]

  • Promise.then/catch/finally
  • MutationObserver
  • process.nextTick(Node.js)
  • queueMicrotask

浏览器与Node.js差异[编辑 | 编辑源代码]

环境 特点
浏览器 每帧执行一次事件循环,包含渲染步骤
Node.js 使用libuv实现,有多个阶段(timers、poll、check等)

实际应用场景[编辑 | 编辑源代码]

性能优化[编辑 | 编辑源代码]

将耗时任务分解为多个微任务,避免阻塞UI:

function processLargeData(data) {
    // 分批处理大数据
    const chunkSize = 1000;
    let i = 0;
    
    function processChunk() {
        const chunk = data.slice(i, i + chunkSize);
        // 处理数据块...
        
        i += chunkSize;
        if (i < data.length) {
            // 使用微任务继续处理
            Promise.resolve().then(processChunk);
        }
    }
    
    processChunk();
}

动画调度[编辑 | 编辑源代码]

结合requestAnimationFrame和事件循环实现流畅动画:

function animate() {
    // 动画逻辑...
    requestAnimationFrame(animate);
}
animate();

常见问题[编辑 | 编辑源代码]

为什么Promise比setTimeout先执行?[编辑 | 编辑源代码]

因为Promise回调属于微任务,而setTimeout回调属于宏任务。事件循环总是先清空微任务队列才会处理宏任务。

如何避免回调地狱?[编辑 | 编辑源代码]

使用Promise/async-await可以更清晰地表达异步流程:

async function fetchData() {
    try {
        const res1 = await fetch('/api/1');
        const data1 = await res1.json();
        
        const res2 = await fetch(`/api/2?id=${data1.id}`);
        return await res2.json();
    } catch (error) {
        console.error('请求失败:', error);
    }
}

数学表示[编辑 | 编辑源代码]

事件循环的执行顺序可以用以下公式表示: ExecutionOrder=Sync同步代码Microtasks所有微任务1Macrotask一个宏任务循环

总结[编辑 | 编辑源代码]

  • JavaScript通过事件循环实现异步执行
  • 任务分为同步任务、微任务和宏任务
  • 执行顺序:同步 → 所有微任务 → 一个宏任务 → 循环
  • 合理利用事件循环机制可以优化性能,避免UI阻塞

模板:Tip