基础概念
- 进程是计算机已经运行的程序,线程是操作系统能够进行运算调度的最小单位,它被包含在进程中.浏览器中每开一个Tab页,就会打开一个进程,而这个进程又包含了很多线程.
- 大家都知道JS是一门单线程语言,如果遇到了非常耗时的操作,那么JS的执行就会受到阻塞,这肯定不是我们想看到的,所以这些耗时的操作,往往不是由JS线程所执行的,而是交由浏览器中的其他线程去完成的,成功之后只要在某个特定的时候进行一个回调函数即可
- 所以引出了事件循环的概念,在事件循环中,分两种任务,分别是宏任务和微任务
- 宏任务包含
ajax、setTimeout、setInterval、DOM监听、UI Rendering
- 微任务包含
Promise的then回调、 Mutation Observer API、queueMicrotask()等
- 宏任务包含
- 接下来我们直接就开始练习面试题熟悉熟悉
面试题一
setTimeout(function () {
console.log("setTimeout1");
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("promise1");
resolve();
}).then(function () {
console.log("then1");
});
setTimeout(function () {
console.log("setTimeout2");
});
console.log(2);
queueMicrotask(() => {
console.log("queueMicrotask1")
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});
- 先解决同步任务
- 输出
promise1
2
- 输出
- 开始解决异步任务中的微任务
- 输出
then1
queueMicrotask1
then3
- 输出
- 开始解决异步任务中的宏任务
- 输出
setTimeout1
,在第一个定时器中,又遇到了微任务,那么接着执行微任务- 输出
then2
然后输出then4
- 输出
- 目光跳出第一个定时器中,看到第二个定时器 开始输出
setTimeout2
- 输出
- 最后的完整输出为
promise1 2 then1 queueMicrotask1 then3 setTimeout1 then2 then4 setTimeout2
面试题二
async function async1() {
console.log('async1 start')
// await异步函数的返回结果 resolve的结果会作为整个异步函数的promise的resolve结果->同步代码
// await后面的执行代码 就会变成.then后面的执行函数->微任务
// 也就是说 console.log('async1 end') 这一段是相当于then方法内的 会被加入微任务中
await async2();
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
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
- 输出
- 完整输出:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
面试题三
Promise.resolve().then(() => {
console.log(0);
//1.直接返回4 微任务不会做任何延迟
// return 4
//2.直接返回Promise.resolve(4) 微任务推迟两次
// return Promise.resolve(4);
//3.返回thenable对象
return {
then: ((resolve, reject) => {
resolve(4);
})
}
}).then((res) => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
})
这道面试题有些特殊,需要大家记住两个结论
- 如果返回的是
thenable
对象,那么微任务会推迟一次,thenable
对象就是实现了Promise.then
的那个函数,具体可看代码 - 如果返回的是
Promise.resolve(4)
,那么微任务会推迟两次,这个相当于是返回一个Promise
之后又用了resolve
,二者是等价的 - 为了配合大家理解,我给大家画了几张图,大家可以看看
面试题四
node的事件循环也分宏任务和微任务
- 宏任务:
setTimeout、setInterval、IO事件、setImmediate、close事件
- 微任务:
Promise的then回调、process.nextTick、queueMicrotask
node的每次事件循环都是按照以下顺序来执行的
- next tick microtask queue
- other microtask queue
- timer queue
- poll queue
- pcheck queue
- close queue
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('setTimeout0')
}, 0)
setTimeout(function () {
console.log('setTimeout2')
}, 300)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick1'));
async1();
process.nextTick(() => console.log('nextTick2'));
new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
})
console.log('script end')
- 首先执行同步任务
- 输出
script start
async1 start
async2
promise1
promise2
script end
- 输出
- 接着执行微任务,因为node会优先执行
nextTick
这个微任务- 所以先输出
nextTick1
nextTick2
- 在输出其他微任务,输出
async1 end
promise3
- 所以先输出
- 最后执行宏任务
- 输出
setTimeout0
setImmediate
- 因为这个定时器延时3ms执行,所以会让其他的宏任务先执行完毕,才回去执行这个定时器,所以最后输出
setTimeout2
- 输出
- 最后的输出结果:
script start
async1 start
async2
promise1
promise2
script end
nextTick1
nextTick2
async1 end
promise3
setTimeout0
setImmediate
setTimeout2