一般认为JavaScript本质上是单线程的,但它可以异步运行。
确实,JavaScript被规定为只有一个线程可以在任何给定时间内在
执行环境中执行。 (执行环境是全局环境及其相关对象;例如浏览器中的窗口/选项卡。)您可以拥有多个活动线程(在不同的窗口中或通过Web工作程序),它们可以相互通信(通过
postMessage
),甚至可以共享一些内存(
SharedArrayBuffer
),但它们不能同时访问同一执行环境。保持执行环境有效地是单线程的,避免了大量的并发编程陷阱。
“我想知道像这样处理非阻塞AJAX请求的单线程模型会出现什么情况?”
JavaScript仅允许在JavaScript环境中一次使用一个线程并不意味着主机(浏览器)是单线程的。 异步AJAX请求被交给浏览器的网络处理。
JavaScript基于作业队列工作(HTML5规范称其为任务队列,但JavaScript规范使用"jobs"——这只是一个名称)。JavaScript线程从队列中获取一个作业,运行该作业直至完成,然后获取下一个作业(如果有的话)。虽然实际上比这复杂一些,但这就是基本思想。当线程运行一个作业时,在同一领域中没有其他线程可以运行另一个作业。
因此,当ajax请求完成(成功、超时等)时,浏览器(可能在非JavaScript线程上)将一个作业放入JavaScript作业队列中以调用ajax回调函数。JavaScript线程获取该作业并调用回调函数。
值得注意的是,对于用户点击等其他事件,它也完全按照此方式响应。
假设在浏览器中发起了一个非阻塞的AJAX请求,但没有立即收到响应。如果事件循环继续检查响应,执行会被阻塞吗?
关键在于线程并不持续地检查响应。线程只是监视作业队列。浏览器的网络处理程序处理网络请求的完成。
¹ 这在 ES2015 中已经明确说明,但在此之前的常见环境(浏览器、Node.js)中也是如此。有一些 JavaScript 环境允许在一个域中使用多个线程(例如 Rhino,在 Java 虚拟机上运行 JavaScript),但它们被认为不够重要,不能阻止 ES2015 添加这个要求。这样做可以定义几个新功能的精确语义,如果保持对线程的沉默,则这将更加复杂甚至不可能。