JS 的执行模型:单线程不是缺陷,是选择

很多人说 JS 是"单线程,所以慢"。但问题应该反过来问:为什么 JS 故意选了单线程?


问题的起源

1995 年,Brendan Eich 用 10 天写了 JS。

当时的场景:浏览器里需要一点交互逻辑,不是写大型应用。多线程?太重了。而且多线程 + DOM 操作 = 灾难——两个线程同时改 DOM,谁来仲裁?

所以 JS 选了一条路:一个主线程 + 事件循环


事件循环的本质

很多人以为事件循环是"异步机制"。不对。

事件循环是调度器——它决定"现在该跑哪段代码"。

线线APIs/etNToidmeeoutfetch

关键点:主线程永远只做一件事。不是"同时",是"快速切换"。


这个选择的代价

单线程 + 事件循环,代价是什么?

CPU 密集任务会阻塞 UI

比如一段大量计算的 JS,跑的时候点击事件不会响应,页面也不会重绘——因为主线程被占着。

这就是为什么 JS 有 setTimeout(fn, 0)PromiserequestAnimationFrame——把任务切碎,让主线程有机会喘口气


为什么其他语言不这么选?

因为多线程的代价是复杂度

Java 可以 new Thread(),但你知道多少 Bug 是因为线程安全问题吗?JS 直接把这条路堵死了——你根本写不出线程不安全的代码,因为根本就没有线程

这是用表达能力换简单性——JS 认觉得,大部分场景不需要多线程,用事件循环足够。


认知升华

回头看,事件循环教给我们的是:

不是所有"并发"都需要多线程。

单线程 + 非阻塞 I/O,在高 I/O 场景(Web 服务器、浏览器)反而比多线程更高效——因为线程切换本身就有开销。

Node.js 就是把这个模型搬到了服务端,才有了"用 JS 写高性能服务器"的可能。


验证你有没有想清楚

如果你能回答这三个问题,说明认知到位了:

  1. 为什么 setTimeout(fn, 0) 不是"立即执行"?
  2. Promise.then()setTimeout(fn, 0) 的回调,谁先执行?为什么?
  3. JS 是单线程的,那 fetch() 发出去的网络请求是谁在跑?

(答案不写在这里——自己去想清楚,想清楚了才是你的认知。)