一、知识储备:
js的执行机制
如下图:
js的事件循环:
- 执行js代码的时候,遇见同步任务,直接推入调用栈中;
- 遇到异步任务,将该任务挂起,等到异步任务执行完后推入到任务队列中;
- 当调用栈中的所有同步任务都执行完,再将任务队列中按顺序一个一个的推入调用栈,重复执行这一操作。
过程如下图所示:
宏微队列及执行顺序
宏微队列:
异步任务又分为宏任务和微任务。
宏任务:任务队列中的任务称为宏任务,每个宏任务中都包含了一个微任务队列。
微任务:等宏任务中的主要功能都完成后,渲染引擎不急着去执行下一个宏任务,而是执行当前宏任务中的微任务。
常见的宏任务包含:
- 执行script标签内部代码、
- setTimeout/setInterval、
- ajax请、
- postMessageMessageChannel、
- setImmediate,
- I/O(Node.js)
常见的微任务包含:
- Promise.then(); Promise.cath()、
- async/await、
- process.nextTick(Node.js)、
- MutonObserver、
- Object.observe(异步监视对象修改,已废弃)、
- 加分回答 浏览器和Node 环境下,microtask 任务队列的执行时机不同 - Node端,microtask 在事件循环的各个阶段之间执行 - 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
注意:1.new Promise()属于同步任务,但是Promise.then(); Promise.cath()属于异步任务的微任务。
2.async函数里遇到await之前的代码是同步里的,遇到await时,会执行await后面的函数,然后返回一个promise,把await下面的代码放入微任务,并且退出这个async函数。
3.resolved后的promise对象会在这该级别事件队列结束之后才开始执行,及执行与该轮微任务队列中,始于下一级别宏任务之前
执行顺序:
1.先执行所有同步任务,遇到异步任务,将任务挂起,等到异步任务有返回之后推入到任务队列;
2.同步任务执行完毕后,将任务队列中的任务按顺序一个一个的推入并执行;
3.先执行任务队列里面所有的微任务,如果执行过程中又产生了微任务也会在本次执行过程中执行(即在下一个宏任务执行之前执行)
4.每次准备取出一个宏任务执行前, 都要将所有的微任务一个一个取出来执行,也就是优先级比宏任务高,且与微任务所处的代码位置无关...依次类推到执行结束。
二、题目汇总:
做完下面几道题目,js事件循环基本就没有问题了,可以说90%掌握吧
题目一:
解释
async function async1() { console.log('async1 start') await async2() console.log('async1 end') } async function async2() { console.log('async2') } console.log('script start') setTimeout(function () { console.log('settimeout') }) async1() new Promise(function (resolve) { console.log('promise1') resolve() }).then(function () { console.log('promise2') }) console.log('script end')
结果如下:
script start
async1 start
async2
promise1
script end
async1 end
promise2
settimeout
详解:
考察点 await上面的代码和后面的代码是直接执行的,await下面的代码是要进微任务队列的;new Promise()同步任务;resolve是同步执行的,then里的代码是要进微任务队列的。
执行过程:script start->async1()->async1 start->async2->promise1->script end->微任务处理->async1 end->promise2->宏任务处理->settimeout
题目二:
解释
console.log('1'); // 定义注解 setTimeout_1 用于下文使用方便 setTimeout(function() { console.log('2'); process.nextTick(function() { console.log('3'); }) new Promise(function(resolve) { console.log('4'); resolve(); }).then(function() { console.log('5') }) }) process.nextTick(function() { console.log('6'); }) new Promise(function(resolve) { console.log('7'); resolve(); }).then(function() { console.log('8') }) // setTimeout_2 setTimeout(function() { console.log('9'); process.nextTick(function() { console.log('10'); }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12') }) }) // 输出结果: 1 7 6 8 2 4 3 5 9 11 10 12
详解:
考察点 new Promise属于同步任务,nextTick放在微任务
执行过程:同步输出1-》同步new Promise 7-》宏任务之前先处理微任务-》6-》8-》第一个宏任务setTimeout-》2-》4-》setTimeout产生的微任务-》3-》5-》第二个setTimeout宏任务-》9-》11-》产生的微任务-》10-》12
题目三:
解释
const promise = new Promise((resolve, reject) => { resolve("10") }).then(res => { console.log("res1:", res) //res1: hahaha return 9 }).then(res => { console.log("res2:", res) //res2: 9 return 8 }).then(res => { console.log("res3:", res) //res3: 8 let promise2=new Promise((resolve,reject)=>{ resolve("p2") }).then(res=>{ console.log(res) setTimeout(function(){ console.log("setTimeout2") },0) }) }) console.log('aaa') setTimeout(function(){ console.log("setTimeout1") },0) const promise1 = new Promise((resolve, reject) => { console.log("p1") resolve(989) }).then(res => { console.log(res) return 990 }).then(res=>{ console.log(res) return 991 }).then(res=>{ console.log(res) return 0 }) /*输出结果: aaa p1 res1: 10 989 res2: 9 990 res3: 8 991 p2 setTimeout1 setTimeout2 */
详解:
考察点 resolved后的promise对象会在这该级别事件队列结束之后才开始执行,及执行与该轮微任务队列中,始于下一级别宏任务之前
执行过程:同步代码aaa-》p1-》宏任务之前把两个微任务处理掉-》res1 10-》989-》处理过程中继续产生微任务,继续处理-》res2 9-》990-》res 8-》991-》处理宏任务之前的微任务p2-》产生了一个宏任务setTimeout放到任务队列里-》执行宏任务,第一个setTimeout-》setTimeout1-》第二个setTimeout-》setTimeout2
题目四:
解释
setTimeout(function () { console.log("set1"); new Promise(function (resolve) { resolve(); }).then(function () { new Promise(function (resolve) { resolve(); }).then(function () { console.log("then4"); }); console.log("then2"); }); }); new Promise(function (resolve) { console.log("pr1"); resolve(); }).then(function () { console.log("then1"); }); setTimeout(function () { console.log("set2"); }); console.log(2); queueMicrotask(() => { console.log("queueMicrotask1") }); new Promise(function (resolve) { resolve(); }).then(function () { console.log("then3"); }); //结果 pr1 2 then1 queueMicrotask1 then3 set1 then2 then4 set2
详解:
考察点 queueMicrotask 来执行微任务,Window 或 Worker 接口的 queueMicrotask() 方法;别的,还需要说?
执行过程:
题目还有很多,但是,还需要吗?
题做错了不要紧,记住下面几句话,再去试试:
判断执行顺序大概以下几个重点:
1、promise中的回调函数立刻执行,then中的回调函数会推入微任务队列中,等待调用栈所有任务执行完才执行
2、async函数里遇到await之前的代码是同步里的,遇到await时,会执行await后面的函数,然后返回一个promise,把await下面的代码放入微任务,并且退出这个async函数。
3、调用栈执行完成后,会不断的轮询微任务队列,即使先将宏任务推入队列,也会先执行微任务