中级前端进阶方向 第一步)JavaScript 微任务 / 宏任务机制

🤙《中级前端进阶方向指南》

🔹 一、为什么需要任务机制?

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) 执行顺序

流程:

  1. 执行一个宏任务(如 script 整体代码)

  2. 执行过程中遇到微任务,放入微任务队列

  3. 宏任务执行完毕后,立即执行所有微任务(FIFO)

  4. 微任务执行完毕 → 渲染 UI → 进入下一个宏任务

  5. 循环往复

🔹 五、经典例子

javascript 复制代码
console.log('start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

Promise.resolve()
  .then(() => {
    console.log('promise1');
  })
  .then(() => {
    console.log('promise2');
  });

console.log('end');

执行顺序解析:

  1. console.log('start') → 同步执行

  2. setTimeout → 放入宏任务队列

  3. Promise.then → 放入微任务队列

  4. console.log('end') → 同步执行

  5. 宏任务(script 主体)执行完 → 执行微任务队列

    • promise1

    • promise2

  6. 执行下一个宏任务 → 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 属于宏任务,但执行时机不同

🔹 七、学习总结

  • 宏任务 :大任务(setTimeoutI/Oscript

  • 微任务 :小任务(Promise.thenqueueMicrotask

  • 执行顺序:宏任务 → 清空微任务 → 渲染 → 下一个宏任务

🔹 八、案例可视化代码:

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>

相关推荐
小宋搬砖第一名15 小时前
深入剖析 Webpack AsyncQueue 源码:异步任务调度的核心
前端·webpack
JarvanMo15 小时前
Flutter 的 Hero Widget 有一个隐藏的超能力(大多数开发者从未使用过)
前端
WindrunnerMax15 小时前
从零实现富文本编辑器#7-基于组合事件的半受控输入模式
前端·前端框架·github
Cache技术分享15 小时前
177. Java 注释 - 重复注释
前端·后端
rocksun16 小时前
如何使用Enhance构建App:后端优先框架指南
前端·前端框架·前端工程化
Mike的AI工坊16 小时前
[知识点记录]createWebHistory的用法
前端
红色石头本尊16 小时前
8-Gsap动画库基本使用与原理
前端
小高00716 小时前
🎯v-for 先还是 v-if 先?Vue2/3 编译真相
前端·javascript·vue.js
原生高钙16 小时前
大模型的流式响应实现
前端·ai编程