0

0

JavaScript中微任务与宏任务区别

幻夢星雲

幻夢星雲

发布时间:2025-07-11 19:48:03

|

989人浏览过

|

来源于php中文网

原创

javascript中微任务优先于宏任务执行。事件循环先执行宏任务,完成后清空微任务队列,再进入下一宏任务。常见宏任务包括整体脚本、settimeout回调、i/o操作、ui渲染等;常见微任务包括promise回调、mutationobserver、queuemicrotask。理解两者执行顺序可避免竞态条件、优化用户体验、提升调试效率。实际开发中,可用微任务处理立即但非阻塞的操作,如promise链式调用或queuemicrotask控制dom布局计算;用宏任务实现延迟执行或任务切片,如settimeout进行非阻塞操作或处理用户输入优先级。错误使用可能导致事件循环阻塞、宏任务饥饿、数据不一致及ui闪烁等问题。

JavaScript中微任务与宏任务区别

在JavaScript的事件循环机制里,微任务和宏任务是两种不同优先级的任务类型。简单来说,微任务总是在当前宏任务执行完毕后、下一个宏任务开始前被清空执行,而宏任务则代表了独立的、更粗粒度的执行单元,它们在不同的事件循环周期中被调度。这意味着微任务拥有更高的执行优先级,能够插队在下一个宏任务之前。

JavaScript中微任务与宏任务区别

理解JavaScript中的任务调度机制,尤其是微任务(Microtask)和宏任务(Macrotask)之间的区别,是编写高效、可预测的异步代码的关键。这不仅仅是理论知识,更是我个人在调试那些“明明顺序没错但结果不对”的异步代码时,屡次发现问题根源所在的地方。

解决方案

立即学习Java免费学习笔记(深入)”;

JavaScript中微任务与宏任务区别

我们先从最基础的事件循环(Event Loop)说起。想象一下,JavaScript的执行环境里有一个永不停歇的循环,它不断地从任务队列里取出任务来执行。这个循环就是事件循环。

宏任务(Macrotasks)

JavaScript中微任务与宏任务区别

宏任务是事件循环中的“大块头”工作。它们是浏览器(或Node.js环境)每次事件循环迭代时处理的单位。一个宏任务执行完毕后,JavaScript引擎会检查微任务队列。常见的宏任务包括:

  • script (整体代码):你的整个JS文件或标签里的代码本身就是一个宏任务。
  • setTimeout() 和 setInterval() 的回调:这些定时器设定的回调函数。
  • I/O 操作:比如文件读写、网络请求(虽然现代Fetch/Ajax更多用Promise,但其底层触发机制仍可能涉及宏任务)。
  • UI 渲染:浏览器会根据需要进行页面重绘
  • postMessage():跨窗口/iframe通信。
  • requestAnimationFrame():虽然与UI渲染紧密相关,但它通常被视为在下一个动画帧前执行的特殊宏任务。

微任务(Microtasks)

微任务则是更细粒度的任务,它们在当前宏任务执行完毕之后,但在下一个宏任务开始之前执行。它们可以被看作是“插队”的任务,优先级高于后续的宏任务。常见的微任务包括:

  • Promise 的回调函数Promise.prototype.then()Promise.prototype.catch()Promise.prototype.finally()
  • MutationObserver 的回调:用于监听DOM变化的API。
  • queueMicrotask():一个显式地将函数放入微任务队列的API。

执行顺序

事件循环的每一次迭代(或称作一个“tick”)大致遵循这样的流程:

  1. 从宏任务队列中取出一个宏任务并执行。
  2. 宏任务执行完毕后,检查微任务队列。
  3. 清空微任务队列,即执行所有在当前宏任务执行期间添加到微任务队列中的微任务,直到队列为空。
  4. 执行UI渲染(如果浏览器判断需要)。
  5. 进入下一个事件循环迭代,从宏任务队列中取出下一个宏任务。

这意味着,即使你用setTimeout(fn, 0)试图让一个任务尽快执行,它也必须等到当前所有微任务都执行完毕后,才有可能在下一个宏任务周期中被调度。而Promise.resolve().then(fn)则会立即将fn放入微任务队列,确保它在当前宏任务结束后立刻执行。

console.log('Start'); // 宏任务

setTimeout(() => {
  console.log('setTimeout callback'); // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('Promise then callback 1'); // 微任务
}).then(() => {
  console.log('Promise then callback 2'); // 微任务
});

console.log('End'); // 宏任务

