<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>日日是好日</title><link>https://jjjjjjy.github.io/</link><description>Recent content on 日日是好日</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Thu, 19 Mar 2026 16:15:12 +0800</lastBuildDate><atom:link href="https://jjjjjjy.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>数组拍平</title><link>https://jjjjjjy.github.io/posts/leetcode/flatten/</link><pubDate>Thu, 19 Mar 2026 16:15:12 +0800</pubDate><guid>https://jjjjjjy.github.io/posts/leetcode/flatten/</guid><description>&lt;h2 id="背景"&gt;背景
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;为什么要写这篇文章？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="核心内容"&gt;核心内容
&lt;/h2&gt;&lt;h3 id="1-概念说明"&gt;1️⃣ 概念说明
&lt;/h3&gt;&lt;h3 id="2-使用方式"&gt;2️⃣ 使用方式
&lt;/h3&gt;&lt;h3 id="3-实际案例"&gt;3️⃣ 实际案例
&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-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 示例代码
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&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:#e6db74"&gt;&amp;#39;Hello Vite&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>SSE</title><link>https://jjjjjjy.github.io/posts/browser/communication/sse/</link><pubDate>Fri, 13 Mar 2026 16:39:13 +0800</pubDate><guid>https://jjjjjjy.github.io/posts/browser/communication/sse/</guid><description>&lt;h4 id=""&gt;
&lt;/h4&gt;&lt;p&gt;SSE（Server-Sent Events）是一种基于 HTTP 的服务器推送技术，浏览器通过 EventSource 建立长连接，服务器可以持续向客户端发送 text/event-stream 数据流。它是单向通信，适合实时推送场景，例如 AI 流式输出、通知系统、日志流等。&lt;/p&gt;
&lt;p&gt;特点：&lt;/p&gt;
&lt;p&gt;基于 HTTP&lt;/p&gt;
&lt;p&gt;单向通信（Server → Client）&lt;/p&gt;
&lt;p&gt;浏览器通过 EventSource API 接收服务器不断推送的数据&lt;/p&gt;
&lt;p&gt;连接建立后 服务器可以持续发送数据流&lt;/p&gt;
&lt;p&gt;优点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;简单易用：SSE提供了一种在服务器和客户端之间建立单向连接的直接方法。客户端订阅了SSE端点，然后服务器可以通过此连接将数据推送到客户端，而不需要客户端不断发送请求&lt;/li&gt;
&lt;li&gt;减少网络开销：与持续轮询技术（即每隔几秒钟让每个客户端从服务器请求数据）相比，SSE显著减少了网络开销。&lt;/li&gt;
&lt;li&gt;标准化协议：SSE基于HTTP协议，使其易于部署并与现有的web基础设施兼容。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="为什么sse适合ai流式输出"&gt;为什么SSE适合AI流式输出
&lt;/h4&gt;&lt;p&gt;因为服务器可以持续push数据&lt;/p&gt;
&lt;h4 id="sse-为什么比-websocket-简单"&gt;SSE 为什么比 WebSocket 简单
&lt;/h4&gt;&lt;p&gt;因为SSE基于 HTTP，不需要协议升级&lt;/p&gt;</description></item><item><title>Promise</title><link>https://jjjjjjy.github.io/posts/javascript/promise/</link><pubDate>Fri, 13 Mar 2026 14:47:52 +0800</pubDate><guid>https://jjjjjjy.github.io/posts/javascript/promise/</guid><description>&lt;h4 id="promise-定义"&gt;Promise 定义
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;Promise 是 JS 提供的&lt;strong&gt;异步&lt;/strong&gt;解决方案。&lt;/li&gt;
&lt;li&gt;内部存在三种状态：pending fulfilled rejected。状态一旦改变不可逆。&lt;/li&gt;
&lt;li&gt;Promise 内部维护两个回调队列：onFulfilledCallbacks和onRejectedCallbacks。then 会把回调存入队列。当 resolve 或 reject 时，会执行对应回调。&lt;/li&gt;
&lt;li&gt;then 返回新的 Promise，因此可以实现链式调用。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="promise-状态"&gt;Promise 状态
&lt;/h4&gt;&lt;p&gt;pending 等待
fulfilled 成功
rejected 失败&lt;/p&gt;
&lt;p&gt;状态变化
pending → fulfilled
pending → rejected&lt;/p&gt;
&lt;p&gt;状态一旦改变就不能再变。【原因:避免竞态条件】&lt;/p&gt;
&lt;h4 id="promise-特点"&gt;Promise 特点
&lt;/h4&gt;&lt;p&gt;Promise 最大的特点是 then会返回新的Promise。也就是Promise可以进行链式调用，因此可以解决回调地狱&lt;/p&gt;
&lt;h4 id="promise-本质"&gt;Promise 本质
&lt;/h4&gt;&lt;p&gt;Promise的本质是 状态 + 回调队列&lt;/p&gt;</description></item><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><item><title>JS模块化</title><link>https://jjjjjjy.github.io/posts/javascript/javascriptmodulesystems/jsmodule/</link><pubDate>Sat, 28 Feb 2026 14:26:13 +0800</pubDate><guid>https://jjjjjjy.github.io/posts/javascript/javascriptmodulesystems/jsmodule/</guid><description>&lt;h1 id="模块化"&gt;模块化
&lt;/h1&gt;&lt;p&gt;模块化（Module）指把代码拆分成独立、可复用、可维护的单元。每个模块能够：1️⃣ 独立作用域 2️⃣ 明确依赖 3️⃣ 对外暴露接口；&lt;strong&gt;目的&lt;/strong&gt;：防止变量污染、提高代码复用、提升可维护性、支持大型工程&lt;/p&gt;
&lt;h1 id="javascript-模块化发展历史"&gt;JavaScript 模块化发展历史
&lt;/h1&gt;&lt;h2 id="阶段1iife"&gt;阶段1：IIFE
&lt;/h2&gt;&lt;p&gt;IIFE = 立即执行函数&lt;/p&gt;
&lt;h2 id="阶段2commonjs"&gt;阶段2：CommonJS
&lt;/h2&gt;&lt;p&gt;CommonJS 是 Node.js 的模块规范。&lt;/p&gt;
&lt;p&gt;特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;同步加载（Node 可以同步读文件。浏览器不行。）&lt;/li&gt;
&lt;li&gt;运行时加载&lt;/li&gt;
&lt;li&gt;适合服务器环境&lt;/li&gt;
&lt;li&gt;值拷贝（CommonJS 导出是 值拷贝）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;CommonJS 加载过程&lt;/strong&gt;： const module = require(&amp;rsquo;./math&amp;rsquo;);&lt;/p&gt;
&lt;h2 id="阶段3amd--cmd"&gt;阶段3：AMD / CMD
&lt;/h2&gt;&lt;h3 id="amd异步模块定义asynchronous-module-definition"&gt;AMD&lt;code&gt;异步模块定义&lt;/code&gt;(Asynchronous Module Definition)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;使用 define 定义模块&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用 require 加载模块&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="cmd-公共模块定义common-module-definition"&gt;CMD &lt;code&gt;公共模块定义&lt;/code&gt;(Common Module Definition)
&lt;/h3&gt;&lt;p&gt;在 CMD 规范中，一个模块就是一个文件。
&lt;strong&gt;使用 define 定义模块&lt;/strong&gt;
&lt;strong&gt;使用 SeaJs 的 use 加载模块&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="umd通用模块定义universal-module-definition"&gt;UMD&lt;code&gt;通用模块定义&lt;/code&gt;(Universal Module Definition)
&lt;/h3&gt;&lt;p&gt;UMD 是 AMD 和 CommonJS 的一个糅合。
AMD 是浏览器优先，异步加载；
CommonJS 是服务器优先，同步加载。
既然要通用，怎么办呢？
那就先判断是否支持 node 的模块，支持就使用 node；
再判断是否支持 AMD，支持则使用 AMD 的方式加载。
这就是所谓的 UMD。&lt;/p&gt;
&lt;h3 id="amd-和-cmd-的区别"&gt;AMD 和 CMD 的区别
&lt;/h3&gt;&lt;p&gt;对于依赖的模块，AMD 是 提前执行，CMD 是 延迟就近执行。
AMD是立即执行，CMD是按需执行。&lt;/p&gt;
&lt;p&gt;AMD 推崇 依赖前置，CMD 推崇 依赖就近。&lt;/p&gt;
&lt;p&gt;AMD 的 API 默认是一个当多个用，CMD 的 API 严格区分，推崇职责单一。&lt;/p&gt;
&lt;h2 id="阶段4es-module"&gt;阶段4：ES Module
&lt;/h2&gt;&lt;p&gt;ES6 模块的设计思想是尽量的 &lt;strong&gt;静态化&lt;/strong&gt;，即在&lt;strong&gt;编译阶段&lt;/strong&gt;就确定模块的依赖关系，以及输入和输出的变量。&lt;/p&gt;
&lt;p&gt;｜ CommonJS 和 AMD 模块，都只能在运行时确定这些东西。&lt;/p&gt;
&lt;p&gt;在 ES6 中，我们使用 export 关键字来导出模块，使用 import 关键字来引入模块。&lt;/p&gt;
&lt;p&gt;🔥 优点：Tree Shaking 和 编译优化&lt;/p&gt;
&lt;p&gt;ESM导出的是值引用。&lt;/p&gt;
&lt;h2 id="es6-模块与-commonjs-模块的差异"&gt;💡ES6 模块与 CommonJS 模块的差异
&lt;/h2&gt;&lt;p&gt;｜ 区别&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: center"&gt;区别&lt;/th&gt;
&lt;th style="text-align: center"&gt;CommonJS&lt;/th&gt;
&lt;th style="text-align: center"&gt;ESM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;加载方式&lt;/td&gt;
&lt;td style="text-align: center"&gt;运行时加载&lt;/td&gt;
&lt;td style="text-align: center"&gt;编译时输出接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;加载方式&lt;/td&gt;
&lt;td style="text-align: center"&gt;同步加载&lt;/td&gt;
&lt;td style="text-align: center"&gt;异步加载，有一个独立的模块依赖的解析阶段。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;模块输出&lt;/td&gt;
&lt;td style="text-align: center"&gt;值的拷贝&lt;/td&gt;
&lt;td style="text-align: center"&gt;值的引用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;语法&lt;/td&gt;
&lt;td style="text-align: center"&gt;require()&lt;/td&gt;
&lt;td style="text-align: center"&gt;import&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;适用环境&lt;/td&gt;
&lt;td style="text-align: center"&gt;Node&lt;/td&gt;
&lt;td style="text-align: center"&gt;浏览器+Node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Tree Shaking&lt;/td&gt;
&lt;td style="text-align: center"&gt;不能，因为require是动态执行，编译期无法确定依赖&lt;/td&gt;
&lt;td style="text-align: center"&gt;可以，因为依赖关系可以静态分析&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;🌼 引申&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;require和import的区别：require()是运行时加载，import是编译时输出接口。&lt;/li&gt;
&lt;li&gt;treeShaking是在哪个阶段做的？&lt;/li&gt;
&lt;li&gt;Node 为什么最早用 CommonJS？&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;因为node读取本地文件是同步的，而CJS同步加载没有问题&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="4"&gt;
&lt;li&gt;module.exports 和 exports 的区别？&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>闭包</title><link>https://jjjjjjy.github.io/posts/javascript/closure/</link><pubDate>Sat, 28 Feb 2026 14:26:13 +0800</pubDate><guid>https://jjjjjjy.github.io/posts/javascript/closure/</guid><description>&lt;h1 id="闭包"&gt;闭包
&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;闭包概念：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;闭包是函数在创建时形成的词法作用域绑定关系。&lt;/li&gt;
&lt;li&gt;本质是函数对象内部持有对其创建时作用域的引用。&lt;/li&gt;
&lt;li&gt;当函数在其定义作用域之外执行时，仍然可以访问当时的变量，这种机制就是闭包。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;📍形成闭包的必要条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;内部函数引用了外部变量&lt;/li&gt;
&lt;li&gt;内部函数有没有活到外层函数结束之后&lt;/li&gt;
&lt;li&gt;这个内部函数在外部仍然存在&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;📍 闭包什么时候产生？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;函数创建时就产生闭包结构&lt;/strong&gt;，但只有函数逃逸时才会保留环境。&lt;/p&gt;
&lt;h3 id="词法作用域"&gt;词法作用域：
&lt;/h3&gt;&lt;p&gt;词法作用域（Lexical Scope）是指：变量的作用域在代码“&lt;strong&gt;写出来&lt;/strong&gt;”的时候就已经确定了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;词法作用域是指变量的作用域在代码定义阶段就已经确定，由函数的书写位置决定，而不是由调用位置决定。&lt;/li&gt;
&lt;li&gt;JavaScript 是词法作用域语言，函数在创建时会记录其外部词法环境，变量查找沿着作用域链向外进行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;经典例子：log打印的是1，而不是2。 因为foo被定义在全局作用域，而不是定义在bar的作用域。&lt;/p&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-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;foo&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:#a6e22e"&gt;a&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;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bar&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&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 style="color:#a6e22e"&gt;foo&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;bar&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>AbortController</title><link>https://jjjjjjy.github.io/posts/abortcontroller/</link><pubDate>Fri, 27 Feb 2026 17:17:10 +0800</pubDate><guid>https://jjjjjjy.github.io/posts/abortcontroller/</guid><description>&lt;p&gt;&lt;strong&gt;🔥 如何保证页面最终展示的一定是用户最后点击的那一次请求结果？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在前端开发中，我们经常会遇到这样的问题：用户快速点击多个按钮或者输入搜索关键词，发出了多次网络请求，但是由于网络传输、服务器处理、返回时间等原因，导致响应的顺序和请求的顺序不一定一致。&lt;/p&gt;
&lt;p&gt;比如说用户点击了三个商品，顺序为ABC，但是响应的顺序为ACB，此时页面最终呈现的就是B商品的详情，但是按照道理来说，此时应该展示的是C商品。这个就是一个典型的竞态问题：即&lt;strong&gt;多个请求并发时，返回顺序不可控，可能导致 UI 被旧请求的响应数据覆盖。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="1-abortcontroller取消旧请求"&gt;1️⃣ AbortController（取消旧请求）
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;AbortController&lt;/code&gt; 是 JavaScript 中的一个全局类，它可以用来终止任何&lt;strong&gt;异步&lt;/strong&gt;操作。&lt;/p&gt;
&lt;p&gt;👉 AbortController是一个Web API，它提供了一个信号对象（AbortSignal），该对象可以用来取消与Fetch API相关的操作。当我们创建AbortController实例时，会自动生成一个与之关联的AbortSignal对象。我们可以将这个AbortSignal对象作为参数传递给fetch函数，从而实现对网络请求的取消控制。&lt;/p&gt;
&lt;p&gt;基本思路如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;为每次请求创建一个 AbortController 实例。 &lt;code&gt;const controller = new AbortController();&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通过AbortController实例的signal属性，获取到AbortSignal对象，然后在调用fetch函数时，我们将signal对象作为选项对象的signal属性传递进去。&lt;code&gt;const signal = controller.signal;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;每次新请求前，取消上一次未完成的请求。 &lt;code&gt;controller.abort();&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;只处理最后一次请求的响应，旧请求即使返回也不会影响页面显示。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过这种方式，无论用户点击多快，页面最终展示的总是 最后一次点击的商品详情。&lt;/p&gt;
&lt;p&gt;💡 小提示：对于搜索框或者频繁触发的请求，还可以结合 防抖（debounce） 或 节流（throttle），进一步优化性能，减少无效请求。&lt;/p&gt;
&lt;p&gt;👉 但是 AbortController 存在兼容性问题&lt;/p&gt;
&lt;h2 id="2-版本号校验保证正确性最稳的方案"&gt;2️⃣ 版本号校验（保证正确性）最稳的方案
&lt;/h2&gt;&lt;p&gt;思路：每次点击生成一个“请求版本号”。只有最后一次请求才允许更新 UI。&lt;/p&gt;
&lt;h2 id="-为什么-abortcontroller-不能完全替代版本号"&gt;&lt;strong&gt;📍 为什么 AbortController 不能完全替代版本号？&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;因为即使你调用：controller.abort()；也可能会出现以下几种情况&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;请求已经返回，已经完成的请求调用 abort() 没用（因为 signal 只控制未完成的请求）&lt;/li&gt;
&lt;li&gt;fetch 响应返回（resolve），abort 不能阻止 resolve 进入微任务队列&lt;/li&gt;
&lt;li&gt;then 已经注册回调，abort 不会移除已注册回调，只能 reject fetch promise&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以：UI 仍然可能被旧数据覆盖，这就是为什么真正稳的方案是：AbortController + 版本号校验&lt;/p&gt;</description></item><item><title>Vite总结</title><link>https://jjjjjjy.github.io/posts/buildtools/vitesummary/</link><pubDate>Tue, 20 Jan 2026 17:46:16 +0800</pubDate><guid>https://jjjjjjy.github.io/posts/buildtools/vitesummary/</guid><description>&lt;h6 id="vite-本质"&gt;vite 本质
&lt;/h6&gt;&lt;p&gt;开发阶段利用浏览器原生 ESM 做按需编译，生产阶段基于Rollup做高质量打包。&lt;/p&gt;
&lt;h6 id="vite配置文件为什么是viteconfigts"&gt;vite配置文件为什么是vite.config.ts
&lt;/h6&gt;&lt;ol&gt;
&lt;li&gt;配置本身就是 ESM&lt;/li&gt;
&lt;li&gt;可以使用 TS&lt;/li&gt;
&lt;li&gt;区分生产环境和开发环境，可以写逻辑（if / env）&lt;/li&gt;
&lt;/ol&gt;
&lt;h6 id="vite请求快的原因"&gt;vite请求快的原因
&lt;/h6&gt;</description></item><item><title>Vite</title><link>https://jjjjjjy.github.io/posts/buildtools/vite/</link><pubDate>Wed, 07 Jan 2026 11:33:39 +0800</pubDate><guid>https://jjjjjjy.github.io/posts/buildtools/vite/</guid><description>&lt;h1 id="vite-介绍"&gt;Vite 介绍
&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;Vite 发音 /viːt/&lt;/li&gt;
&lt;li&gt;vite官网: &lt;a class="link" href="https://cn.vitejs.dev/" target="_blank" rel="noopener"
&gt;https://cn.vitejs.dev/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;vite源码: &lt;a class="link" href="https://github.com/vitejs/vite" target="_blank" rel="noopener"
&gt;https://github.com/vitejs/vite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vite是一个由原生ESM驱动的Web开发构建工具。&lt;/p&gt;
&lt;p&gt;在开发环境下基于浏览器原生ESimports开发，在生产环境下基于Rollup打包。&lt;/p&gt;
&lt;p&gt;vite是vue作者开发的一款意图取代webpack的工具，其实现原理是&lt;strong&gt;利用ES6的import会发送请求去加载文件的特性，拦截这些请求，做一些预编译&lt;/strong&gt;，省去webpack冗长的打包时间。&lt;/p&gt;
&lt;h1 id="vite-组成"&gt;Vite 组成
&lt;/h1&gt;&lt;p&gt;vite主要由两部分组成：一个开发服务器 + 一套构建指令。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个开发服务器，它基于 原生 ES 模块 提供了 丰富的内建功能，如速度快到惊人的 模块热更新（HMR）。&lt;/li&gt;
&lt;li&gt;一套构建指令，它使用 Rollup 打包代码，并且是预配置的，可输出用于生产环境的高度优化过的静态资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;理解：Vite 的开发服务器 只在开发环境使用。Vite 的构建（build / Rollup）只在生产环境构建时使用。两者职责清晰、阶段完全不同。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Vite 的精髓就是：
&lt;strong&gt;开发阶段基于ESM，不打包，速度更快，生产阶段基于Rollup打包，打包到极致&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="开发服务器"&gt;开发服务器
&lt;/h2&gt;&lt;p&gt;在开发期间，vite就是一个服务器。开发服务器只在开发阶段使用，本质上是“让浏览器直接跑你的源码”。&lt;/p&gt;
&lt;p&gt;✔ 提供本地服务
✔ 利用 浏览器原生 ES Modules
✔ 实时转换源码（TS / JSX / Vue）
✔ HMR（模块热替换）
✔ 不做整体打包&lt;/p&gt;
&lt;p&gt;你写的源码
↓
Vite Dev Server（即时转换）
↓
浏览器按需请求模块（ESM）
💡 特点：&lt;/p&gt;
&lt;p&gt;没有 bundle&lt;/p&gt;
&lt;p&gt;改一个文件，只重新加载这一个模块&lt;/p&gt;
&lt;p&gt;启动速度 ≈ 0（项目再大也快）&lt;/p&gt;
&lt;h2 id="构建指令"&gt;构建指令
&lt;/h2&gt;&lt;p&gt;本质：把源码变成适合线上部署的静态文件&lt;/p&gt;
&lt;p&gt;✔ 使用 Rollup
✔ 真正“打包”
✔ 代码分割、Tree-shaking
✔ 文件 hash
✔ 输出静态资源（dist）&lt;/p&gt;
&lt;h1 id="--diff-vite-vs-webpack"&gt;🚧 Diff: Vite vs Webpack
&lt;/h1&gt;&lt;h2 id="开发环境"&gt;开发环境
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Webpack 是在“浏览器不会模块化”的时代诞生的正确方案，【浏览器不支持ESModule和import和export】因此他的世界观是先打包再运行，因此开发服务器启动速度慢。&lt;/li&gt;
&lt;li&gt;Vite 是在“浏览器已经会模块化”之后，对构建工具的重构，因此他的世界观是先运行再打包，因此开发服务器启动速度极快。
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;开发环境对比点&lt;/th&gt;
&lt;th&gt;Webpack&lt;/th&gt;
&lt;th&gt;Vite&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;是否打包&lt;/td&gt;
&lt;td&gt;✅ 是（内存 bundle）&lt;/td&gt;
&lt;td&gt;❌ 否&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;模块加载&lt;/td&gt;
&lt;td&gt;bundle&lt;/td&gt;
&lt;td&gt;浏览器原生 ESM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;启动时&lt;/td&gt;
&lt;td&gt;全量构建依赖图&lt;/td&gt;
&lt;td&gt;不扫描全量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;改一个文件&lt;/td&gt;
&lt;td&gt;重新构建 chunk&lt;/td&gt;
&lt;td&gt;精确替换模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HMR&lt;/td&gt;
&lt;td&gt;基于 bundle&lt;/td&gt;
&lt;td&gt;基于模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;项目越大&lt;/td&gt;
&lt;td&gt;越慢&lt;/td&gt;
&lt;td&gt;基本不变&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;｜ 在开发环境，Vite 和 Webpack 的主要区别在于是否对代码进行打包。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;webpack在启动开发服务器时，会将整个项目模块构建一个依赖图，打包为一个bundle.js文件，然后再运行这个文件。&lt;/p&gt;
&lt;p&gt;只是这个打包是 内存中的打包（in-memory bundle），不是写到磁盘而已。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;vite则是利用浏览器的ES Module Imports特性，不进行预打包，只有在真正需要时才编译文件，按需加载模块。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;在启动开发服务器时，Webpack 将整个项目所有模块构建一个依赖图，打包为一个bundle.js文件，然后再运行这个文件。对于大型项目，这个过程可能非常耗时，导致开发者需要等待较长时间才能开始调试代码。&lt;/li&gt;
&lt;li&gt;Vite：Vite 利用浏览器的ES Module Imports特性，不进行预打包，只有在真正需要时才编译文件，按需加载模块。
&lt;ul&gt;
&lt;li&gt;Vite直接启动了一个开发服务器devServer，然后劫持浏览器的HTTP请求，在后端进行相应的处理将项目中使用的文件通过简单的分解与整合，然后再返回给浏览器(整个过程没有对文件进行打包编译)。所以Vite 的开发服务器启动速度非常快，通常可以在几百毫秒内启动。&lt;/li&gt;
&lt;li&gt;（在生产模式下，vite使用Rollup进行打包，提供更好的tree-shaking，代码压缩和性能优化。）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;【对比: Webpack 在开发阶段「本质上也要打包」, 只是这个打包是 内存中的打包（in-memory bundle），不是写到磁盘而已。】&lt;/p&gt;
&lt;h2 id="为什么-vite-启动这么快"&gt;为什么 Vite 启动这么快？
&lt;/h2&gt;&lt;p&gt;因为vite是浏览器直接加载 ESM，不做 bundle，而是按需加载模块，只在请求到某个模块时才编译（on-demand）&lt;/p&gt;
&lt;h2 id="vite-的工作原理"&gt;vite 的工作原理
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;启动 dev server&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;浏览器请求 index.html&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;浏览器通过 &lt;!-- raw HTML omitted --&gt; 加载模块&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;遇到裸模块（vue / react）→ Vite 转成浏览器可识别路径&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;按需编译、缓存结果&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="vite-中如何区分环境"&gt;Vite 中如何区分环境？
&lt;/h2&gt;&lt;p&gt;import.meta.env.MODE
import.meta.env.DEV
import.meta.env.PROD&lt;/p&gt;
&lt;p&gt;只有 VITE_ 开头的环境变量才会注入到客户端&lt;/p&gt;
&lt;h2 id="vite-如何处理-css"&gt;Vite 如何处理 CSS？
&lt;/h2&gt;&lt;p&gt;原生支持：
CSS / Less / Sass&lt;/p&gt;
&lt;p&gt;CSS Module：*.module.css&lt;/p&gt;
&lt;p&gt;自动拆分 CSS&lt;/p&gt;
&lt;p&gt;开发阶段 style 注入&lt;/p&gt;
&lt;p&gt;生产阶段提取成文件&lt;/p&gt;</description></item><item><title>防抖和节流</title><link>https://jjjjjjy.github.io/posts/debounce-throttle/</link><pubDate>Sat, 15 Nov 2025 16:16:27 +0800</pubDate><guid>https://jjjjjjy.github.io/posts/debounce-throttle/</guid><description>&lt;h5 id="防抖--把一段时间内的多次触发压缩成最后一次"&gt;防抖 = 把一段时间内的多次触发，压缩成“最后一次”
&lt;/h5&gt;&lt;h5 id="节流--给事件加一个时间闸门按固定频率放行"&gt;节流 = 给事件加一个“时间闸门”，按固定频率放行
&lt;/h5&gt;&lt;h6 id="requestanimationframe-是以帧为单位的节流"&gt;requestAnimationFrame 是以帧为单位的节流
&lt;/h6&gt;&lt;p&gt;防抖是指在事件触发一段时间后才执行，如果期间再次触发会重新计时；
节流是在一段时间内只允许函数执行一次，无论事件触发多少次。
防抖关注的是“只执行最后一次”，
节流关注的是“限制执行频率”，
前者压缩事件，后者采样事件。&lt;/p&gt;
&lt;hr&gt;
&lt;h5 id="为什么-scroll-用节流不用防抖"&gt;为什么 scroll 用节流不用防抖？
&lt;/h5&gt;&lt;h6 id="scroll-过程中需要持续反馈防抖只在结束后执行一次会造成卡顿"&gt;scroll 过程中需要持续反馈，防抖只在结束后执行一次，会造成卡顿
&lt;/h6&gt;&lt;hr&gt;
&lt;h5 id="为什么防抖会破坏交互而节流不会"&gt;为什么“防抖会破坏交互，而节流不会”
&lt;/h5&gt;&lt;h6 id="判断依据是用户是否关心过程中的状态变化"&gt;判断依据是用户是否关心“过程中的状态变化”。
&lt;/h6&gt;&lt;p&gt;&lt;strong&gt;用户如果不关心过程，就可以用防抖。&lt;/strong&gt; 防抖适合输入框搜索、表单校验、resize；&lt;/p&gt;
&lt;p&gt;用户如果关心过程，就需要用节流。节流适合 scroll、mousemove、拖拽、曝光统计。&lt;/p&gt;
&lt;h3 id="requestanimationframe"&gt;requestAnimationFrame
&lt;/h3&gt;&lt;h4 id="基础概念及相关概念"&gt;基础概念及相关概念
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;requestAnimationFrame (简称 rAF) 是以浏览器帧率为单位的节流，由浏览器主导的，保证每一帧最多执行一次，并与渲染同步的 JS 执行钩子。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;帧：屏幕被“画”一次&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;rAF的核心&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一帧只调用一次，每次屏幕刷新前调用&lt;/li&gt;
&lt;li&gt;自动节流，tab 不可见时几乎不执行&lt;/li&gt;
&lt;li&gt;与渲染同步，回调发生在浏览器绘制前&lt;/li&gt;
&lt;li&gt;时间戳稳定，用于计算动画进度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="浏览器一帧里发生了什么"&gt;浏览器一帧里发生了什么？
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;处理输入事件（click、scroll）&lt;/li&gt;
&lt;li&gt;执行 JS
&lt;ul&gt;
&lt;li&gt;setTimeout&lt;/li&gt;
&lt;li&gt;Promise&lt;/li&gt;
&lt;li&gt;requestAnimationFrame ← 在这里&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;样式计算（Recalculate Style）&lt;/li&gt;
&lt;li&gt;布局（Layout / Reflow）&lt;/li&gt;
&lt;li&gt;绘制（Paint）&lt;/li&gt;
&lt;li&gt;合成（Composite）&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>CSR和SSR</title><link>https://jjjjjjy.github.io/posts/performance/ssr-csr/</link><pubDate>Sun, 20 Jul 2025 16:16:27 +0800</pubDate><guid>https://jjjjjjy.github.io/posts/performance/ssr-csr/</guid><description>&lt;h1 id="csrclient-side-rendering-客户端渲染"&gt;CSR（Client-Side Rendering）: 客户端渲染
&lt;/h1&gt;&lt;p&gt;服务器主要负责提供静态的HTML文件（可能包含一些基本的HTML结构和JavaScript脚本），而真正的页面渲染工作则完全由客户端的浏览器来完成。这意味着页面内容是在用户的浏览器上动态生成的。&lt;/p&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;p&gt;响应速度快：一旦HTML文件加载完成，浏览器就可以开始渲染页面，而不需要等待服务器返回完整的渲染结果。
动态性强：由于页面渲染在客户端进行，因此可以方便地实现各种动态交互效果。
前端部署简单：只需要一个静态服务即可部署前端代码，降低了部署成本。&lt;/p&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;p&gt;首屏加载时间长：由于需要加载整个JavaScript包，可能导致首屏加载时间较长，特别是对于复杂的单页应用（SPA）。
不利于SEO：搜索引擎爬虫可能无法很好地解析由JavaScript动态生成的页面内容，导致SEO效果较差。
白屏时间：在JavaScript代码加载和执行期间，用户可能会看到空白的页面，即所谓的“白屏时间”。&lt;/p&gt;
&lt;h1 id="ssrserver-side-rendering-服务端渲染"&gt;SSR（Server-Side Rendering）: 服务端渲染
&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;SSR 的流程一定要熟：构建 → 请求 → 服务端渲染 → 注水 → 客户端接管&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSR 是一种在服务器端完成页面渲染的技术。&lt;/li&gt;
&lt;li&gt;在SSR下，服务器端接收到客户端的请求后，会根据 &lt;strong&gt;客户端 请求的数据&lt;/strong&gt; 以及 &lt;strong&gt;模板文件&lt;/strong&gt; 生成完整的HTML页面。然后将这个完整的HTML页面直接发送给客户端。&lt;/li&gt;
&lt;li&gt;这样，用户可以直接看到完成的内容，无需等待JavaScript加载和执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ssr-模板文件"&gt;SSR 模板文件
&lt;/h2&gt;&lt;p&gt;👉 SSR 本质上是生成一段 HTML 字符串， 然后插入到 SSR 模板文件 中。&lt;/p&gt;
&lt;p&gt;｜&lt;code&gt;模板文件&lt;/code&gt;: SSR 模板 = 静态 HTML 容器 (纯HTML字符串) + 插槽占位&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSR 模板文件 不会渲染，而是“接收渲染结果”的容器。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt; 使用SSR模板文件的好处是无需每次构建整个页面，只需要每次将数据插入到slot中，从而渲染插槽即可。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSR模板文件 &lt;strong&gt;不会被&lt;/strong&gt;服务端构建器&lt;strong&gt;编译&lt;/strong&gt;（build/compile）
&lt;ul&gt;
&lt;li&gt;构建器（如 Vite、Webpack、Rollup）并不会处理这个 index.html 模板（它可能会被拷贝，但不会编译、分析依赖、生成 bundle）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SSR模板文件 &lt;strong&gt;不会被&lt;/strong&gt;客户端解析模板语法而&lt;strong&gt;渲染&lt;/strong&gt;（render）
&lt;ul&gt;
&lt;li&gt;SSR模板文件 不会被 像 Vue/React/San 这样的框架渲染，因为浏览器只会看到它已经被替换好的 HTML，根本不会看到插槽占位&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SSR模版只是 在 node 读取模版时，使用JS 将 插槽占位 替换成 服务端解析客户端获得的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;与slot的区别:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSR 插槽 = 等待“某个组件渲染结果”的静态 HTML 占位符&lt;/li&gt;
&lt;li&gt;组件插槽 = 等待“其他组件传入内容”的动态子组件插入点&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ssr-反解--hydration注水机制"&gt;SSR 反解 —— Hydration（注水）机制
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;🌟 既然SSR是在服务端渲染一个HTML的过程，那么客户端是如何接管运行的呢？如何执行页面上的JS逻辑呢？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这就涉及到 SSR反解，即挂载JS的过程。 又称 Hydration（注水）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SSR 注水时的 JS 逻辑，是浏览器从 HTML 中加载 script 脚本文件后执行的，而注水的入口逻辑 就是 JS 应用接管 DOM 的起点。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SSR 的“反解”就是客户端的 Hydration（注水）过程，即将 SSR 渲染好的 HTML，挂上 JS 逻辑，变成真正可交互的应用。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;🔥 注水机制的 &lt;strong&gt;本质&lt;/strong&gt; 是在 &lt;strong&gt;已有DOM上挂载JS逻辑&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;❌ 这里需要区分CSR。 Hydration 是 SSR 独有的步骤，纯 CSR 是 没有 Hydration过程的，因为CSR 不会预先接管HTML，而是自己从 JS 渲染 DOM。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;React 的 hydrateRoot()、Vue 的 app.mount()、san的 myComponent.attach(el, { ssr: true }) 都是注水入口。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;但是 有的时候 我们也会在 CSR 项目里看到下面这种代码：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const app = createApp(App);
app.mount(&amp;#39;#app&amp;#39;);
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;但这其实不是注水，这里 #app 是空的，App 是从 0 渲染出来的。只是 API 长得像 SSR 的注水 mount()，但它做的是“创建 DOM”，不是“复用已有 DOM”。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SSR 的 HTML 是“服务器生成的”，DOM是在服务端生成的，Hydration 是“接管”， 只需要在 DOM上挂载，而无需重新创建DOM；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在CSR中，服务器返回一份 空的 HTML， CSR 的 HTML 是“客户端生成的”， 需要 直接从 0 开始创建DOM，没有可接管的内容。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="注水的作用"&gt;注水的作用
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;注水不是重新渲染，而是对比已有 HTML 和虚拟 DOM，找到匹配点， 然后 &lt;strong&gt;“在已有 DOM 上挂载 JS 逻辑”&lt;/strong&gt; ；这样可以省掉客户端从 0 渲染，页面加载速度更快。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="注水顺序"&gt;注水顺序：
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;【初始化客户端数据】 创建响应式状态 或 store 状态 【还没有对比DOM结构】&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;执行组件的 initData() 或 data: { &amp;hellip; }&lt;/li&gt;
&lt;li&gt;恢复 Vue 的 data()，React 的 useState()、Redux 状态等&lt;/li&gt;
&lt;li&gt;或读取 window.&lt;strong&gt;INITIAL_DATA&lt;/strong&gt; 来还原服务端数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;【对比DOM】&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对比 客户端 JS 渲染出来的“虚拟 DOM” 和 页面中已经由服务端生成并送到客户端的“真实 DOM” （不一致可能会触发警告或 DOM patch）&lt;/li&gt;
&lt;li&gt;如果不一致，框架会：
&lt;ul&gt;
&lt;li&gt;报 warning（如 Vue 的 hydration mismatch）&lt;/li&gt;
&lt;li&gt;或直接 patch DOM（丢掉 SSR 性能）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;🔁 这是为了让“JS 状态”与“页面结构”达成一致。&lt;/li&gt;
&lt;li&gt;⚠️ 特别注意：不是所有属性都“必须对比”
&lt;ul&gt;
&lt;li&gt;一些如 data-* 属性、非交互性属性，或者某些 aria-* 辅助属性，框架可能会有容忍度。&lt;/li&gt;
&lt;li&gt;但关键属性（如：id、class、key、绑定属性、样式、children 顺序）都必须对比且一致，否则注水可能失败。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;【挂载事件监听器】&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="ssr的优缺点"&gt;SSR的优缺点
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首屏加载速度快：由于服务器已经生成了完整的HTML页面，因此客户端可以直接显示这个页面，无需等待JavaScript加载和执行。&lt;/li&gt;
&lt;li&gt;SEO友好：搜索引擎爬虫可以很好地解析由服务器生成的HTML页面内容，有利于SEO优化。&lt;/li&gt;
&lt;li&gt;适合复杂页面：对于包含大量数据、需要复杂计算的页面，SSR可以更好地处理并减少客户端的负载。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务器压力大：对于每个请求，服务器都需要重新渲染页面，这可能导致服务器压力过大。&lt;/li&gt;
&lt;li&gt;开发限制：SSR要求开发者在编写Vue组件时，需要考虑到服务器端和客户端环境的差异，不能过度依赖客户端环境。&lt;/li&gt;
&lt;li&gt;调试困难：SSR的调试过程相对复杂，需要同时考虑到服务器端和客户端的日志和错误信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="-diff-注水-vs-挂载-vs-重新渲染"&gt;🚧 Diff 注水 vs 挂载 vs 重新渲染
&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;动作&lt;/th&gt;
&lt;th&gt;会创建 DOM？&lt;/th&gt;
&lt;th&gt;会挂载事件？&lt;/th&gt;
&lt;th&gt;用在哪&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSR&lt;/td&gt;
&lt;td&gt;服务器渲染HTML&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;首屏&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hydration注水&lt;/td&gt;
&lt;td&gt;接管 HTML&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;首屏&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSR 挂载&lt;/td&gt;
&lt;td&gt;浏览器全量渲染&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;二次跳转、非首屏&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description></item></channel></rss>