JavaScript事件循环机制
一、JavaScript执行机制基础
1. 单线程与异步
JavaScript设计之初就是为了在浏览器中处理DOM操作,为了避免复杂的线程同步问题,采用了单线程模型。这意味着JS代码是按顺序执行的,同一时间只能做一件事。
console.log('1');
console.log('2');
// 输出顺序永远是1, 2
但单线程并不意味着阻塞,JavaScript通过事件循环机制实现了异步非阻塞的执行模式。
2. 调用栈与任务队列
- 调用栈(Call Stack):存储同步任务的执行上下文,后进先出(LIFO)
- 任务队列(Task Queue):存储异步任务的回调函数,先进先出(FIFO)
当调用栈为空时,事件循环会从任务队列中取出任务压入调用栈执行。
二、同步代码与异步代码
1. 同步代码执行
同步代码会立即进入调用栈执行:
function func1() {console.log('func1');
}function func2() {func1();console.log('func2');
}func2();
// 输出顺序: func1, func2
2. 异步代码类型
常见的异步操作包括:
- 定时器:setTimeout, setInterval
- 网络请求:Ajax, Fetch
- DOM事件:click, load等
- Promise
- async/await
console.log('Start');setTimeout(() => {console.log('Timeout');
}, 0);Promise.resolve().then(() => {console.log('Promise');
});console.log('End');
// 输出顺序: Start, End, Promise, Timeout
三、宏任务与微任务
1. 宏任务(MacroTask)
包括:
- js脚本执行事件
- setTimeout setInterval
- AJAX请求完成事件
- 用户交互事件
2. 微任务(MicroTask)
包括:
- Promise.then/catch/finally
- process.nextTick(Node.js环境)
- MutationObserver
3. 执行顺序规则
- 执行一个宏任务(通常是script整体代码)
- 执行过程中遇到微任务,加入微任务队列;遇到宏任务,加入宏任务队列
- 当前宏任务执行完毕,立即执行所有微任务
- 进行UI渲染(浏览器环境)
- 从宏任务队列取出下一个宏任务执行
console.log('script start'); // 宏任务1开始setTimeout(function() {console.log('setTimeout'); // 宏任务2
}, 0);Promise.resolve().then(function() {console.log('promise1'); // 微任务1
}).then(function() {console.log('promise2'); // 微任务2
});console.log('script end'); // 宏任务1结束/*
输出顺序:
script start
script end
promise1
promise2
setTimeout
*/
四、典型面试题解析
题目1:基础执行顺序
console.log('1');setTimeout(function() {console.log('2');new Promise(function(resolve) {console.log('3');resolve();}).then(function() {console.log('4');});
}, 0);new Promise(function(resolve) {console.log('5');resolve();
}).then(function() {console.log('6');
});setTimeout(function() {console.log('7');
}, 0);console.log('8');
解析过程:
-
同步代码执行:
- 输出1
- 输出5(Promise构造函数是同步的)
- 输出8
-
微任务队列:
- then回调:输出6
-
宏任务队列:
- 第一个setTimeout:
- 输出2
- Promise构造函数输出3
- then回调(微任务):输出4
- 第二个setTimeout:
- 输出7
- 第一个setTimeout:
最终输出顺序:1, 5, 8, 6, 2, 3, 4, 7
题目2:async/await执行顺序
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');
}, 0);async1();new Promise(function(resolve) {console.log('promise1');resolve();
}).then(function() {console.log('promise2');
});console.log('script end');
解析过程:
-
同步代码:
- 输出script start
- 调用async1(),输出async1 start
- 调用async2(),输出async2
- await后面的代码相当于Promise.then,放入微任务队列
- Promise构造函数输出promise1
- then回调放入微任务队列
- 输出script end
-
微任务队列:
- async1 end
- promise2
-
宏任务队列:
- setTimeout输出setTimeout
最终输出顺序:
script start, async1 start, async2, promise1, script end, async1 end, promise2, setTimeout