Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JavaScript 事件循环(Event Loop)深度剖析

JavaScript 事件循环(Event Loop)深度剖析

作者头像
全栈若城
发布于 2025-02-19 23:49:13
发布于 2025-02-19 23:49:13
457016
代码可运行
举报
文章被收录于专栏:若城技术专栏若城技术专栏
运行总次数:16
代码可运行

一、事件循环的本质

1.1 什么是事件循环

事件循环(Event Loop)是 JavaScript 实现异步编程的核心机制,它是为了解决 JavaScript 单线程执行模型下的非阻塞操作而设计的。事件循环负责协调和调度以下任务:

  • 执行同步代码
  • 管理回调队列
  • 处理异步事件
  • 执行微任务和宏任务
1.2 为什么 JavaScript 是单线程的?

JavaScript 最初设计为浏览器脚本语言,主要用于处理页面交互。采用单线程模型的主要原因是:

  1. DOM 操作的一致性:如果是多线程,当两个线程同时操作 DOM(一个添加节点,一个删除节点),浏览器难以协调。
  2. 简化编程模型:单线程避免了多线程编程中的复杂性,如死锁、资源竞争等问题。
  3. 符合大多数 Web 应用场景:Web 应用主要是 I/O 密集型,而不是计算密集型。

二、事件循环的核心概念

2.1 运行时环境的组成
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
A[运行时环境] --> B[调用栈 Call Stack]
A --> C[任务队列 Task Queue]
A --> D[微任务队列 Microtask Queue]
A --> E[Web APIs]
B --> F[正在执行的代码]
C --> G[宏任务 setTimeout/setInterval等]
D --> H[微任务 Promise/MutationObserver等]
E --> I[浏览器提供的API DOM/AJAX]
2.2 任务类型详解
1. 同步任务

直接在主线程上排队执行的任务。特点:

  • 立即执行
  • 会阻塞后续代码执行
  • 按照代码顺序执行
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 同步任务示例
console.log('1'); // 立即执行
const result = expensive_computation(); // 会阻塞后续代码
console.log('2'); // 等待上面的计算完成才执行
2. 异步任务

不进入主线程,而是进入任务队列的任务。分为宏任务(Macrotask)和微任务(Microtask)。

2.2.1 宏任务(Macrotask)

由宿主环境(浏览器、Node.js)提供的异步 API。主要包括:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 1. setTimeout/setInterval
setTimeout(() => {
    console.log('延迟执行');
}, 1000);

// 2. I/O操作
fs.readFile('file.txt', (err, data) => {
    console.log('文件读取完成');
});

// 3. UI渲染
requestAnimationFrame(() => {
    updateUI();
});

// 4. setImmediate(Node.js特有)
setImmediate(() => {
    console.log('下一轮事件循环执行');
});
2.2.2 微任务(Microtask)

由 JavaScript 引擎提供,优先级高于宏任务。主要包括:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 1. Promise回调
new Promise((resolve) => {
    resolve('success');
}).then(result => {
    console.log(result);
});

// 2. MutationObserver
const observer = new MutationObserver(() => {
    console.log('DOM变化了');
});
observer.observe(document.body, { childList: true });

// 3. process.nextTick(Node.js)
process.nextTick(() => {
    console.log('nextTick执行');
});

三、事件循环执行顺序

3.1 完整的事件循环流程
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
A[开始执行代码] --> B[执行同步代码]
B --> C{执行栈是否为空?}
C -->|| B
C -->|| D[执行所有微任务]
D --> E{微任务队列是否为空?}
E -->|| D
E -->|| F[执行一个宏任务]
F --> G[渲染页面]
G --> C
3.2 实际案例分析
案例 1:Promise 和 setTimeout 的执行顺序
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
console.log('开始执行');

setTimeout(() => {
    console.log('setTimeout 1');
    Promise.resolve().then(() => {
        console.log('setTimeout 1 中的 Promise');
    });
}, 0);

new Promise((resolve) => {
    console.log('Promise 初始化');
    resolve();
}).then(() => {
    console.log('Promise 第一个 then');
    setTimeout(() => {
        console.log('Promise 中的 setTimeout');
    }, 0);
}).then(() => {
    console.log('Promise 第二个 then');
});

console.log('结束执行');

/* 输出顺序:
开始执行
Promise 初始化
结束执行
Promise 第一个 then
Promise 第二个 then
setTimeout 1
setTimeout 1 中的 Promise
Promise 中的 setTimeout
*/

分析过程:

  1. 同步代码执行:打印"开始执行"、“Promise 初始化”、“结束执行”
  2. 微任务队列执行:打印"Promise 第一个 then"、“Promise 第二个 then”
  3. 宏任务队列执行:
    • 第一个 setTimeout:打印"setTimeout 1",产生新的微任务
    • 微任务执行:打印"setTimeout 1 中的 Promise"
    • 第二个 setTimeout:打印"Promise 中的 setTimeout"
