概念辨析
并发(Concurrency):两个或多个任务交替执行,在一段时间内看起来像是同时进行。CPU 在不同任务之间快速切换。
并行(Parallelism):两个或多个任务真正同时执行。需要多个 CPU 核心。
JavaScript 是单线程的,但它通过事件循环实现了并发——不是并行。
JavaScript 的并发模型
JavaScript 的并发靠事件循环实现。同一时刻只有一个任务在执行,但任务之间交替执行,给人的感觉像是"同时"执行。
// 这个函数不会阻塞其他代码
function longComputation() {
let result = 0;
for (let i = 0; i < 1e9; i++) {
result += i;
}
return result;
}
// 虽然 longComputation 需要很长时间,
// 但浏览器仍然可以处理用户点击、滚动等事件
// 因为事件循环会在 longComputation 的间隙中处理这些事件
为什么 JavaScript 选择单线程
JavaScript 最初是为浏览器设计的脚本语言。浏览器环境中,如果 JavaScript 多线程操作同一个 DOM,会产生复杂的同步问题。
单线程的优势:
- 无竞态条件:不需要锁或同步机制
- 简单:编程模型简单,不需要考虑线程安全
- 适合 IO 密集型:前端主要是 IO 操作(网络请求、用户交互),CPU 计算不是瓶颈
CPU 密集型任务的挑战
单线程模型在 CPU 密集型任务面前显得无力:
// 这个计算会阻塞主线程 1 秒
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(45)); // 阻塞约 1 秒
Web Worker:真正的并行
Web Worker 是在后台线程运行 JavaScript 的机制,可以实现真正的并行:
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ n: 45 });
worker.onmessage = function(e) {
console.log('Result:', e.data);
};
// worker.js
self.onmessage = function(e) {
const result = fibonacci(e.data.n);
self.postMessage(result);
};
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
Worker 的限制
- Worker 不能直接操作 DOM
- Worker 与主线程通过消息传递通信
- 数据是复制的,不是共享的
Service Worker:后台任务的另一种形态
Service Worker 是浏览器在后台运行的另一个脚本,可以:
- 拦截网络请求(实现离线缓存)
- 推送通知
- 后台同步
navigator.serviceWorker.register('/sw.js');
navigator.serviceWorker.addEventListener('message', event => {
console.log('Data from worker:', event.data);
});
实际应用场景
适合 Worker 的场景
- 复杂的数学计算(加密、图像处理)
- 大数据排序、搜索
- 语法高亮、Markdown 解析等耗时操作
不需要 Worker 的场景
- 简单的 DOM 操作
- 小数据量处理
- 网络请求
这一章想说的
JavaScript 通过单线程 + 事件循环实现了并发,但不是并行。
对于 IO 密集型任务(前端的主要场景),并发模型足够。但对于 CPU 密集型任务,需要用 Web Worker 将计算移到后台线程,实现真正的并行。
使用 Worker 的关键是:不要共享状态,只传递数据。Worker 和主线程之间的通信是异步的,遵循消息传递模式。