如果你是 JavaScript 新手,那么你一定听说过 JavaScript 是一门异步编程语言,但我要说其实你是错的,JavaScript 不是异步而是同步的语言,那么为什么每个人都在说它是异步的呢?让我们通过下面的文章来了解一下
什么是同步的 JavaScript?
你写了一段代码,代码都是一行接一行地执行,这就是同步代码。每一步操作都要等待前一个步骤执行完成。我们来通过一个例子理解一下。

图1. 一个计算两数之和的同步函数
我们来看一下图1里的代码是如何工作的,代码是按照下面的顺序执行的:
- 首先会执行的代码是 getSum=add(2,3)。
- 这会调用到第一行里的 add 函数。
- a 与 b 的和存储在常量 sum 里。
- 然后返回 sum 变量并将结果存储在常量 getSum 里。
- 最后打印出 getSum。
在上面我们可以看到 console.log 在等着结果返回。 这是因为 JavaScript 是单线程的,在单线程中的单个时间点只能发生一件事,因此它会等待上一步执行,在此之前一切都会被阻塞。
什么是异步的 JavaScript?
你写了一段代码并且特定的步骤不会等待上一步执行完时,那么它就是异步的。 这种情况会出现在调用使用外部设备获取数据的 API 时,那么在数据到来之前阻塞整个后续代码的执行是没有意义的。 让我们通过下面的例子来理解。

图2. 一个会在 3s 后打印 sum 值的异步函数
图2中,你认为上面的代码执行后会输出什么?‘The sum of 2 and 3 is 5’是会先打印然后打印 sum 的值呢,还是会它会在 sum 的值之后打印?我们来看一下代码是如何工作的,代码是按照下面的顺序执行的:
- 首先会执行的代码是 getSum=add(2,3)。
- 这会调用到第一行里的 add 函数。
- a 与 b 的和存储在常量 sum 里。
- setTimeout 并不会立即执行,它会发送给浏览器来执行,一旦执行完毕,回调函数就会发送到事件队列(稍后会讨论)里去。
- 接着返回 sum 变量并将结果存储在常量 getSum 里。
- 最后打印出 getSum。
- 现在,当调用栈清空后,回调函数会进入主线程,并在 3 秒后打印“2 和 3 之和为 5”
那么上面发生了什么,为什么“The sum of 2 and 3 is 5”会后打印,即使它在“5”之前调用的。这是因为我们有事件队列和事件循环的概念,只有在调用栈为空后才执行异步调用。
什么是事件队列和事件循环?
异步调用会放入到事件队列或回调队列中,它们会在调用栈清空后执行,也就是在主线程处理完所有操作后,以便这些异步调用不会阻止下一行 javascript 代码的运行。

图3. 事件循环和回调队列在 JS 引擎中的工作原理
在图 3 中,一旦主线程中的所有代码执行完毕,这些回调队列就会被发送回主线程,然后执行这些队列中的代码。 在事件循环的帮助下,事件队列被发送回主线程。 事件循环持续运行并检查主线程是否为空,一旦主线程为空,队列就会被压进调用栈里。
总结
如我们在上面读到的,它证明了 JS 是一种同步、阻塞和单线程的语言。 是外部 API 使语言表现出了异步特性,为了实现异步,才有了事件队列和事件循环的概念。 这些东西是由 JavaScript 引擎控制的,而不是 Javascript 本身。 浏览器有 JavaScript 引擎,V8就是最常用的引擎之一。