案例 2:async/await 的执行机制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async function async1() {
    console.log('async1 开始');
    await async2();
    console.log('async1 结束');
}

async function async2() {
    console.log('async2');
}

console.log('脚本开始');

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

async1();

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

console.log('脚本结束');

/* 输出顺序:
脚本开始
async1 开始
async2
Promise
脚本结束
async1 结束
Promise.then
setTimeout
*/

分析过程:

  1. 同步代码执行:
    • 打印"脚本开始"
    • 执行 async1():打印"async1 开始"和"async2"
    • 执行 Promise:打印"Promise"
    • 打印"脚本结束"
  2. 微任务队列执行:
    • await 后的代码:打印"async1 结束"
    • Promise.then:打印"Promise.then"
  3. 宏任务队列执行:
    • setTimeout:打印"setTimeout"

四、框架中的事件循环应用

4.1 Vue 中的 nextTick
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Vue组件中的实际应用
export default {
    data() {
        return {
            message: 'Hello'
        }
    },
    methods: {
        async updateMessage() {
            this.message = 'Updated';
            console.log(this.$el.textContent); // 仍然是 'Hello'
            await this.$nextTick();
            console.log(this.$el.textContent); // 现在是 'Updated'
        }
    }
}
4.2 React 中的调度机制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// React中的优先级调度示例
function App() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        // 低优先级更新
        const timer = setInterval(() => {
            setCount(c => c + 1);
        }, 1000);
        return () => clearInterval(timer);
    }, []);

    const handleClick = () => {
        // 高优先级更新
        ReactDOM.flushSync(() => {
            setCount(0);
        });
    };

    return (
        <div onClick={handleClick}>{count}</div>
    );
}

五、性能优化建议

  1. 合理使用微任务和宏任务
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 不推荐
setTimeout(() => {
    // 处理大量数据
}, 0);

// 推荐
Promise.resolve().then(() => {
    // 处理大量数据
});
  1. 避免长时间占用主线程
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 不推荐
function processData(items) {
    items.forEach(item => {
        heavyComputation(item);
    });
}

// 推荐
async function processData(items) {
    for (let item of items) {
        if (needToYield()) {
            await new Promise(resolve => setTimeout(resolve, 0));
        }
        heavyComputation(item);
    }
}
  1. 使用 Web Workers 处理计算密集型任务
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: complexData });
worker.onmessage = function(e) {
    console.log('计算结果:', e.data);
};

// worker.js
self.onmessage = function(e) {
    const result = heavyComputation(e.data);
    self.postMessage(result);
};
总结
  • 事件循环核心:单线程通过任务队列实现非阻塞。
  • 执行顺序铁律:同步 → 微任务 → 宏任务 → 循环。
  • async/await 本质:基于 Promise 的语法糖,通过微任务实现异步控制。
  • 优化关键:减少主线程阻塞,合理利用任务优先级。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
