1. 事件循环机制概述
1.1 事件循环的定义
JavaScript 是一种单线程的编程语言,只有一个调用栈,决定了它在同一时间只能做一件事。在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。因此JS又是一个非阻塞、异步、并发式的编程语言。
事件循环(Event Loop)是JavaScript中一种运行机制,它允许JavaScript引擎在单线程环境中处理异步操作。事件循环不断地检查回调队列,并在调用栈清空时执行队列中的回调函数。
1.2 宏任务与微任务的概念
宏任务(Macro Tasks)和微任务(Micro Tasks)是事件循环中的两种任务类型:
- 宏任务:通常包括如
setTimeout
,setInterval
,I/O
操作,UI渲染
等。它们在每个事件循环的迭代中按顺序执行。 - 微任务:包括
Promise.then
,MutationObserver
,process.nextTick
等。微任务的优先级高于宏任务,它们会在当前宏任务完成后立即执行,而不是等待下一个事件循环迭代。
2. 宏任务与微任务详解
2.1 宏任务的执行机制
宏任务通常是由浏览器或Node.js环境提供的,它们在事件循环的每个迭代中执行。每个宏任务完成后,事件循环会检查微任务队列,并执行所有微任务,然后再次检查宏任务队列,开始下一个迭代。
2.2 微任务的优先级
微任务的优先级高于宏任务,这意味着在当前宏任务完成后,所有的微任务都会被执行完毕,才会执行下一个宏任务。这种机制确保了微任务能够快速响应,例如Promise
的回调能够尽快执行,提高应用程序的响应性。
2.3 宏任务与微任务的实际应用
在实际开发中,理解和区分宏任务与微任务对于编写高效的JavaScript代码至关重要。例如,使用Promise
可以确保异步操作的顺序执行,而使用setTimeout
可以延迟操作的执行,避免阻塞主线程。
2.4 宏任务与微任务的执行顺序示例
console.log('Script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
});
setTimeout(function() {
console.log('setTimeout2');
}, 0);
Promise.resolve().then(function() {
console.log('promise2');
});
console.log('Script end');
上述代码的输出顺序将会是:
Script start
promise1
promise2
Script end
setTimeout
setTimeout2
这个顺序展示了微任务(promise1
和promise2
)如何在当前宏任务(script
执行)完成后立即执行,而宏任务(setTimeout
)则在下一个事件循环迭代中执行。
2. 宏任务(Macro Tasks)
2.1 宏任务的分类
宏任务主要可以分为以下几类:
- 定时器回调:如
setTimeout
和setInterval
,它们会在指定的时间后触发执行。 - I/O事件:例如来自网络请求的响应、读取文件操作等。
- UI渲染:浏览器在每个事件循环迭代结束时会进行UI的重新渲染。
- DOM事件:如
click
、scroll
等,它们会在对应的事件发生时被添加到宏任务队列中。 - 网络请求:如
XMLHttpRequest
和fetch
请求,它们的响应处理会被作为宏任务添加到队列。
2.2 宏任务的执行顺序
宏任务的执行顺序遵循先进先出(FIFO)的原则。事件循环会在每个迭代中按照以下顺序执行宏任务:
- 执行栈清空:首先,JavaScript引擎会执行调用栈中的所有同步任务。
- 执行微任务队列:一旦调用栈清空,事件循环会立即执行所有等待的微任务。
- 执行宏任务队列:微任务执行完毕后,事件循环会从宏任务队列中取出排在队首的任务执行。
- 重复以上步骤:事件循环会不断重复上述步骤,直到调用栈和任务队列都为空。
这种机制确保了宏任务的执行顺序与它们被添加到队列中的顺序一致,同时也保证了微任务能够在当前宏任务结束后立即得到处理,从而提高了应用程序的响应速度和效率。
例如,考虑以下代码片段:
console.log('Script start'<