开启掘金成长之旅!这是我参与「掘金日新计划 · 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执行顺序
首先,JS代码都在主线程中执行,执行到同步代码时,直接放入执行栈;执行到异步代码时,会把异步任务放入工作线程,工作线程会把里面的任务按顺序加入执行队列中。然后主线程接着筛选下面的代码,主线程代码筛选完后,执行栈会按照先进后出的顺序依次执行,执行完成后会清空执行栈。执⾏栈清空后,事件循环会检测任务队列中是否有要执⾏的任务,有的话会把任务队列中的任务按顺序放入执⾏栈中继续执⾏,直到清空任务队列。
宏任务、微任务
通过JS执行顺序我们清楚了异步任务都会放入任务队列中等待执行,任务队列中的任务又分为宏任务和微任务,下面一起来看下这两者:
宏任务:JS最早的异步任务,在主线程中按同步代码的执行顺序逐步进入任务队列,再按队列顺序进入执行栈。
微任务:随着ECMAScript标准提出的异步方案,每一个宏任务执行前都会先清空当前事件循环内的微任务,执行完成后再执行下一个宏任务。
常见的宏任务、微任务:
宏任务 | 微任务 |
---|---|
setTimeout | process.nextTick |
setInterval | Promise |
requestAnimationFrame | MutationObserver |
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');