// 预期输出顺序:
// Start
// End
// Promise then callback 1
// Promise then callback 2
// setTimeout callback

这个例子清楚地展示了微任务如何“插队”在setTimeout之前。

为什么理解微任务和宏任务的执行顺序至关重要?

深入理解微任务和宏任务的执行顺序,远不止是面试时能答对几个概念题那么简单,它直接关系到我们编写的异步代码是否能按预期运行,尤其是在处理复杂的用户交互、数据流或动画时。我个人就曾因为对这块理解不够透彻,导致一些看似随机的UI更新延迟或数据状态不一致的问题。

首先,它能帮助你避免难以追踪的竞态条件和时序错误。当你同时使用setTimeoutPromise来调度任务时,如果不清楚它们的优先级,很容易出现某个操作比预期早或晚执行的情况。比如,你可能期望一个DOM更新在数据处理完成后立即发生,但如果数据处理的回调是微任务,而DOM更新被放到了宏任务队列,那么在数据处理完成后,可能会有其他微任务先执行,甚至浏览器会先进行一次UI渲染,导致你看到一个中间状态,或者更新不及时。

其次,这对于优化用户体验至关重要。长时间运行的同步代码会阻塞主线程,导致页面卡顿。通过将耗时操作拆分成小块,并合理地利用宏任务(如setTimeout(fn, 0))来将其推迟到下一个事件循环周期,可以确保主线程有空闲时间来处理用户输入和UI渲染,从而保持页面的响应性。而微任务则允许你在不阻塞UI的情况下,立即执行一些关键的、依赖于当前状态的后续操作,比如数据验证或状态更新,确保在下一次UI渲染前,数据已经是最新的。

再者,它深化了你对JavaScript并发模型的理解。JS是单线程的,但它通过事件循环和异步任务机制实现了非阻塞的并发。理解微任务和宏任务,就是理解这个非阻塞机制的核心。这不仅让你能写出更健壮的代码,也能更好地预测代码的行为,尤其是在涉及到复杂第三方库或框架时,它们内部也大量依赖这些机制。

最后,它在调试异步代码时提供了强大的心智模型。当异步代码行为异常时,你不再是盲目地添加console.log,而是能够根据任务的类型和优先级,推断出可能出错的地方,比如某个回调是否被“插队”了,或者某个任务是否因为优先级低而被“饿死”了。

在实际开发中,如何利用微任务和宏任务的特性?

在日常开发中,对微任务和宏任务特性的巧妙运用,能让我们的代码更加高效和优雅。这不仅仅是理论层面的认知,更是解决实际问题的一把利器。

利用微任务实现“立即但非阻塞”的后续操作

贞龙多用户商城电子商务系统java版
贞龙多用户商城电子商务系统java版

MALL的中文含义是购物中心,是区别于专卖店和百货公司的一个流行的商业模式,MALL里面是各个独立商家,自由自主的定价,各自管理自己的供销渠道和客户关系。电子商务的MALL模式其实就是对B2C业务模式做了多主体的扩展和延伸。目前具有代表性的电子商务MALL模式就是淘宝商城。比如淘宝电器城,他们的模式更像是做房地产的,阿里巴巴有着繁华的互联网商业物业,只是开了一个名字叫淘宝电器城的大市场而已,没有任

下载

微任务的特性在于,它们会在当前宏任务结束后立刻执行,而不会等到下一个事件循环周期。这使得它们非常适合处理那些需要紧接着当前操作完成,但又不想阻塞后续UI渲染或其他宏任务的场景。

  • Promise 链式调用:这是最常见的用法。当你有一个异步操作(如网络请求)返回Promise时,then()catch()finally()中的回调都会作为微任务执行。这意味着你可以安全地进行数据处理、状态更新等操作,确保这些操作在数据真正可用后立即执行,且在浏览器进行下一次UI渲染前完成。

    function fetchDataAndProcess() {
        fetch('/api/data')
            .then(response => response.json())
            .then(data => {
                // 这是一个微任务,会在fetch成功后立即执行
                // 可以在这里更新组件状态,但不会立即触发UI重绘
                console.log('数据已获取并处理:', data);
                this.setState({ data: data, isLoading: false }); 
            })
            .catch(error => {
                console.error('数据获取失败:', error); // 也是微任务
                this.setState({ error: error, isLoading: false });
            });
    }
  • queueMicrotask() 的精准控制:当你想确保某个函数在当前脚本执行完毕后,但在任何宏任务(包括UI渲染)之前执行时,queueMicrotask()就显得尤为有用。比如,你可能在组件的生命周期方法中批量修改了DOM,然后希望在所有修改完成后,立即执行一些基于最新DOM状态的计算或副作用,而不想等到下一个动画帧。

    function updateComplexUI() {
        // 假设这里有很多同步的DOM操作
        element1.style.width = '100px';
        element2.textContent = 'New Text';
        // ...
    
        // 确保在所有DOM操作完成后,立即执行后续的布局计算,而不是等到下一个requestAnimationFrame
        queueMicrotask(() => {
            const currentWidth = element1.offsetWidth;
            console.log('DOM更新后的宽度:', currentWidth);
            // 可以在这里触发一些依赖于最新布局的逻辑
        });
    }

