diff --git a/05-JavaScript基础:ES6语法/10-单线程和异步.md b/05-JavaScript基础:ES6语法/10-单线程和异步.md index 30e5426..a69389b 100644 --- a/05-JavaScript基础:ES6语法/10-单线程和异步.md +++ b/05-JavaScript基础:ES6语法/10-单线程和异步.md @@ -1,55 +1,50 @@ - ## 单线程 JavaScript 的执行环境是**单线程**。即同一时间,只能处理一个任务。 具体来说,所谓单线程,是指 JS 引擎中负责解释和执行 JavaScript 代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个。所有的任务都**需要排队**。 -JS为何要被设计为单线程呢?原因如下: +**JS 为何要被设计为单线程呢**?原因如下: -- 首先是历史原因,在最初设计 JS 这门语言时,多进程、多线程的架构并不流行,硬件支持并不好。 +- 首先是历史原因,在最初设计 JS 这门语言时,多进程、多线程的架构并不流行,硬件支持并不好。 -- 其次是因为多线程的复杂性,多线程操作需要加锁,编码的复杂性会增高。 +- 其次是因为多线程的复杂性,多线程操作需要加锁,编码的复杂性会增高。 -- 而且,如果多个线程同时操作同一个 DOM,在多线程不加锁的情况下,会产生冲突,最终会导致 DOM 渲染的结果不符预期。 +- 而且,如果多个线程同时操作同一个 DOM,在多线程不加锁的情况下,会产生冲突,最终会导致 DOM 渲染的结果不符预期。 -所以,为了避免这些复杂问题的出现,JS被设计成了单线程语言。 +所以,为了避免这些复杂问题的出现,JS 被设计成了单线程语言。 ## 同步任务和异步任务 +如果当前正在执行的任务很耗时,它就会**阻塞**其他正在排队的任务。为了解决这个问题,JS 在设计之初,将任务分成了两类:同步任务、异步任务。 -如果当前正在执行的任务很耗时,它就会**阻塞**其他正在排队的任务。为了解决这个问题,JS在设计之初,将任务分成了两类:同步任务、异步任务。 +- 同步任务:在**主线程**上排队执行的任务。只有前一个任务执行完毕,才能执行下一个任务。 -- 同步任务:在**主线程**上排队执行的任务。只有前一个任务执行完毕,才能执行下一个任务。 +- 异步任务:不进入主线程、而是进入**任务队列**(Event Queue)的任务。只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。 -- 异步任务:不进入主线程、而是进入**任务队列**(task queue)的任务。只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。 +```js +console.log('同步任务'); +setTimeout(() => { + console.log('异步任务'); +}, 1000); +``` -### 事件循环 - -这里少一个图 - - -### 多次异步调用的顺序 - -- 多次异步调用的结果,顺序可能不同步。 - -- 异步调用的结果如果**存在依赖**,则需要通过回调函数进行嵌套。 - +比如上面的代码里:第一行代码是同步任务,会**立即执行**;定时器里的回调函数是异步任务,需要等 1 秒后才会执行。 ### 前端使用异步的场景 什么时候需要**等待**,就什么时候用异步。 -- 定时任务:setTimeout(定时炸弹)、setInterval(循环执行) +- 定时任务:setTimeout(定时炸弹)、setInterval(循环执行) -- 事件绑定(比如说,按钮绑定点击事件之后,用户爱点不点。我们不可能卡在按钮那里,什么都不做。所以,应该用异步) +- 事件绑定(比如说,按钮绑定点击事件之后,用户爱点不点。我们不可能卡在按钮那里,什么都不做。所以,应该用异步) -- 网络请求:ajax请求、网络图片加载 - -- ES6中的Promise +- 网络请求:ajax 请求、网络图片加载 +- ES6 中的 Promise +现在的大部分软件项目,都是前后端分离的。前端发送 ajax 请求,向后端请求数据,然后**等待一段时间**后,才能拿到数据。这个请求过程就是异步任务。 ### 接口调用的方式 @@ -60,12 +55,110 @@ js 中常见的接口调用方式,有以下几种: - Fetch - axios - 下一篇文章,我们重点讲一下接口调用里的**Promise**。 +### 多次异步调用的顺序 + +- 多次异步调用的结果,顺序可能不同步。 + +- 异步调用的结果如果**存在依赖**,则需要通过回调函数进行嵌套。 + +### 事件循环机制(重要) + +![](http://img.smyhvae.com/20210517_1431.png) + +执行顺序如下: + +- 同步任务:进入主线程后,立即执行。 + +- 异步任务:会先进入 Event Table;等时间到了之后,再进入 Event Queue,然后排队(为什么要排队?因为同一时间,JS 只能执行一个任务)。比如说,`setTimeout(()=> {}, 1000)`这种定时器任务,需要等一秒之后再进入 Event Queue。 + +- 当主线程的任务执行完毕之后,此时主线程处于空闲状态,于是会去读取 Event Queue 中的任务队列,如果有任务,则进入到主线程去执行。 + +## 代码示例 + +掌握了上面的事件循环原理之后,我们来看几个例子。 + +### 举例 1 + +```js +console.log(1); + +setTimeout(() => { + console.log(2); +}, 1000); +console.log(3); +console.log(4); +``` + +打印结果: + +``` +1 +3 +4 +2 +``` + +解释:先等同步任务执行完成后,再执行异步任务。 + +### 举例 2(重要) + +如果我把上面的等待时间,从 1 秒改成 0 秒,你看看打印结果会是什么。 + +```js +console.log(1); + +setTimeout(() => { + console.log(2); +}, 0); +console.log(3); +console.log(4); +``` + +打印结果: + +``` +1 +3 +4 +2 +``` + +可以看到,打印结果没有任何变化,这个题目在面试中经常出现,考的就是 `setTimeout(()=> {}, 0)`会在什么时候执行。这就需要我们了解同步任务、异步任务的执行顺序,即前面讲到的**事件循环机制**。 + +解释:先等同步任务执行完成后,再执行异步任务。 + +同理,我们再来看看下面这段伪代码: + +```js +setTimeout(() => { + console.log('异步任务'); +}, 2000); + +// 伪代码 +sleep(5000); //表示很耗时的同步任务 +``` + +上面的代码中,异步任务不是2秒之后执行,而是等耗时的同步任务执行完毕之后,才执行。那这个异步任务,是在5秒后执行?还是在7秒后执行?这个作业,留给读者你来思考~ + + +### 举例 3(较真系列) + +```js +setTimeout(() => { + console.log('异步任务'); +}, 1000); +``` + +上面的代码中,等到 1 秒之后,真的会执行异步任务吗?其实不是。 + +在浏览器中, setTimeout()/ setInterval() 的每调用一次定时器的最小时间间隔是**4ms**,这通常是由于函数嵌套导致(嵌套层级达到一定深度),或者是由于已经执行的setInterval的回调函数阻塞导致的。 + +上面的案例中,异步任务需要等待1004毫秒之后,才会从 Event Table 进入到 Event Queue。这在面试中也经常被问到。 ## 参考链接 -- [JS-同步任务,异步任务,微任务,和宏任务](https://github.com/PleaseStartYourPerformance/javaScript/issues/34) +- [JS-同步任务,异步任务,微任务,和宏任务](https://github.com/PleaseStartYourPerformance/javaScript/issues/34) -- [JS同步异步宏任务微任务](https://juejin.cn/post/6875605533127081992)、[JavaScript中事件循环的理解](https://zhuanlan.zhihu.com/p/364475433)、[javascript事件循环机制](https://github.com/reng99/blogs/issues/34) +- [JS 同步异步宏任务微任务](https://juejin.cn/post/6875605533127081992)、[JavaScript 中事件循环的理解](https://zhuanlan.zhihu.com/p/364475433)、[javascript 事件循环机制](https://github.com/reng99/blogs/issues/34)