首先,javascript
是一个单线程,这意味着它只能同时执行一个任务。当遇到耗时的任务(如网络请求、文件读取等)时,主线程就会被阻塞,而事件循环机制是JavaScript
运行时环境的一部分,负责协调代码的执行、事件的处理和调度。她可以监听来自消息队列的消息,并依次处理它们。
JavaScript运行模型
要理解事件循环,首先需要了解JavaScript
的运行模型。JavaScript
的执行可以分为两个主要部分:同步代码和异步代码。
- 同步代码:这是按顺序执行的代码。每一行代码必须等前一行执行完成后才能执行。
- 异步代码:这种代码允许延迟执行,例如定时器、网络请求等。
事件循环的工作流程
事件循环的工作可以分为几个步骤:
- 执行栈:所有同步任务都在主线程上执行,形成一个执行栈。
- 消息队列:当一个异步事件发生并需要执行回调时,该事件的回调会被推送到一个消息队列中等待处理。
- 事件循环:一旦所有同步任务在执行栈中完成,事件循环就会从消息队列中取出任务,放到执行栈中执行。
重要的是要注意,如果执行栈中有任务正在执行,事件循环会等待直到执行栈为空。
宏任务与微任务
JavaScript
的异步任务可以分为两类:宏任务(MacroTask
)和微任务(MicroTask
)。
- 宏任务:
setTimeout
,setInterval
,I/O
,UI渲染
等。 - 微任务:
Promise
,process.nextTick
等。
处理过程中,微任务的优先级高于宏任务。在每个宏任务执行完后,如果存在微任务队列,会先执行微任务队列中的所有任务才会继续执行宏任务。
实际案例
考虑以下代码:
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
输出顺序是:
1 4 3 2
首先,1
和 4
是同步代码,按顺序执行。尽管setTimeout
的延时为0,它仍被视为宏任务,会在当前执行栈清空及所有微任务(这里的Promise回调)执行完毕后才执行。
这是一道关于事件循环机制的面试题
//判断异步执行顺序
console.log(1);
setTimeout(()=> {
Promise.resolve().then(()=>{
console.log(2);
})
console.log(3);
},0)
new Promise((resolve) => {
for (let i = 0; i <= 1000; i++) {
if (i === 1000) {
resolve()
}
}
console.log(4);
}).then(()=>{
console.log(5);
})
console.log(6);
1 4 6 5 3 2
结论
JavaScript的事件循环是一个强大的机制,使得单线程JavaScript能够执行异步代码,处理并发操作。理解事件循环的工作原理对于编写高效且错误更少的异步JavaScript代码至关重要。