Web/05-JavaScript基础:ES6语法/10-单线程和异步.md
2021-05-17 16:58:16 +08:00

5.6 KiB
Raw Blame History

单线程

JavaScript 的执行环境是单线程。即同一时间,只能处理一个任务。

具体来说,所谓单线程,是指 JS 引擎中负责解释和执行 JavaScript 代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个。所有的任务都需要排队

JS 为何要被设计为单线程呢?原因如下:

  • 首先是历史原因,在最初设计 JS 这门语言时,多进程、多线程的架构并不流行,硬件支持并不好。

  • 其次是因为多线程的复杂性,多线程操作需要加锁,编码的复杂性会增高。

  • 而且,如果多个线程同时操作同一个 DOM在多线程不加锁的情况下会产生冲突最终会导致 DOM 渲染的结果不符预期。

所以为了避免这些复杂问题的出现JS 被设计成了单线程语言。

同步任务和异步任务

如果当前正在执行的任务很耗时,它就会阻塞其他正在排队的任务。为了解决这个问题JS 在设计之初,将任务分成了两类:同步任务、异步任务。

  • 同步任务:在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行下一个任务。

  • 异步任务:不进入主线程、而是进入任务队列Event Queue的任务。只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

console.log('同步任务');

setTimeout(() => {
    console.log('异步任务');
}, 1000);

比如上面的代码里:第一行代码是同步任务,会立即执行;定时器里的回调函数是异步任务,需要等 1 秒后才会执行。

前端使用异步的场景

什么时候需要等待,就什么时候用异步。

  • 定时任务setTimeout定时炸弹、setInterval循环执行

  • 事件绑定(比如说,按钮绑定点击事件之后,用户爱点不点。我们不可能卡在按钮那里,什么都不做。所以,应该用异步)

  • 网络请求ajax 请求、网络图片加载

  • ES6 中的 Promise

现在的大部分软件项目,都是前后端分离的。前端发送 ajax 请求,向后端请求数据,然后等待一段时间后,才能拿到数据。这个请求过程就是异步任务。

接口调用的方式

js 中常见的接口调用方式,有以下几种:

  • 原生 ajax、基于 jQuery 的 ajax
  • Promise
  • Fetch
  • axios

下一篇文章,我们重点讲一下接口调用里的Promise

多次异步调用的顺序

  • 多次异步调用的结果,顺序可能不同步。

  • 异步调用的结果如果存在依赖,则需要通过回调函数进行嵌套。

事件循环机制(重要)

执行顺序如下:

  • 同步任务:进入主线程后,立即执行。

  • 异步任务:会先进入 Event Table等时间到了之后再进入 Event Queue然后排队为什么要排队因为同一时间JS 只能执行一个任务)。比如说,setTimeout(()=> {}, 1000)这种定时器任务,需要等一秒之后再进入 Event Queue。

  • 当主线程的任务执行完毕之后,此时主线程处于空闲状态,于是会去读取 Event Queue 中的任务队列,如果有任务,则进入到主线程去执行。

代码示例

掌握了上面的事件循环原理之后,我们来看几个例子。

举例 1

console.log(1);

setTimeout(() => {
    console.log(2);
}, 1000);
console.log(3);
console.log(4);

打印结果:

1
3
4
2

解释:先等同步任务执行完成后,再执行异步任务。

举例 2重要

如果我把上面的等待时间,从 1 秒改成 0 秒,你看看打印结果会是什么。

console.log(1);

setTimeout(() => {
    console.log(2);
}, 0);
console.log(3);
console.log(4);

打印结果:

1
3
4
2

可以看到,打印结果没有任何变化,这个题目在面试中经常出现,考的就是 setTimeout(()=> {}, 0)会在什么时候执行。这就需要我们了解同步任务、异步任务的执行顺序,即前面讲到的事件循环机制

解释:先等同步任务执行完成后,再执行异步任务。

同理,我们再来看看下面这段伪代码:

setTimeout(() => {
    console.log('异步任务');
}, 2000);

// 伪代码
sleep(5000); //表示很耗时的同步任务

上面的代码中异步任务不是2秒之后执行而是等耗时的同步任务执行完毕之后才执行。那这个异步任务是在5秒后执行还是在7秒后执行这个作业留给读者你来思考~

举例 3较真系列

setTimeout(() => {
    console.log('异步任务');
}, 1000);

上面的代码中,等到 1 秒之后,真的会执行异步任务吗?其实不是。

在浏览器中, setTimeout()/ setInterval() 的每调用一次定时器的最小时间间隔是4ms这通常是由于函数嵌套导致嵌套层级达到一定深度或者是由于已经执行的setInterval的回调函数阻塞导致的。

上面的案例中异步任务需要等待1004毫秒之后才会从 Event Table 进入到 Event Queue。这在面试中也经常被问到。

参考链接