JS事件循环机制

171 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

前言

众所周知,JS是一门单线程语言,在同⼀个时间节点只能开展⼀项任务,如果这项任务没有做完,无法去进行后续的任务。但这和我们日常使用的JS显然不太符合,ajax、定时器、Promise等都可以让我们的代码不影响后续任务的执行。本次我们就来探究下JS的这种执行方式--事件循环机制(EventLoop)。

同步、异步

首先来看下JS基础的概念,同步和异步。同步也叫阻塞就是严格按照单线程,在主线程中自上而下执行,报错则停止;异步也叫非阻塞,遇到异步任务时,JS会将当前任务挂起到任务线程,不会立即去执行,等到同步代码执行完后,再按顺序去执行工作线程中挂起的异步代码。异步代码挂起后,并不会影响同步代码的执行。

举个很简单的例子,我们排队去食堂打饭,必须前面的同学打完饭才能轮到我们,这就是同步;我们去外面下馆子时,点好了菜,厨房开始做,这个时候服务员可以去点别的客人的菜,并不用等我们的菜做好后再去,这就是异步。

JS线程

搞清楚同步、异步后,我们来看下JS的线程,首先看下线程的概念:

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

----百度百科

JS是单线程的,但浏览器为了实现异步实际上多线程执行的。这些线程我们分成两类,一类就是主要执行JS代码的主线程,一类是用来执行异步任务的其他工作线程,主要有这些:

  • JS引擎线程
  • GUI渲染线程
  • 浏览器事件线程
  • 定时器线程
  • http请求线程
  • EventLoop循环线程
  • 其他线程

其中JS引擎线程、GUI渲染线程、浏览器事件线程都是主线程,这里我们仅关注JS引擎线程,主要用来执行JS的同步代码,其他线程用来处理异步任务。

JS执行顺序

事件循环模型 (1).png 首先,JS代码都在主线程中执行,执行到同步代码时,直接放入执行栈;执行到异步代码时,会把异步任务放入工作线程,工作线程会把里面的任务按顺序加入执行队列中。然后主线程接着筛选下面的代码,主线程代码筛选完后,执行栈会按照先进后出的顺序依次执行,执行完成后会清空执行栈。执⾏栈清空后,事件循环会检测任务队列中是否有要执⾏的任务,有的话会把任务队列中的任务按顺序放入执⾏栈中继续执⾏,直到清空任务队列。

宏任务、微任务

通过JS执行顺序我们清楚了异步任务都会放入任务队列中等待执行,任务队列中的任务又分为宏任务和微任务,下面一起来看下这两者:

宏任务:JS最早的异步任务,在主线程中按同步代码的执行顺序逐步进入任务队列,再按队列顺序进入执行栈。

微任务:随着ECMAScript标准提出的异步方案,每一个宏任务执行前都会先清空当前事件循环内的微任务,执行完成后再执行下一个宏任务。

常见的宏任务、微任务:

宏任务微任务
setTimeoutprocess.nextTick
setIntervalPromise
requestAnimationFrameMutationObserver
setImmediate
I/O

写在最后

最后来看一组代码吧(大家可以先自己思考下,比较简单,输出顺序已经写在代码里了)

setTimeout(function () {
    console.log('7')
}, 1000);
new Promise(function fun(resolve) {
    console.log('1')
    resolve()
    console.log('2')
}).then(function () {
    console.log('4')
});
requestAnimationFrame(function () {
    console.log('5')
});
setTimeout(function () {
    console.log('6')
}, 0);
console.log('3');