Webcourse/13-前端面试/01-面试必看/10-js运行机制:异步和单线程.md
2021-01-18 20:46:03 +08:00

243 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 前言
面试时关于`同步和异步`可能会问以下问题
- 同步和异步的区别是什么分别举一个同步和异步的例子
- 一个关于 setTimeout 的笔试题
- 前端使用异步的场景哪些
面试时关于`js运行机制`需要注意以下几个问题
- 如何理解JS的**单线程**
- 什么是**任务队列**
- 什么是 EventLoop
- 理解哪些语句会放入异步任务队列
- 理解语句放入异步任务队列的**时机**
## JS的异步和单线程
> 因为是单线程所以必须异步
我们通过题目来解释以下
### 题目一异步
现有如下代码
```javascript
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000);
console.log(3);
console.log(4);
```
上面的代码中我们很容易知道打印的顺序是`1342`因为你会想到要等一秒之后再打印`2`
可如果我把延时的时间从`1000`改成`0`
```javascript
console.log(1);
setTimeout(function () {
console.log(2);
}, 0);
console.log(3);
console.log(4);
```
上方代码中打印的顺序仍然是`1342`这是为什么呢我们来分析一下
**总结**
js 是单线程同一时间只能做一件事而且有一个**任务队列**全部的同步任务执行完毕后再来执行异步任务第一行代码和最后一行代码是同步任务但是**`setTimeout`是异步任务**
于是执行的顺序是
- 先执行同步任务`console.log(1)`
- 遇到异步任务`setTimeout`**挂起**
- 执行同步任务`console.log(3)`
- **全部的同步任务执行完毕后再来执行异步任务**`console.log(2)`
很多人会把这个题目答错这是因为他们不懂 js 的运行机制
注意上面那句话同步任务执行完毕后再来执行异步任务也就是说**如果同步任务没有执行完异步任务是不会执行的**为了解释这句话我们来看下面这个例子
### 题目二异步
现有如下代码
```javascript
console.log('A');
while (1) {
}
console.log('B');
```
我们很容易想到上方代码的打印结果是`A`因为while是同步任务代码会陷入死循环里出不来自然也就无法打印`B`可如果我把代码改成下面的样子
```javascript
console.log('A');
setTimeout(function () {
console.log('B');
})
while (1) {
}
```
上方代码的打印结果仍然是`A`因为while是同步任务setTimeout是异步任务所以还是那句话**如果同步任务没有执行完队列里的异步任务是不会执行的**
### 题目三同步
```javascript
console.log('A');
alert('haha'); //1秒之后点击确认
console.log('B');
```
`alert`函数是同步任务我只有点击了确认才会继续打印`B`
### 同步和异步的对比
我们在上面列举了异步和同步的例子现在来描述一下区别重要
因为`setTimeout`**异步任务**所以程序并不会卡在那里而是继续向下执行即使settimeout设置了倒计时一万秒但是`alert`函数是**同步**任务程序会**卡在那里**如果它没有执行后面的也不会执行卡在那里自然也就造成了**阻塞**
### 前端使用异步的场景
什么时候需要**等待**就什么时候用异步
- 定时任务setTimeout定时炸弹setInterval循环执行
- 网络请求ajax请求动态`<img>`加载
- 事件绑定比如说按钮绑定点击事件之后用户爱点不点我们不可能卡在按钮那里什么都不做所以应该用异步
- ES6中的Promise
代码举例
```javascript
console.log('start');
var img = document.createElement('img');
img.onload = function () {
console.log('loaded');
}
img.src = '/xxx.png';
console.log('end');
```
上图中先打印`start`然后执行`img.src = '/xxx.png'`然后打印`end`最后打印`loaded`
## 任务队列和Event Loop事件循环
### 任务队列
所有任务可以分成两种一种是同步任务synchronous另一种是异步任务asynchronous同步任务指的是在主线程上排队执行的任务只有前一个任务执行完毕才能执行后一个任务异步任务指的是不进入主线程而进入"任务队列"task queue的任务只有"任务队列"通知主线程某个异步任务可以执行了该任务才会进入主线程执行
总结**只要主线程空了就会去读取"任务队列"这就是JavaScript的运行机制**重要
### Event Loop
主线程从"任务队列"中读取事件这个过程是循环不断的所以整个的这种运行机制又称为Event Loop事件循环
![](http://img.smyhvae.com/20180310_1840.png)
在理解Event Loop时要理解两句话
- 理解哪些语句会放入异步任务队列
- 理解语句放入异步任务队列的**时机**
### 容易答错的题目
```javascript
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
```
很多人以为上面的题目答案是`0,1,2,3`其实正确的答案是`3,3,3,3`
分析for 循环是同步任务setTimeout是异步任务for循环每次遍历的时候遇到settimeout就先暂留着等同步任务全部执行完毕此时i已经等于3了再执行异步任务
我们把上面的题目再加一行代码最终代码如下
```javascript
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
console.log(i);
```
如果我们约定用箭头表示其前后的两次输出之间有 1 秒的时间间隔而逗号表示其前后的两次输出之间的时间间隔可以忽略代码实际运行的结果该如何描述可能会有两种答案
- A. 60% 的人会描述为`3 -> 3 -> 3 -> 3`即每个 3 之间都有 1 秒的时间间隔
- B. 40% 的人会描述为`3 -> 3,3,3`即第 1 3 直接输出1 秒之后连续输出 3 3
循环执行过程中几乎同时设置了 3 个定时器这些定时器都会在 1 秒之后触发而循环完的输出是立即执行的显而易见正确的描述是 B
上面这个题目的参考链接
- [80% 应聘者都不及格的 JS 面试题](https://juejin.im/post/58cf180b0ce4630057d6727c)
- [深入浅出Javascript事件循环机制()](https://zhuanlan.zhihu.com/p/26229293)