单线程与事件循环
单线程的本质
JavaScript 是单线程语言,这意味着它一次只能执行一个任务。这种设计源于其最初作为浏览器脚本语言的定位,避免多线程带来的复杂性,比如竞态条件和死锁问题。单线程模型简化了开发,但同时也带来了性能挑战。
console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
console.log('End');
// 输出顺序:Start -> End -> Timeout
这个例子展示了单线程的执行顺序。即使setTimeout
的延迟设为0,回调函数仍然会在当前执行栈清空后才会执行。这种机制就是事件循环的核心表现。
调用栈与任务队列
调用栈是 JavaScript 引擎追踪函数执行顺序的机制。当一个函数被调用时,它会被推入调用栈;执行完毕后,从栈中弹出。同步代码按照顺序依次入栈执行。
function first() {
console.log('First');
second();
}
function second() {
console.log('Second');
}
first();
// 调用栈变化:
// [first] -> [first, second] -> [first] -> []
异步操作如setTimeout
或fetch
会将回调函数放入任务队列。任务队列分为两种:
- 宏任务队列:包含
setTimeout
、setInterval
、I/O等 - 微任务队列:包含
Promise.then
、MutationObserver
等
事件循环的工作流程
事件循环持续检查调用栈和任务队列,按照特定顺序处理任务:
- 执行当前调用栈中的所有同步代码
- 检查微任务队列并执行所有微任务
- 执行一个宏任务
- 重复上述过程
console.log('Script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 1');
}).then(() => {
console.log('Promise 2');
});
console.log('Script end');
/*
输出顺序:
Script start
Script end
Promise 1
Promise 2
setTimeout
*/
这个例子清晰展示了微任务优先于宏任务执行的特性。Promise回调属于微任务,会在当前执行栈清空后立即执行,而setTimeout
回调作为宏任务会在下一轮事件循环中处理。
阻塞与非阻塞I/O
虽然JavaScript是单线程,但通过非阻塞I/O和事件循环实现了高效并发。浏览器环境中的Web API(如fetch
、setTimeout
)和Node.js中的I/O操作都是异步非阻塞的。
// 模拟耗时同步操作
function syncOperation() {
const start = Date.now();
while (Date.now() - start < 3000) {}
console.log('Sync operation done');
}
// 异步非阻塞操作
function asyncOperation() {
setTimeout(() => {
console.log('Async operation done');
}, 3000);
}
console.log('Start');
syncOperation(); // 阻塞3秒
asyncOperation(); // 不阻塞
console.log('End');
宏任务与微任务的优先级
理解宏任务和微任务的执行顺序对编写高效代码至关重要。微任务会在当前宏任务结束后立即执行,而新的宏任务要等到下一次事件循环。
// 示例1:嵌套任务
setTimeout(() => {
console.log('macro 1');
Promise.resolve().then(() => console.log('micro 1'));
}, 0);
setTimeout(() => {
console.log('macro 2');
Promise.resolve().then(() => console.log('micro 2'));
}, 0);
/*
可能输出:
macro 1
micro 1
macro 2
micro 2
*/
// 示例2:微任务中产生新微任务
Promise.resolve().then(() => {
console.log('micro 1');
Promise.resolve().then(() => console.log('micro 2'));
});
/*
输出:
micro 1
micro 2
*/
实际应用中的性能考量
长时间运行的同步代码会阻塞事件循环,导致页面无响应。合理使用Web Worker可以将计算密集型任务分流。
// 主线程
const worker = new Worker('worker.js');
worker.postMessage({ data: largeArray });
worker.onmessage = (e) => {
console.log('Result from worker:', e.data);
};
// worker.js
self.onmessage = (e) => {
const result = processLargeData(e.data);
self.postMessage(result);
};
对于UI更新,可以使用requestAnimationFrame
来确保动画流畅:
function animate() {
// 更新UI
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
异步编程模式演进
从回调地狱到现代异步模式,JavaScript的异步处理方式不断进化:
- 回调函数:
fs.readFile('file.txt', (err, data) => {
if (err) throw err;
console.log(data);
});
- Promise链:
fetch('/api/data')
.then(response => response.json())
.then(data => processData(data))
.catch(error => handleError(error));
- Async/Await:
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return processData(data);
} catch (error) {
handleError(error);
}
}
Node.js中的事件循环差异
Node.js的事件循环与浏览器略有不同,包含更多阶段:
- timers:执行
setTimeout
和setInterval
回调 - pending callbacks:执行系统操作的回调
- idle, prepare:内部使用
- poll:检索新的I/O事件
- check:执行
setImmediate
回调 - close callbacks:执行关闭事件的回调
// Node.js特定示例
setImmediate(() => {
console.log('immediate');
});
setTimeout(() => {
console.log('timeout');
}, 0);
// 输出顺序可能不同,取决于上下文
常见误区与陷阱
- 误认为
setTimeout(fn, 0)
会立即执行:
// 实际上只是尽快执行,但仍需等待当前栈清空
setTimeout(() => console.log('timeout'), 0);
heavyCalculation(); // 这会延迟timeout输出
- 在循环中创建闭包的问题:
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0);
}
// 输出五个5,而不是0-4
// 解决方案:使用let或额外闭包
- 未处理的Promise拒绝:
function riskyOperation() {
return new Promise((resolve, reject) => {
if (Math.random() > 0.5) reject('Error');
else resolve('Success');
});
}
// 应该总是添加catch处理
riskyOperation().catch(console.error);
调试与性能分析
Chrome DevTools提供了强大的事件循环调试能力:
- Performance面板可以记录完整的执行过程
- Console中查看未处理的Promise拒绝
- 使用
queueMicrotask
API直接添加微任务
// 测量代码执行时间
console.time('operation');
expensiveOperation();
console.timeEnd('operation');
// 跟踪微任务
queueMicrotask(() => {
console.log('Microtask executed');
});
高级应用场景
- 实现自定义调度器:
class TaskScheduler {
constructor() {
this.queue = [];
this.isProcessing = false;
}
addTask(task) {
this.queue.push(task);
if (!this.isProcessing) this.processQueue();
}
processQueue() {
this.isProcessing = true;
queueMicrotask(() => {
const task = this.queue.shift();
if (task) task();
if (this.queue.length) this.processQueue();
else this.isProcessing = false;
});
}
}
- 批量DOM更新优化:
function batchDOMUpdates(updates) {
Promise.resolve().then(() => {
document.body.style.display = 'none';
updates.forEach(update => update());
document.body.style.display = '';
});
}
- 实现类似React的调度机制:
const taskQueue = [];
let isPerformingWork = false;
function scheduleTask(task) {
taskQueue.push(task);
if (!isPerformingWork) {
isPerformingWork = true;
requestIdleCallback(performWork);
}
}
function performWork(deadline) {
while (deadline.timeRemaining() > 0 && taskQueue.length) {
const task = taskQueue.shift();
task();
}
if (taskQueue.length) {
requestIdleCallback(performWork);
} else {
isPerformingWork = false;
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn