微任务、宏任务、事件循环、异步

这一部分主要讲述了 JavaScript 的执行,涉及到的知识点

  • 事件循环
  • 微任务宏任务
  • 异步执行
  • async 和 generator 对比

事件循环(Event Loop)

事件循环包含的知识点很多。而且浏览器和 NodeJS 的事件循环还略有不同。

事件循环-阮一峰

浏览器的 Event Loop

浏览器的事件循环和 NodeJs 的事件循环不同。

JavaScript 是单线程的,因此同一个时间只能做一件事。JavaScript 作为浏览器语言,如果采用多线程可能会带来一些复杂的同步问题,比如两个线程操作 DOM,一个修改,一个删除,不太好判断此时的交互状态。JavaScript 主要用途是和用户互动,操作 DOM。

单线程意味着需要排队,如果前一个任务时间很长,后面的任务就会被阻塞。IO(比如 Ajax)很慢,如果一直等待会造成 CPU 空闲(操作系统中的多道批处理操作系统)。浏览器和服务端不同。浏览器端受限于网络原因,网络不好的情况下,请求可能会很长时间才会响应。因此对于浏览器端来讲,更合理的方案是异步(CommonJs 和 AMD&CMD 方案原因同)。

对任务来讲,可以分为同步任务和异步任务。同步任务进入主线程,按顺序执行。异步任务不进入主线程,进入”任务队列”,只有异步任务有运行结果了,就会在”任务队列”中放置事件。主线程上的同步任务执行完毕后,才会读取”任务队列”,异步任务才会进入执行栈,开始执行(执行异步任务的回调函数)。

说到事件循环,就要提到定时器的问题(setTimeout 和 setInterval)。定时器进入”执行队列”,因此会等待执行栈内的任务执行完毕后才会执行,如下代码。执行顺序是 1 2。

1
2
3
4
setTimeout(function () {
console.log(2)
}, 0)
console.log(1)

这样就会产生一个问题,函数设置的执行时间未必一定会执行,设置的时间只是到目标时间时将其放入”任务队列”,如果执行找还有任务,那么就不会执行。如下,5s 后才会执行,并不是 1s。

1
2
3
4
5
6
7
8
9
setTimeout(function () {
console.log("执行")
}, 1000)
;(function func() {
var begin = Date.now()
while (Date.now() - begin < 5000) {
// console.log(1);
}
})()

NodeJS 的 Event Loop

NodeJS 提供了process.nextTicksetImmediate两个与”任务队列”相关的方法。

  • process.nextTick:在”执行栈”的尾部触发回调(发生在所有异步任务之前)
  • setImmediate:在”任务队列”尾部触发回调(下一次 Event Loop 开始时执行)

执行宏任务的几个队列。

  • timers 阶段:执行 setTimeout,setInterval 回调
  • I/O callbacks 阶段:其它异步回调
  • idle,prepare 阶段:node 内部使用
  • poll 阶段:获取新的 I/O 事件
  • check 阶段:执行 setImmediate 回调
  • close callbacks 阶段:执行 socket.on(‘close’, …)等回调

执行微任务的几个队列。

  • Next Tick 队列:放置 process.nextTick 回调
  • Other Micro 队列:放置其它微任务,比如 Promise

微任务、宏任务

首先,微任务和宏任务的概念来区。一个 JavaScript 引擎会常驻内存中,等待我们(宿主)把 JavaScript 代码或函数传递执行。

在之前,JavaScript 本身还没有异步执行代码的能力,就意味着宿主传递给 JavaScript 引擎一段代码,引擎直接把代码顺序执行了,这个任务就是宿主发起的任务。在 ES6 引入的 Promise,不需要浏览器的安排,JavaScript 引擎本身也能发起任务了。

  • 宏任务:这里把宿主发起的任务称为宏任务(setTimeout,setInterval)
  • 微任务:JavaScript 引擎发起的任务称为微任务(Promise)

总结一下,浏览器端的循环优先级,在同一个循环下,同步 > 微任务(Promise) > 宏任务(setTimeout,setInterval)

Generator 和 async

这里感觉 winter 和阮一峰两者对其区别的解释似乎有一些出入。

winter:

generator 并非被设计成实现异步,所以有了 async/await 之后,generator/iterator 来模拟异步方法应该被废弃。

阮一峰:

Generator 函数是 ES6 提供的一种异步编程解决方案。async 函数是什么?一句话,它就是 Generator 函数的语法糖。

两者似乎是有些不同,但是根据我重新阅读 Generator 后,我个人更偏向于 winter 的解释,Generator 里的 co 模块就是用于函数的自动执行,此时才算是模拟了 async/await。

那 Generator 的适用范围?根据我的理解,和状态机比较的契合,刚好在做 html 解析的部分,可以尝试用 Generator 来写一写。