众所周知,JS是一门单线程语言,可是浏览器又能很好的处理异步请求,那么到底是为什么呢?
前言
JS 的执行环境一般是浏览器和 Node.js,两者稍有不同,这里只讨论浏览器环境下的情况。
JS 执行过程中会产生两种任务,分别是:同步任务和异步任务
- 同步任务:比如声明语句、for、赋值等,读取后依据从上到下从左到右,立即执行。
- 异步任务:比如 ajax 网络请求,setTimeout 定时函数等都属于异步任务。异步任务会通过任务队列(Event Queue)的机制(先进先出的机制)来进行协调。
同步任务和异步任务的执行过程
① 同步任务由JavaScript主线程次序执行
② 异步任务委托给宿主环境执行
③ 已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行
④ JavaScript主线程的执行栈被清空后,会读取任务队列中的回调函数,次序执行
⑤ JavaScript主线程不断重复上面的第4步
EventLoop的基本概念
JavaScript主线程从任务队列中读取异步任务的回调函数,放到执行栈中依次执行。这个过程是循环不断的,所以整个的这种机制又称为 EventLoop(事件循环)。
宏任务和微任务
JavaScript把异步任务存放到任务队列中,任务队列中的任务又分为两种,分别是:
- 宏任务(Macrotask)
- 异步Ajax请求
- setTimeout、setInterval
- 文件操作
- 其他宏任务
- 微任务(Microtask)
- Primise.then、.catch 和 .finally(
重点) - process.nextTick(Node.js)
- MutaionObserver
宏任务和微任务的执行顺序
每一个宏任务执行完之后,都会检查是否存在待执行的微任务,如果有,则执行完所有微任务之后,再继续执行下一个宏任务。
分析以下代码的输出顺序
setTimeout(() => {
console.log('1');
});
new Promise(function(resolve){
console.log('2');
resolve()
}).then(function() {
console.log('3');
})
console.log('4');
分析:
① 先执行所有的同步任务
- 执行第6行、第12行代码
② 再执行微任务
- 执行第9行代码
③ 再执行下一个宏任务
- 执行第2行代码
正确输出顺序为:2431
经典面试题
请分析以下代码的输出顺序
console.log('1');
setTimeout(() => {
console.log('2');
new Promise(function(resolve) {
console.log('3');
resolve()
}).then(function() {
console.log('4');
})
});
new Promise(function(resolve) {
console.log('5');
resolve()
}).then(function() {
console.log('6');
})
setTimeout(() => {
console.log('7');
new Promise(function(resolve) {
console.log('8');
resolve()
}).then(function() {
console.log('9');
})
});
正确输出顺序为:156234789