JS 的执行模型:单线程不是缺陷,是选择
很多人说 JS 是"单线程,所以慢"。但问题应该反过来问:为什么 JS 故意选了单线程?
问题的起源
1995 年,Brendan Eich 用 10 天写了 JS。
当时的场景:浏览器里需要一点交互逻辑,不是写大型应用。多线程?太重了。而且多线程 + DOM 操作 = 灾难——两个线程同时改 DOM,谁来仲裁?
所以 JS 选了一条路:一个主线程 + 事件循环。
事件循环的本质
很多人以为事件循环是"异步机制"。不对。
事件循环是调度器——它决定"现在该跑哪段代码"。
关键点:主线程永远只做一件事。不是"同时",是"快速切换"。
这个选择的代价
单线程 + 事件循环,代价是什么?
CPU 密集任务会阻塞 UI。
比如一段大量计算的 JS,跑的时候点击事件不会响应,页面也不会重绘——因为主线程被占着。
这就是为什么 JS 有 setTimeout(fn, 0)、Promise、requestAnimationFrame——把任务切碎,让主线程有机会喘口气。
为什么其他语言不这么选?
因为多线程的代价是复杂度。
Java 可以 new Thread(),但你知道多少 Bug 是因为线程安全问题吗?JS 直接把这条路堵死了——你根本写不出线程不安全的代码,因为根本就没有线程。
这是用表达能力换简单性——JS 认觉得,大部分场景不需要多线程,用事件循环足够。
认知升华
回头看,事件循环教给我们的是:
不是所有"并发"都需要多线程。
单线程 + 非阻塞 I/O,在高 I/O 场景(Web 服务器、浏览器)反而比多线程更高效——因为线程切换本身就有开销。
Node.js 就是把这个模型搬到了服务端,才有了"用 JS 写高性能服务器"的可能。
验证你有没有想清楚
如果你能回答这三个问题,说明认知到位了:
- 为什么
setTimeout(fn, 0)不是"立即执行"? Promise.then()和setTimeout(fn, 0)的回调,谁先执行?为什么?- JS 是单线程的,那
fetch()发出去的网络请求是谁在跑?
(答案不写在这里——自己去想清楚,想清楚了才是你的认知。)