<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>浏览器 on 日日是好日</title><link>https://jjjjjjy.github.io/posts/browser/</link><description>Recent content in 浏览器 on 日日是好日</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Thu, 12 Mar 2026 21:13:47 +0800</lastBuildDate><atom:link href="https://jjjjjjy.github.io/posts/browser/index.xml" rel="self" type="application/rss+xml"/><item><title>浏览器事件循环</title><link>https://jjjjjjy.github.io/posts/browser/event-loop/</link><pubDate>Thu, 12 Mar 2026 21:13:47 +0800</pubDate><guid>https://jjjjjjy.github.io/posts/browser/event-loop/</guid><description>&lt;h2 id="谨记"&gt;谨记
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;JavaScript 在浏览器中是单线程执行的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但需要注意：&lt;/p&gt;
&lt;p&gt;JavaScript 本身是单线程执行的，但浏览器运行环境是多线程的。
异步任务由浏览器的其他线程处理，完成后通过 &lt;strong&gt;事件循环（Event Loop）&lt;/strong&gt; 回调到 JS 主线程执行。
如果需要真正的并行计算，可以通过 &lt;strong&gt;Web Worker&lt;/strong&gt; 创建新的 JS 线程。&lt;/p&gt;
&lt;h4 id="浏览器事件循环是什么"&gt;浏览器事件循环是什么
&lt;/h4&gt;&lt;p&gt;JavaScript 在浏览器中是 &lt;strong&gt;单线程执行的&lt;/strong&gt;，但浏览器需要处理很多异步任务，例如：网络请求、定时器、用户交互事件、DOM 事件。&lt;/p&gt;
&lt;p&gt;为了在不阻塞主线程的情况下处理这些异步任务，浏览器引入了 &lt;strong&gt;事件循环（Event Loop）机制&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;🌟 &lt;strong&gt;核心流程：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;同步代码&lt;/strong&gt;进入调用栈 &lt;code&gt;Call Stack&lt;/code&gt; 执行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异步任务&lt;/strong&gt;交给浏览器的 &lt;code&gt;Web APIs&lt;/code&gt; 处理（如定时器、网络请求）&lt;/li&gt;
&lt;li&gt;异步任务完成后，其回调函数进入 &lt;strong&gt;任务队列 &lt;code&gt;Task Queue&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event Loop&lt;/strong&gt; 持续检测调用栈是否为空
如果为空，则把任务队列中的任务放入调用栈执行&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="为什么需要事件循环"&gt;为什么需要事件循环
&lt;/h4&gt;&lt;p&gt;由于 JavaScript 是单线程的：同一时间只能执行一段代码，如果没有事件循环，setTimeout就需要等待对应的time才能继续执行，而这会导致：页面卡死。&lt;/p&gt;
&lt;p&gt;因此&lt;strong&gt;浏览器设计成 = 单线程 JS + 事件循环调度 + 浏览器异步线程&lt;/strong&gt;。从而实现：非阻塞异步&lt;/p&gt;
&lt;h4 id="浏览器运行-js-的基本结构"&gt;浏览器运行 JS 的基本结构
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;同步代码进入 调用栈&lt;code&gt;Call Stack&lt;/code&gt; 执行&lt;/li&gt;
&lt;li&gt;异步任务交给浏览器的 Web APIs（例如定时器、网络请求）&lt;/li&gt;
&lt;li&gt;异步任务完成后，其回调函数会进入 任务队列&lt;code&gt;Task Queue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Event Loop 会不断检查 调用栈是否为空，如果为空，就把任务队列中的任务放入调用栈执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="任务队列宏任务-与-微任务"&gt;任务队列：宏任务 与 微任务
&lt;/h4&gt;&lt;p&gt;任务队列中的任务分为两类：Macro Task（宏任务）和 Micro Task（微任务）&lt;/p&gt;
&lt;h4 id="宏任务macro-task"&gt;宏任务（Macro Task）
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;定义：&lt;/strong&gt; 宏任务是由浏览器调度的任务，每次事件循环会执行 &lt;strong&gt;一个宏任务&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常见宏任务：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;整个 JS 文件（script）&lt;/li&gt;
&lt;li&gt;setTimeout&lt;/li&gt;
&lt;li&gt;setInterval&lt;/li&gt;
&lt;li&gt;DOM 事件&lt;/li&gt;
&lt;li&gt;postMessage&lt;/li&gt;
&lt;li&gt;MessageChannel&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="微任务micro-task"&gt;微任务（Micro Task）
&lt;/h4&gt;&lt;p&gt;微任务会在 &lt;strong&gt;当前宏任务执行结束后立即执行&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;常见微任务：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Promise的回调&lt;/strong&gt;[.then、.catch、.finally]&lt;/li&gt;
&lt;li&gt;queueMicrotask&lt;/li&gt;
&lt;li&gt;MutationObserver&lt;/li&gt;
&lt;li&gt;await 后面的代码会进入微任务队列&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="浏览器事件循环的执行顺序是"&gt;浏览器事件循环的执行顺序是
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;🔥 执行一个宏任务 → 执行所有微任务 → 浏览器进行一次渲染 → 再执行下一个宏任务&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;也就是说：&lt;strong&gt;微任务的优先级高于宏任务&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id="浏览器渲染时机"&gt;浏览器渲染时机
&lt;/h4&gt;&lt;p&gt;浏览器通常会在 宏任务执行完并清空微任务队列后 才进行页面渲染，所以如果微任务不断产生，可能会导致页面渲染被延迟。&lt;/p&gt;
&lt;h3 id="常见误区"&gt;常见误区
&lt;/h3&gt;&lt;h4 id="误区一同步任务是宏任务异步任务是微任务-"&gt;误区一：同步任务是宏任务，异步任务是微任务 ❌
&lt;/h4&gt;&lt;p&gt;浏览器调度任务分为宏任务和微任务，从浏览器层面而言，没有同步任务和异步任务的说法。在浏览器层面，同步和异步只是执行方式。同步代码只是 &lt;strong&gt;当前宏任务中的执行内容&lt;/strong&gt;。&lt;/p&gt;
&lt;h4 id="误区二promise-本身是微任务---promisethen是微任务-"&gt;误区二：Promise 本身是微任务 ❌ / Promise.then是微任务 ❌
&lt;/h4&gt;&lt;p&gt;Promise &lt;strong&gt;本身不是微任务&lt;/strong&gt;。Promise executor 是同步执行，Promise的then、catch、finally的&lt;strong&gt;回调&lt;/strong&gt;才是微任务。&lt;/p&gt;
&lt;h4 id="误区三async是微任务---await-是微任务-"&gt;误区三：async是微任务 ❌ / await 是微任务 ❌
&lt;/h4&gt;&lt;p&gt;这个误区其实和 “Promise 是微任务” 的误区本质类似。
async 本身并不是微任务。async 只是 JavaScript 的一个语法糖，它会让 函数返回一个 Promise。
await 也是语法糖，await = &lt;strong&gt;暂停当前 async 函数&lt;/strong&gt; + 注册一个 Promise.then。await的后面的代码才是微任务。&lt;/p&gt;
&lt;h4 id="误区四浏览器执行-js-时并不是每一行代码都是一个任务-"&gt;误区四：浏览器执行 JS 时，并不是每一行代码都是一个任务。 ❌
&lt;/h4&gt;&lt;p&gt;整个JS代码块是一个宏任务。&lt;/p&gt;
&lt;h3 id="实战"&gt;实战
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;async1&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;async2&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;async2&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Promise.&lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;async1&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最后输出:
1 3 5 4 2&lt;/p&gt;
&lt;p&gt;拆解:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;进入 script（一个宏任务）执行 async1()&lt;/li&gt;
&lt;li&gt;进入 async1()&lt;/li&gt;
&lt;li&gt;执行 console.log(1); 此时输出 1。&lt;/li&gt;
&lt;li&gt;执行 await async2();&lt;/li&gt;
&lt;li&gt;进入 async2()&lt;/li&gt;
&lt;li&gt;执行 console.log(3); 此时输出 3。&lt;/li&gt;
&lt;li&gt;执行 await Promise.resolve(); 由于 await = 暂停当前 async 函数 + 注册一个 Promise.then。也就是说 console.log(4); 会被注册成一个微任务。&lt;/li&gt;
&lt;li&gt;此时 async2() 被暂停，后续代码 console.log(4); 已经进入 微任务队列。&lt;/li&gt;
&lt;li&gt;async2() 返回一个 pending 的 Promise。&lt;/li&gt;
&lt;li&gt;回到 async1() 中的 await async2(); 由于 async2() 返回的 Promise 还没有 resolve，所以 async1() 也会 被暂停。&lt;/li&gt;
&lt;li&gt;async1() 后面的 console.log(2); 暂时不会执行，也不会进入微任务队列。&lt;/li&gt;
&lt;li&gt;async1() 执行结束（暂停状态），回到 script。&lt;/li&gt;
&lt;li&gt;执行 console.log(5); 此时输出 5。&lt;/li&gt;
&lt;li&gt;当前 script（宏任务）执行完毕。&lt;/li&gt;
&lt;li&gt;Event Loop 开始执行 微任务队列。&lt;/li&gt;
&lt;li&gt;执行微任务 console.log(4); 此时输出 4。&lt;/li&gt;
&lt;li&gt;async2() 执行完成，其返回的 Promise resolve。&lt;/li&gt;
&lt;li&gt;async1() 中的 await async2(); 得到结果，于是继续执行 async1() 剩余代码。&lt;/li&gt;
&lt;li&gt;console.log(2); 被加入 微任务队列。&lt;/li&gt;
&lt;li&gt;执行该微任务 console.log(2); 此时输出 2。&lt;/li&gt;
&lt;/ol&gt;</description></item></channel></rss>