利用宏任务实现“延迟执行”和“任务切片”

宏任务的特点是它们会等到当前微任务队列清空后,在下一个事件循环周期中执行。这使得它们非常适合用于延迟执行、任务切片以及避免阻塞主线程的场景。

  • setTimeout(fn, 0) 进行任务切片/非阻塞操作:将一个耗时操作分解成多个小块,并使用setTimeout(fn, 0)将它们推迟到后续的事件循环周期执行。这样可以避免长时间占用主线程,确保UI的响应性。

    function processLargeArray(arr) {
        let i = 0;
        const batchSize = 1000;
    
        function processBatch() {
            const start = i;
            const end = Math.min(i + batchSize, arr.length);
            for (let j = start; j < end; j++) {
                // 模拟耗时计算
                // console.log('Processing item:', arr[j]);
            }
            i = end;
    
            if (i < arr.length) {
                // 将下一个批次的处理推迟到下一个宏任务,允许浏览器进行UI渲染
                setTimeout(processBatch, 0);
            } else {
                console.log('Large array processing complete.');
            }
        }
        processBatch();
    }
    
    // 假设有一个很大的数组
    const largeArray = Array.from({ length: 100000 }, (_, index) => index);
    // processLargeArray(largeArray);
    // console.log('主线程未被阻塞,可以继续其他操作...');
  • 处理用户输入和UI更新的优先级:在某些情况下,你可能希望某个操作在所有当前脚本执行完毕,甚至在UI更新之后再执行,以确保用户能看到最新的UI状态。这时,setTimeout就很有用。例如,一个动画的启动,可能需要在DOM完全准备好后才开始。

通过这种方式,我们不仅能写出功能正确的代码,还能确保它在用户体验层面是流畅和响应迅速的。

微任务和宏任务处理不当可能导致哪些常见问题?

在我的开发实践中,处理微任务和宏任务不当引发的问题,往往比表面看起来要隐蔽得多。它们不会直接报错,但会表现为性能瓶颈、UI闪烁、数据不一致,甚至应用程序假死。这些问题尤其在异步操作密集、交互复杂的应用中更容易浮现。

1. 事件循环阻塞 (Event Loop Blocking)

这是最直接也最常见的问题。如果一个宏任务(比如一个长时间运行的同步计算,或者一个回调函数中包含了大量耗时操作)执行时间过长,它就会长时间霸占主线程,导致浏览器无法处理用户输入、无法进行UI渲染,给用户的感觉就是页面“卡死”了。

// 宏任务中的同步阻塞
setTimeout(() => {
    console.log('setTimeout start');
    // 模拟一个非常耗时的同步计算
    let sum = 0;
    for (let i = 0; i < 1000000000; i++) {
        sum += i;
    }
    console.log('setTimeout end', sum);
}, 0);

console.log('主线程其他操作'); // 这条会立即打印,但如果上面setTimeout中的计算量更大,用户界面就会卡住
// 用户点击、动画等都会延迟响应

虽然这看起来是宏任务的问题,但如果你的微任务逻辑复杂且连续触发,也可能间接导致宏任务无法及时执行,进而影响UI。

2. 微任务饥饿 (Microtask Starvation) 的反面——宏任务饥饿

理论上,微任务队列应该在每个宏任务之后被完全清空。但如果微任务队列被无限地填充,比如一个Promise链不当地递归调用,或者一个MutationObserver的回调持续触发DOM变化,导致新的微任务不断产生,那么宏任务队列中的任务就永远得不到执行的机会。这会导致后续的UI渲染、setTimeout回调等宏任务被“饿死”,页面完全失去响应。