事件循环Event Loop
js是单线程,js任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:1)同步任务 2)异步任务
程序员法医
2022/08/11
1.1K0
事件循环Event Loop
教你做一些动图,学习一下 EventLoop
https://juejin.cn/post/6969028296893792286
程序员小猿
2021/07/06
4640
js事件循环与macro&micro任务队列
这个题目主要是考察对同步任务、异步任务:setTimeout、promise、async/await的执行顺序的理解程度。(建议大家也自己先做一下o)
loveX001
2022/12/16
5580
JS:事件循环机制(Event Loops)
* 为什么 `setTimeout()` 设定的时间是 0 毫秒,但 1 却是在最后输出的?
前端小tips
2021/12/08
2.4K0
JS:事件循环机制(Event Loops)
面试题:说说事件循环机制(满分答案来了)
JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
winty
2020/03/19
4.1K0
面试题:说说事件循环机制(满分答案来了)
【前端进阶】深入浅出浏览器事件循环【内附练习题】
我们看一个很经典的图,这张图基本可以概括了事件循环(该图来自演讲—— 菲利普·罗伯茨:到底什么是Event Loop呢?| 欧洲 JSConf 2014[1])后面演示用的 Loupe[2] 也是该演讲者写的((Loupe是一种可视化工具,可以帮助您了解JavaScript的调用堆栈/事件循环/回调队列如何相互影响))
GopalFeng
2020/10/27
1.2K0
【前端进阶】深入浅出浏览器事件循环【内附练习题】
说说Event Loop事件循环、微任务、宏任务
JS是一门单线程语言,单线程就意味着,所有的任务需要排队,前一个任务结束,才会执行下一个任务。这样所导致的问题是:如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的觉。为了解决这个问题,JS中出现了同步和异步。他们的本质区别是:一条流水线上各个流程的执行顺序不同。在讲JS任务执行机制前,先要了解一下什么是同步任务与异步任务。
loveX001
2022/12/14
7950
Javascript运行机制(Event loop)原理知道吗?不懂就来看看吧,一篇文章让你搞定
在写这篇文章之前,我看了很多写的不错的文章,但是每篇文章都有那么几个关键的点,很多篇文章凑在一起综合来看,才可以对这些概念有较为深入的理解。所以,我就想要写这么一篇文章,结合自己的理解以及示例代码,用最通俗的文字表达出来。
吴佳
2022/09/26
5860
什么是事件循环 Eventloop
我们先不着急明白事件循环是什么。先从它的起源入手。大家都知道JavaScript是同步的,也就是单线程,原因是因为如果不使用单线程,在操作DOM时可能会出现一些问题,比如我们有两个事件,一个是删除div,一个是添加div,他们的执行顺序不同,导致的结果也将截然不同。比如当前有div1和div2,如果先执行删除后添加,那么得到的就是div1和div2,但是如果是先执行添加后删除,那么得到的还是div1和div2。为了避免这种逻辑上的混乱,因此规定JavaScript是单线程的。
HelloWorldZ
2024/03/20
1440
JS事件循环之宏任务和微任务
众所周知,JS 是一门单线程语言,可是浏览器又能很好的处理异步请求,那么到底是为什么呢?
九旬
2020/10/23
1.2K0
JS事件循环之宏任务和微任务
JavaScript: 从 Event Loop 到 Promise (常见问题分析)
总结一点:JavaScript是单线程的,但是浏览器不是单线程的。一些I/O操作,定时器的计时和事件监听是由其他线程完成的。
西南_张家辉
2021/02/02
7610
你可能不知道的 JavaScript Event Loop
实际上,谈到任务队列,绝大多数人第一反应就是 同步任务/异步任务、宏任务/微任务,在很多博客和帖子中也有详细的说明。在看了 winter 老师在极客时间《重学前端》这门课,发现自己对 Event Loop 了解的不够深入,是从一道题目开始:
Meteors
2021/12/08
3000
你可能不知道的 JavaScript Event Loop
22道js输出顺序问题,你能做出几道
单线程是 JavaScript 核心特征之一。这意味着,在 JS 中所有任务都需要排队执行,前一个任务结束,才会执行后一个任务。
loveX001
2022/12/14
2.1K0
一文搞懂javascript事件循环原理?「前端每日一题v22.11.16」
了解javascript的第一步,就是要了解事件循环机制。但是要真正的了解javascript的事件循环机制并不容易,因为它是javascript引擎最基础的部分。它可以让单线程的javascript以非阻塞方式执行
FE情报局
2022/12/05
3070
一文搞懂javascript事件循环原理?「前端每日一题v22.11.16」
从async/await面试题看宏观任务和微观任务
这道题主要考察的是事件循环中函数执行顺序的问题,其中包括async ,await,setTimeout,Promise函数。下面来说一下本题中涉及到的知识点。
用户5495275
2020/11/11
3.1K0
从async/await面试题看宏观任务和微观任务
记两道关于事件循环的题
这里的关键其实是搞清楚 await async2() 做了什么事情。我以为在 async1 内部,async2 被调用之后,就会继续往后执行,因此是先打印 async1 end ,再回到主栈打印 start。然而 async2 里面包含了一个异步操作,在异步操作得到结果之前,其实是会跳出当前 async1 函数的执行栈,优先去执行同步任务的,所以这里其实会先执行 start,再去执行 async1 end。具体地说:
Chor
2020/05/18
4180
宏任务和微任务到底是什么?
这个就涉及到JavaScript事件轮询中的宏任务和微任务。那么,你能说清楚到底宏任务和微任务是什么?是谁发起的?为什么微任务的执行要先于宏任务呢?
娜姐
2020/09/22
5.1K1
宏任务和微任务到底是什么?
详解JavaScript 执行机制
而第二个例子则可能优点小问题,JavaScript 从上到下执行,那么遇到 0s 的计时器函数,就应该先输出 2 才对啊。这就是因为后面要提到的 JavaScript 执行机制导致的啦,因为 setTimeout 是异步任务。
赤蓝紫
2023/01/05
7430
详解JavaScript 执行机制
深入理解JavaScript的事件循环(Event Loop)
在两个环境下的Event Loop实现是不一样的,在浏览器中基于 规范 来实现,不同浏览器可能有小小区别。在Node中基于 libuv 这个库来实现
书童小二
2018/09/03
1.2K0
深入理解JavaScript的事件循环(Event Loop)
你不知道的 Event Loop
笔者最近忙着做项目之类的,文章输出遗落下了一段时间,这次我们就来聊一个面试中一个比较重要的知识点 —— Event Loop
一只图雀
2020/04/07
9160
相关推荐
事件循环Event Loop
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档