Skip to content

事件循环

事件循环简介

事件循环(Event Loop)是JavaScript实现异步编程的核心机制,负责协调执行栈、任务队列和微任务队列的执行。

JavaScript执行环境

1. 执行栈

执行栈是JavaScript代码执行的地方,采用后进先出(LIFO)的数据结构。

javascript
console.log('1');

function foo() {
  console.log('2');
}

function bar() {
  console.log('3');
  foo();
}

bar();

console.log('4');

// 输出顺序: 1, 3, 2, 4

2. 任务队列

任务队列分为宏任务队列和微任务队列。

宏任务(Macro Task)

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

// setInterval
setInterval(() => {
  console.log('setInterval');
}, 1000);

// I/O
fs.readFile('file.txt', (err, data) => {
  console.log('I/O');
});

// UI渲染
requestAnimationFrame(() => {
  console.log('requestAnimationFrame');
});

微任务(Micro Task)

javascript
// Promise.then()
Promise.resolve().then(() => {
  console.log('Promise.then');
});

// MutationObserver
const observer = new MutationObserver(() => {
  console.log('MutationObserver');
});

observer.observe(document.body, { childList: true });

// queueMicrotask
queueMicrotask(() => {
  console.log('queueMicrotask');
});

事件循环流程

完整流程

1. 执行同步代码(执行栈)
2. 执行所有微任务
3. 执行一个宏任务
4. 执行所有微任务
5. 重复3-4步骤

示例

javascript
console.log('1');

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

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');

// 输出顺序: 1, 4, 3, 2

详细执行过程

javascript
console.log('1');  // 1. 同步代码

setTimeout(() => {
  console.log('2');  // 3. 宏任务
}, 0);

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

console.log('4');  // 1. 同步代码

// 执行顺序:
// 1. 执行同步代码: 输出 1, 4
// 2. 执行微任务: 输出 3
// 3. 执行宏任务: 输出 2

复杂示例

示例1

javascript
console.log('1');

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

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

console.log('6');

// 输出顺序: 1, 6, 4, 2, 3, 5

示例2

javascript
console.log('1');

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

Promise.resolve().then(() => {
  console.log('3');
  Promise.resolve().then(() => {
    console.log('4');
  });
});

Promise.resolve().then(() => {
  console.log('5');
});

console.log('6');

// 输出顺序: 1, 6, 3, 5, 4, 2

示例3

javascript
console.log('1');

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

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

console.log('5');

// 输出顺序: 1, 5, 2, 4, 3

async/await

async/await与事件循环

javascript
async function foo() {
  console.log('1');
  await Promise.resolve();
  console.log('2');
}

async function bar() {
  console.log('3');
  await Promise.resolve();
  console.log('4');
}

console.log('5');

foo();
bar();

console.log('6');

// 输出顺序: 5, 1, 3, 6, 2, 4

async/await原理

javascript
async function foo() {
  console.log('1');
  await Promise.resolve();
  console.log('2');
}

// 等价于
function foo() {
  console.log('1');
  return Promise.resolve().then(() => {
    console.log('2');
  });
}

Promise

Promise与事件循环

javascript
console.log('1');

Promise.resolve().then(() => {
  console.log('2');
  Promise.resolve().then(() => {
    console.log('3');
  });
});

Promise.resolve().then(() => {
  console.log('4');
});

console.log('5');

// 输出顺序: 1, 5, 2, 4, 3

Promise链式调用

javascript
Promise.resolve()
  .then(() => {
    console.log('1');
    return Promise.resolve();
  })
  .then(() => {
    console.log('2');
  });

// 输出顺序: 1, 2

requestAnimationFrame

requestAnimationFrame与事件循环

javascript
console.log('1');

requestAnimationFrame(() => {
  console.log('2');
});

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

console.log('4');

// 输出顺序: 1, 4, 3, 2

requestAnimationFrame特点

  • 在浏览器重绘之前执行
  • 与屏幕刷新率同步(通常60fps)
  • 适合动画效果
javascript
function animate() {
  requestAnimationFrame(animate);
  console.log('动画帧');
}

animate();

Node.js事件循环

Node.js事件循环阶段

1. Timers: setTimeout、setInterval
2. Pending callbacks: I/O回调
3. Idle, prepare: 内部使用
4. Poll: 获取新的I/O事件
5. Check: setImmediate
6. Close callbacks: 关闭回调

Node.js示例

javascript
console.log('1');

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

setImmediate(() => {
  console.log('3');
});

console.log('4');

// 输出顺序: 1, 4, 2, 3 或 1, 4, 3, 2

Node.js与浏览器差异

javascript
// 浏览器
console.log('1');

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

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');

// 输出顺序: 1, 4, 3, 2

// Node.js
console.log('1');

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

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');

// 输出顺序: 1, 4, 3, 2

面试常见问题

1. 什么是事件循环?

事件循环是JavaScript实现异步编程的核心机制,负责协调执行栈、任务队列和微任务队列的执行。

2. 宏任务和微任务的区别?

  • 宏任务: setTimeout、setInterval、I/O、UI渲染
  • 微任务: Promise.then、MutationObserver、queueMicrotask

3. 事件循环的执行顺序?

1. 执行同步代码(执行栈)
2. 执行所有微任务
3. 执行一个宏任务
4. 执行所有微任务
5. 重复3-4步骤

4. async/await的执行顺序?

javascript
async function foo() {
  console.log('1');
  await Promise.resolve();
  console.log('2');
}

console.log('3');
foo();
console.log('4');

// 输出顺序: 3, 1, 4, 2

5. requestAnimationFrame的特点?

  • 在浏览器重绘之前执行
  • 与屏幕刷新率同步(通常60fps)
  • 适合动画效果

通过理解事件循环的工作原理,可以更好地理解JavaScript的异步编程和性能优化。

好好学习,天天向上