JavaScript高级 - 6 - EventLoop、宏任务、微任务
1. JavaScript 是单线程语言
JavaScript 是一门单线程执行的编程语言,同一时间只能做一件事情。
单线程执行任务队列的问题:如果前一个任务非常耗时,则后续任务就不得不一直等待,从而导致程序假死的问题。
2. 同步任务和异步任务
为了防止某个耗时任务导致程序假死的问题,JavaScript 把待执行的任务分为了两类:
-
同步任务(synchronous)
又叫做非耗时任务,指的是在主线程上排队执行的那些任务。只有前一个任务结束,才会执行后一个任务。
-
异步任务(asynchronous)
又叫做耗时任务,异步任务有JavaScript 委托给宿主环境进行执行。当异步任务执行完成后,会通知JavaScript 主线程执行异步任务的回调函数。
3. 同步任务和异步任务的执行过程
-
同步任务由 JavaScript 主线程依次执行
-
异步任务委托给宿主环境执行
-
已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行
-
JavaScript 主线程的执行栈被清空后,会按顺序读取任务队列中的回调函数,拿到主线程执行栈中执行
-
JavaScript 主线程不断重复上面的第四步
4. 事件循环
JavaScript 主线程从“任务队列”中读取异步任务的回调函数,放在执行栈中依次执行。这个过程是循环不断的,所以整个的这种运行机制又称为 EventLoop(事件循环)。
代码示例:
import thenFs from 'then-fs'
console.log('A')
thenFs.readFile('./files/test.txt', 'utf8').then(data => { console.log('B')})
setTimeout(() => console.log('C'), 0)
console.log('D')
结合事件循环机制,上面代码的输出顺序应该是:ADCB。
-
A 和 D 属于同步任务。会根据代码的先后顺序依次执行
-
C 和 B 属于异步任务。异步任务完成后,它们的回调函数会被加入到任务队列,等待主线程空闲时再执行
5. 宏任务和微任务
JavaScript 把异步任务又做了进一步的划分。
异步任务又分为两类:
-
宏任务(macrotask)
-
异步 Ajax 请求
-
setTimeout、setInterval
-
文件操作
-
其他宏任务
-
-
微任务(microtask)
-
Promise.then、Promise.catch、Promise.finally
-
process.nextTick
-
其他微任务
-
6. 宏任务和微任务执行顺序
宏任务和微任务是交替执行的:每一个宏任务执行完之后,都会检查是否存在待执行的微任务。如果有,则执行所有微任务,然后再继续执行下一个宏任务。
-
举个栗子:
-
需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
setTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one'); // one // two // three
-
简单理解就是每次主线程执行栈中的同步任务执行完成后,都会检查是否有微任务需要执行。执行完微任务之后,再进行下一轮事件循环,把任务队列中最近的宏任务拿到主线程执行栈中执行。
console.log('1') setTimeout(() => { console.log('2') new Promise((resolve) => { console.log('3') resolve() }).then(() => { console.log('4') }) }) new Promise(resolve => { console.log('5'); resolve() }).then(() => { console.log('6') }) setTimeout(() => { console.log('7') new Promise((resolve) => { console.log('8') resolve() }).then(() => { console.log('9') }) }) // 156234789