Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions js-advanced/eventloop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# JavaScript 事件循环

## 浏览器的事件循环

### 执行顺序

- 执行一个宏任务(如 setTimeout 回调)。
- 清空微任务队列(所有 Promise.then、queueMicrotask 等)。
- 渲染 UI(如果需要)。
- 重复步骤 1-3。

## Node.js 的事件循环

### 阶段划分(简化版)

- timers:执行 setTimeout 和 setInterval 的回调。
- I/O callbacks:处理网络、文件等 I/O 操作的回调。
- idle, prepare:内部使用。
- poll:用户级回调,检索新的 I/O 事件,执行 I/O 相关回调。
- check:执行 setImmediate 的回调。
- close callbacks:处理关闭事件(如 socket.on('close'))。

<!-- 怎么体现循环了? -->

### 微任务执行时机

- 每个阶段结束后,会清空微任务队列(包括 Promise.then 和 queueMicrotask)。
- process.nextTick() 会在当前阶段执行结束后立即执行,优先级高于其他微任务。
13 changes: 13 additions & 0 deletions js-advanced/eventloop/browser/browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
console.log('start');

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

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

console.log('end');
27 changes: 27 additions & 0 deletions js-advanced/eventloop/node/1.node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@


console.log('start');

setTimeout(() => {
console.log('setTimeout');
process.nextTick(() => console.log('nextTick in setTimeout'));
}, 0);

setImmediate(() => {
console.log('setImmediate');
process.nextTick(() => console.log('nextTick in setImmediate'));
});

process.nextTick(() => {
console.log('nextTick 1');
process.nextTick(() => console.log('nextTick 2'));
});

console.log('end');

// 输出顺序(可能有两种情况,取决于定时器和 setImmediate 的执行时机):
// 情况 1:
// start → end → nextTick 1 → nextTick 2 → setTimeout → nextTick in setTimeout → setImmediate → nextTick in setImmediate

// 情况 2(定时器延迟稍高时):
// start → end → nextTick 1 → nextTick 2 → setImmediate → nextTick in setImmediate → setTimeout → nextTick in setTimeout
26 changes: 26 additions & 0 deletions js-advanced/eventloop/node/2.fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const fs = require('fs');

// 1. timers 阶段执行 setTimeout
setTimeout(() => {
console.log('setTimeout');
process.nextTick(() => console.log('nextTick in setTimeout')); // 阶段结束后执行
Promise.resolve().then(() => console.log('Promise in setTimeout')); // 后于 nextTick
}, 0);

// 2. poll 阶段执行 I/O 回调
fs.readFile(__filename, () => {
console.log('fs.readFile');
process.nextTick(() => console.log('nextTick in fs.readFile')); // 阶段结束后执行
Promise.resolve().then(() => console.log('Promise in fs.readFile')); // 后于 nextTick
});

// 3. check 阶段执行 setImmediate
setImmediate(() => {
console.log('setImmediate');
process.nextTick(() => console.log('nextTick in setImmediate')); // 阶段结束后执行
Promise.resolve().then(() => console.log('Promise in setImmediate')); // 后于 nextTick
});

// 主线程代码(属于 poll 阶段前的初始化)
process.nextTick(() => console.log('nextTick 1')); // 主线程结束后立即执行
Promise.resolve().then(() => console.log('Promise 1')); // 后于 nextTick
22 changes: 22 additions & 0 deletions js-advanced/eventloop/node/3.io-pool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const net = require('net');
const fs = require('fs');

// 读取文件(异步操作)
fs.readFile(__filename, (err, data) => {
console.log('File read complete'); // 此回调在 poll 阶段执行
});

// 定时器和 setImmediate 的执行顺序受 poll 阶段影响
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));

// 连接一个不存在的服务器(触发 ECONNREFUSED 错误)
const socket = net.connect(18080, 'localhost', () => {
console.log('Connected');
});

socket.on('error', (err) => {
console.log('Error:', err.code); // 此回调在 I/O callbacks 阶段执行
});

// setTimeout -> error -> File read complete -> setImmediate
57 changes: 57 additions & 0 deletions js-advanced/eventloop/node/4.loop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const fs = require('fs');
const net = require('net');

console.log('=== 开始第一次事件循环 ===');

// 首次事件循环 - timers阶段
setTimeout(() => {
console.log('\n=== 第二次事件循环 - timers阶段 ===');
console.log('执行 setTimeout 回调');

// 微任务会在阶段结束后执行
process.nextTick(() => console.log('timers 阶段的 nextTick'));
Promise.resolve().then(() => console.log('timers 阶段的 Promise'));

// 安排一个在check阶段执行的任务
setImmediate(() => {
console.log('\n=== 第三次事件循环 - check阶段 ===');
console.log('执行 setImmediate 回调');
});

// 安排一个在下次timers阶段执行的任务
setTimeout(() => {
console.log('\n=== 第四次事件循环 - timers阶段 ===');
console.log('执行嵌套的 setTimeout 回调');
}, 0);
}, 0);

// 首次事件循环 - poll阶段(I/O操作)
fs.readFile(__filename, (err, data) => {
console.log('\n=== 第二次事件循环 - poll阶段(I/O回调) ===');
console.log('执行 fs.readFile 回调');

// 微任务会在阶段结束后执行
process.nextTick(() => console.log('poll 阶段的 nextTick'));
Promise.resolve().then(() => console.log('poll 阶段的 Promise'));
});

// 首次事件循环 - 模拟网络I/O
const server = net.createServer();
server.listen(18080, () => {
console.log('\n=== 第二次事件循环 - poll阶段(网络监听) ===');
console.log('服务器已启动,监听端口 8080');

// 关闭服务器,触发close callbacks阶段
server.close(() => {
console.log('\n=== 第二次事件循环 - close callbacks阶段 ===');
console.log('服务器已关闭');
});
});

// 首次事件循环 - 主线程结束后的微任务
process.nextTick(() => console.log('\n主线程的 nextTick'));
Promise.resolve().then(() => console.log('主线程的 Promise'));

console.log('=== 首次事件循环 - 主线程执行完毕 ===');

//