1.1什么叫异步

异步是相对于同步而言的,很好理解。
同步就是一件事一件事的执行。只有前一个任务执行完毕,才能执行后一个任务。而异步比如:

setTimeout(() => {
  console.log('what is 异步')
},1000)

setTimeout就是一个异步任务,当js引擎顺序执行到的时候发现它是个异步任务,则会把这个任务放进任务队列中去,继续执行后面的代码。所以简单来说只要改变的js的执行顺序,就是异步操作。

1.2为什么要在js中使用异步

由于javascript是单线程的,只能在js引擎的主线程上运行,所以js只能一行一行的执行,不能再同一时间执行多个js任务,这就会导致如果有一段耗时较长的计算,或者是一个ajax请求,如果没有异步,就会出现用户长时间等待的情况。

1.3常见的异步模式

  • 回调函数
  • 事件监听
  • 发布/订阅模式(又称观察者模式)
  • promise

1.4 JS如何实现异步

具体JS是如何异步操作呢?
就是JS的事件循环机制。
具体来说:
当JS解析执行时,会被引擎分为两类任务,同步任务(synchronous)和异步任务(asynchronous)。

对于同步任务来说,会被推到执行栈按顺序去执行这些任务。
对于异步任务来说,当其可以执行时,会被放到一个任务队列(task queue)里等待js引擎去执行。

当执行栈中的所有同步任务完成后,js引擎才会去任务队列里查看是否有任务存在,并将任务放到执行栈中去执行,执行完了又会去任务队列里查看是否有已经可执行的任务。这种循环检查的机制,就叫做事件循环(event loop)。

对于任务队列,其实是有更细的分类。其被分为微任务(microtask)队列 && 宏任务(macrotask)队列。

  • 宏任务:setTimeout,setInterval等,会被放到宏任务队列
  • 微任务:Promise的then,Mutation Observer等,会被放在微任务队列

Event Loop的执行顺序是:

  1. 首先执行栈里的任务
  2. 执行栈清空之后,检查微任务(microtask)队列,将可执行的微任务全部执行
  3. 取宏任务(macrotask)队列中的第一项执行。
  4. 回到第二步。

注意:微任务队列每次全部执行,宏任务队列每次只取一次执行。
举个例子:

setTimeout(() => {
  console.log('我是第一个宏任务');
  Promise.resolve().then(() => {
    console.log('我是第一个宏任务里的第一个微任务');
  });
  Promise.resolve().then(() => {
    console.log('我是第一个宏任务里的第二个微任务');
  });
},0)

setTimeout(() => {
  console.log('我是第二个宏任务');
},0);

Promise.resolve().then(() => {
 console.log('我是第一个微任务');
});

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

最后的执行结果是:

  • // 执行同步任务
  • // 我是第一个微任务
  • // 我是第一个宏任务
  • // 我是第一个宏任务里的第一个微任务
  • // 我是第一个宏任务里的第二个微任务
  • // 我是第二个宏任务