🔹 一、为什么需要任务机制?
JavaScript 是 单线程 的(同一时间只能执行一件事)
为了保证页面流畅,JS 采用了 事件循环 (Event Loop) 来调度任务
任务被分为两类:
-
宏任务 (Macro Task) :整体执行环境,主流程的大任务,例如整个
script
标签 -
微任务 (Micro Task):更细粒度的任务,会在宏任务之后、下一个宏任务开始前执行。
这样做的好处可以 避免长任务阻塞,让高优先级的微任务能更快执行
🔹 二、宏任务(Macro Task)
-
script
整体代码(程序入口) -
setTimeout
-
setInterval
-
setImmediate
(Node.js 独有) -
I/O
操作(如文件读写、网络请求回调) -
UI 渲染任务
👉 特点:每次执行宏任务时,会从头到尾完整运行完毕,期间不会被打断
🔹 三、微任务(Micro Task)
-
Promise.then/catch/finally
-
MutationObserver
-
queueMicrotask
-
Node.js 的
process.nextTick
👉 特点:
-
在 当前宏任务执行完毕后 ,立即执行所有微任务,直至清空
-
微任务的优先级 高于 下一个宏任务
🔹 四、事件循环 (Event Loop) 执行顺序
流程:
-
执行一个宏任务(如
script
整体代码) -
执行过程中遇到微任务,放入微任务队列
-
宏任务执行完毕后,立即执行所有微任务(FIFO)
-
微任务执行完毕 → 渲染 UI → 进入下一个宏任务
-
循环往复
🔹 五、经典例子
javascript
console.log('start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve()
.then(() => {
console.log('promise1');
})
.then(() => {
console.log('promise2');
});
console.log('end');
执行顺序解析:
-
console.log('start')
→ 同步执行 -
setTimeout
→ 放入宏任务队列 -
Promise.then
→ 放入微任务队列 -
console.log('end')
→ 同步执行 -
宏任务(script 主体)执行完 → 执行微任务队列
-
promise1
-
promise2
-
-
执行下一个宏任务 →
setTimeout
输出结果:
arduino
start
end
promise1
promise2
setTimeout
🔹 六、Node.js 中的差异
Node.js 的事件循环更复杂,分为多个阶段(timers → I/O callbacks → idle → poll → check → close callbacks),其中:
-
process.nextTick
优先级最高,在当前操作结束后立即执行(甚至比微任务还快) -
Promise.then
属于微任务 -
setTimeout / setImmediate
属于宏任务,但执行时机不同
🔹 七、学习总结
-
宏任务 :大任务(
setTimeout
、I/O
、script
) -
微任务 :小任务(
Promise.then
、queueMicrotask
) -
执行顺序:宏任务 → 清空微任务 → 渲染 → 下一个宏任务
🔹 八、案例可视化代码:
xml
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript 微任务与宏任务机制</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h1 {
color: #2c3e50;
margin-bottom: 10px;
}
.description {
color: #7f8c8d;
max-width: 800px;
margin: 0 auto;
}
.content {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 30px;
}
.code-example, .visualization {
flex: 1;
min-width: 300px;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h2 {
color: #3498db;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #f0f0f0;
}
.code-container {
background: #2d2d2d;
color: #f8f8f2;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
margin-bottom: 15px;
font-family: 'Fira Code', monospace;
}
.code-line {
margin-bottom: 8px;
display: block;
}
.comment {
color: #75715e;
}
.macro {
color: #e6db74;
}
.micro {
color: #a6e22e;
}
.console {
background: #2d2d2d;
color: #f8f8f2;
padding: 15px;
border-radius: 5px;
height: 200px;
overflow-y: auto;
font-family: 'Fira Code', monospace;
}
.log {
margin-bottom: 5px;
}
.macro-log {
color: #e6db74;
}
.micro-log {
color: #a6e22e;
}
.controls {
text-align: center;
margin-top: 20px;
}
button {
background: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background 0.3s;
}
button:hover {
background: #2980b9;
}
.event-loop {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 20px;
position: relative;
}
.stack, .microtask-queue, .macrotask-queue {
flex: 1;
padding: 15px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
min-height: 200px;
}
.queue-title {
text-align: center;
font-weight: bold;
margin-bottom: 10px;
color: #2c3e50;
}
.task {
padding: 8px;
margin: 5px 0;
border-radius: 5px;
text-align: center;
}
.micro-task {
background: #a6e22e;
color: white;
}
.macro-task {
background: #e6db74;
color: #333;
}
.arrow {
font-size: 24px;
padding: 0 10px;
}
.explanation {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-top: 30px;
}
.step {
margin-bottom: 15px;
padding: 10px;
border-left: 4px solid #3498db;
background: #f8f9fa;
}
.highlight {
background: #fff8e1;
padding: 2px 5px;
border-radius: 3px;
font-weight: bold;
}
@media (max-width: 768px) {
.content {
flex-direction: column;
}
.event-loop {
flex-direction: column;
gap: 20px;
}
.arrow {
display: none;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>JavaScript 微任务与宏任务机制</h1>
<p class="description">了解微任务(Microtask)和宏任务(Macrotask)的区别以及它们在事件循环(Event Loop)中的执行顺序。</p>
</header>
<div class="content">
<div class="code-example">
<h2>代码示例</h2>
<div class="code-container">
<span class="code-line"><span class="comment">// 同步代码(宏任务)</span></span>
<span class="code-line">console.log('<span class="macro">Script start</span>');</span>
<span class="code-line"></span>
<span class="code-line"><span class="comment">// 宏任务</span></span>
<span class="code-line">setTimeout(() => {</span>
<span class="code-line"> console.log('<span class="macro">setTimeout</span>');</span>
<span class="code-line">}, 0);</span>
<span class="code-line"></span>
<span class="code-line"><span class="comment">// 微任务</span></span>
<span class="code-line">Promise.resolve()</span>
<span class="code-line"> .then(() => console.log('<span class="micro">Promise 1</span>'))</span>
<span class="code-line"> .then(() => console.log('<span class="micro">Promise 2</span>'));</span>
<span class="code-line"></span>
<span class="code-line"><span class="comment">// 同步代码(宏任务)</span></span>
<span class="code-line">console.log('<span class="macro">Script end</span>');</span>
</div>
<div class="console">
<div class="log">控制台输出将显示在这里...</div>
</div>
<div class="controls">
<button id="run-btn">运行代码</button>
<button id="reset-btn">重置</button>
</div>
</div>
<div class="visualization">
<h2>事件循环可视化</h2>
<div class="event-loop">
<div class="stack">
<div class="queue-title">调用栈 (Call Stack)</div>
<div id="stack-tasks"></div>
</div>
<div class="arrow">→</div>
<div class="microtask-queue">
<div class="queue-title">微任务队列 (Microtask Queue)</div>
<div id="micro-tasks"></div>
</div>
<div class="arrow">→</div>
<div class="macrotask-queue">
<div class="queue-title">宏任务队列 (Macrotask Queue)</div>
<div id="macro-tasks"></div>
</div>
</div>
</div>
</div>
<div class="explanation">
<h2>执行机制解析</h2>
<div class="step">
<p>1. JavaScript 是单线程的,通过事件循环机制处理异步操作</p>
</div>
<div class="step">
<p>2. 事件循环每次从宏任务队列中取出一个任务执行</p>
</div>
<div class="step">
<p>3. 当调用栈为空时,会先检查微任务队列并执行所有微任务</p>
</div>
<div class="step">
<p>4. 微任务执行完成后,浏览器可能会进行渲染,然后开始下一次事件循环</p>
</div>
<div class="step">
<p>5. 常见的宏任务:<span class="highlight">setTimeout</span>, <span class="highlight">setInterval</span>, <span class="highlight">I/O操作</span>, <span class="highlight">UI渲染</span></p>
</div>
<div class="step">
<p>6. 常见的微任务:<span class="highlight">Promise.then()</span>, <span class="highlight">MutationObserver</span>, <span class="highlight">process.nextTick</span> (Node.js)</p>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const runBtn = document.getElementById('run-btn');
const resetBtn = document.getElementById('reset-btn');
const consoleEl = document.querySelector('.console');
const stackTasks = document.getElementById('stack-tasks');
const microTasks = document.getElementById('micro-tasks');
const macroTasks = document.getElementById('macro-tasks');
let originalConsoleLog = console.log;
// 初始化任务队列显示
function initQueues() {
stackTasks.innerHTML = '';
microTasks.innerHTML = '';
macroTasks.innerHTML = '';
// 添加初始宏任务
addMacroTask('Script (整体代码)');
}
// 添加任务到调用栈
function addToStack(task, isMicro = false) {
const taskEl = document.createElement('div');
taskEl.className = `task ${isMicro ? 'micro-task' : 'macro-task'}`;
taskEl.textContent = task;
stackTasks.appendChild(taskEl);
// 模拟执行后移除
setTimeout(() => {
stackTasks.removeChild(taskEl);
}, 1000);
}
// 添加微任务
function addMicroTask(task) {
const taskEl = document.createElement('div');
taskEl.className = 'task micro-task';
taskEl.textContent = task;
microTasks.appendChild(taskEl);
return taskEl;
}
// 添加宏任务
function addMacroTask(task) {
const taskEl = document.createElement('div');
taskEl.className = 'task macro-task';
taskEl.textContent = task;
macroTasks.appendChild(taskEl);
return taskEl;
}
// 从队列中移除任务
function removeTaskFromQueue(queue, taskEl) {
queue.removeChild(taskEl);
}
// 重写 console.log 以捕获输出
function captureConsoleLog() {
console.log = function(message) {
originalConsoleLog.apply(console, arguments);
const logEl = document.createElement('div');
logEl.className = 'log';
if (message.includes('Promise')) {
logEl.classList.add('micro-log');
} else if (message.includes('setTimeout') || message.includes('Script')) {
logEl.classList.add('macro-log');
}
logEl.textContent = message;
consoleEl.appendChild(logEl);
consoleEl.scrollTop = consoleEl.scrollHeight;
};
}
// 运行演示
function runDemo() {
consoleEl.innerHTML = '';
initQueues();
captureConsoleLog();
// 模拟事件循环执行过程
setTimeout(() => {
console.log('Script start');
addToStack('console.log');
// 添加setTimeout到宏任务队列
const setTimeoutTask = addMacroTask('setTimeout回调');
// 添加Promise到微任务队列
const promiseTask1 = addMicroTask('Promise.then 1');
const promiseTask2 = addMicroTask('Promise.then 2');
console.log('Script end');
addToStack('console.log');
// 模拟调用栈清空后处理微任务队列
setTimeout(() => {
// 执行第一个微任务
removeTaskFromQueue(microTasks, promiseTask1);
addToStack('Promise.then 1', true);
console.log('Promise 1');
// 执行第二个微任务
setTimeout(() => {
removeTaskFromQueue(microTasks, promiseTask2);
addToStack('Promise.then 2', true);
console.log('Promise 2');
// 微任务执行完成后,执行下一个宏任务
setTimeout(() => {
removeTaskFromQueue(macroTasks, setTimeoutTask);
addToStack('setTimeout回调');
console.log('setTimeout');
}, 500);
}, 500);
}, 1000);
}, 500);
}
// 重置演示
function resetDemo() {
console.log = originalConsoleLog;
consoleEl.innerHTML = '<div class="log">控制台输出将显示在这里...</div>';
initQueues();
}
// 初始化
initQueues();
// 绑定事件
runBtn.addEventListener('click', runDemo);
resetBtn.addEventListener('click', resetDemo);
});
</script>
</body>
</html>