// 这是一个极端例子,会导致宏任务饥饿
let count = 0;
function createInfiniteMicrotasks() {
    Promise.resolve().then(() => {
        console.log('Microtask', count++);
        if (count < 100000) { // 实际中可能没有这个限制,导致无限循环
            createInfiniteMicrotasks(); // 递归调用,不断添加微任务
        } else {
            console.log('Microtask loop finished.');
        }
    });
}

createInfiniteMicrotasks();

setTimeout(() => {
    console.log('setTimeout will be greatly delayed or never run');
}, 0);

console.log('Script end');
// 在这个例子中,setTimeout 可能会在大量微任务执行后才运行,
// 如果微任务无限循环,setTimeout 甚至可能永远不会执行。

3. 竞态条件和数据不一致

当多个异步操作(宏任务和微任务)同时进行,并且它们都尝试修改同一个数据源或DOM元素时,由于执行顺序的不确定性(如果对优先级理解不清),就可能出现竞态条件,导致数据状态不一致或UI显示错误。比如,一个微任务更新了数据,但一个依赖旧数据的宏任务却在微任务更新前进行了UI渲染,或者反之。

let sharedData = 'initial';

// 宏任务:可能在微任务之前或之后读取sharedData,取决于宏任务的调度
setTimeout(() => {
    console.log('setTimeout reads:', sharedData); // 读到的可能是'initial'或'updated'
}, 0);

// 微任务:立即更新sharedData
Promise.resolve().then(() => {
    sharedData = 'updated';
    console.log('Promise updates:', sharedData);
});

console.log('Script end reads:', sharedData); // 立即读到 'initial'

在这个例子中,setTimeout的回调何时执行,以及它读到sharedData的哪个值,取决于事件循环的精确时机和Promise微任务的执行。如果setTimeout的回调在微任务之前被处理(这在单次事件循环中不会发生,但如果sharedData被其他宏任务修改,就会变得复杂),就可能出现问题。关键在于,如果开发者不清楚微任务和宏任务的优先级,就容易误判何时数据状态是稳定的。

4. UI 闪烁或不必要的重绘

如果你在同一个事件循环周期内,先通过同步代码或微任务修改了DOM,然后又在同一个宏任务的末尾或下一个宏任务中进行了额外的DOM操作,浏览器可能会进行多次不必要的重绘,或者出现UI闪烁。理想情况下,我们希望在一次宏任务中,所有DOM相关的修改都完成后,浏览器再进行一次统一的重绘。requestAnimationFrame在这方面提供了更好的控制,因为它将回调安排在浏览器下一次重绘之前。

避免这些问题,核心在于对事件循环的深入理解,以及在编写异步代码时,有意识地选择正确的任务类型来调度你的操作。当你面对一个异步问题时,不妨在脑中模拟一下事件循环的“tick”过程,看看你的任务会在哪个阶段被执行。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
ajax教程
ajax教程

php中文网为大家带来ajax教程合集,Ajax是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,Ajax可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。php中文网还为大家带来ajax的相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

160

2023.06.14

ajax中文乱码解决方法
ajax中文乱码解决方法

ajax中文乱码解决方法有设置请求头部的字符编码、在服务器端设置响应头部的字符编码和使用encodeURIComponent对中文进行编码。本专题为大家提供ajax中文乱码相关的文章、下载、课程内容,供大家免费下载体验。

160

2023.08.31

ajax传递中文乱码怎么办
ajax传递中文乱码怎么办

ajax传递中文乱码的解决办法:1、设置统一的编码方式;2、服务器端编码;3、客户端解码;4、设置HTTP响应头;5、使用JSON格式。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

117

2023.11.15

ajax网站有哪些
ajax网站有哪些

使用ajax的网站有谷歌、维基百科、脸书、纽约时报、亚马逊、stackoverflow、twitter、hacker news、shopify和basecamp等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

235

2024.09.24

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

525

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

525

2023.08.10

go语言 数组和切片
go语言 数组和切片

本专题整合了go语言数组和切片的区别与含义,阅读专题下面的文章了解更多详细内容。

46

2025.09.03

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

515

2023.06.20

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

14

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
如何进行WebSocket调试
如何进行WebSocket调试

共1课时 | 0.1万人学习

TypeScript全面解读课程
TypeScript全面解读课程

共26课时 | 5.1万人学习

前端工程化(ES6模块化和webpack打包)
前端工程化(ES6模块化和webpack打包)

共24课时 | 5.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号