面试题答案
一键面试1. 事件循环机制基础
JavaScript是单线程语言,这意味着它在同一时间只能执行一个任务。事件循环(Event Loop)机制的存在,使得JavaScript可以在非阻塞的情况下处理多个任务。事件循环的核心思想是:主线程从“任务队列”中读取任务,执行任务,然后再去读取下一个任务,如此循环。
2. 宏任务队列和微任务队列
- 宏任务(Macrotask):也称为Task。常见的宏任务有
setTimeout
、setInterval
、setImmediate
(仅在Node.js环境)、I/O
操作、UI rendering
等。宏任务队列可以有多个,但在事件循环中,每次事件循环只会处理一个宏任务队列中的一个任务。 - 微任务(Microtask):常见的微任务有
Promise.then
、process.nextTick
(仅在Node.js环境)、MutationObserver
等。微任务队列只有一个,在当前宏任务执行完后,事件循环会立即处理微任务队列中的所有任务,直到微任务队列为空,然后才会去处理下一个宏任务。
3. 回调函数与任务队列关系
- 宏任务回调:像
setTimeout
的回调函数会被放入宏任务队列。当setTimeout
设定的延迟时间到达后,其回调函数就会被推入宏任务队列,等待主线程执行完当前宏任务且微任务队列清空后执行。例如:
setTimeout(() => {
console.log('setTimeout callback');
}, 0);
console.log('main script');
这里,console.log('main script')
先执行,因为它在主线程中按顺序执行。而setTimeout
的回调函数被放入宏任务队列,等主线程任务执行完,且微任务队列清空后才执行,所以最后输出setTimeout callback
。
- 微任务回调:
Promise.then
的回调函数属于微任务回调。当Promise
状态改变后,then
方法中的回调函数会被放入微任务队列。例如:
Promise.resolve().then(() => {
console.log('Promise then callback');
});
console.log('main script');
在这个例子中,console.log('main script')
先执行,然后Promise.then
的回调函数被放入微任务队列,在主线程任务执行完后,事件循环立即处理微任务队列,所以接着输出Promise then callback
。
4. 综合例子分析
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => {
console.log('Promise then in setTimeout 1');
});
}, 0);
Promise.resolve().then(() => {
console.log('Promise then 1');
setTimeout(() => {
console.log('setTimeout in Promise then 1');
}, 0);
});
setTimeout(() => {
console.log('setTimeout 2');
}, 0);
- 首先执行主线程代码,遇到第一个
setTimeout
,其回调函数被放入宏任务队列。 - 遇到第一个
Promise.then
,其回调函数被放入微任务队列。 - 遇到第二个
setTimeout
,其回调函数被放入宏任务队列。 - 主线程代码执行完毕,开始处理微任务队列,输出
Promise then 1
,接着在这个微任务回调中又遇到setTimeout
,其回调函数被放入宏任务队列。 - 微任务队列处理完毕,开始处理宏任务队列,先执行第一个
setTimeout
回调,输出setTimeout 1
,然后在这个回调中的Promise.then
回调被放入微任务队列。 - 宏任务队列中该任务执行完毕,再次处理微任务队列,输出
Promise then in setTimeout 1
。 - 微任务队列处理完毕,继续处理宏任务队列,执行第二个
setTimeout
回调,输出setTimeout 2
。 - 宏任务队列处理完毕,再次处理微任务队列(此时为空)。
- 宏任务队列处理完毕,执行
setTimeout in Promise then 1
回调。
综上,在JavaScript事件循环机制下,回调函数根据其所属的任务类型(宏任务或微任务)被分配到相应队列,按照事件循环规则依次执行。