<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/feed.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>星光の平</title><description>碎碎念</description><link>https://star-xp.top</link><item><title>Agent记忆到底应该怎么实现? 一篇文章带你读懂记忆实现 !</title><link>https://star-xp.top/post/agent-memory</link><guid isPermaLink="false">agent-memory</guid><description>agent记忆</description><pubDate>Fri, 05 Jun 2026 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;code&gt;Agent为什么需要记忆?&lt;/code&gt;&lt;a href=&quot;#agent为什么需要记忆&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;LLM(大语言模型) 是 Agent 的大脑, 它是一个非常聪明的天才, 但是它没有记忆, 你问了它上一句, 回复之后都忘掉了, 鱼的记忆有 7 秒, 但是大语言模型的记忆只有 0.0001 秒……
&lt;strong&gt;关键概念 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;无状态: 你给它一段输入, 它算下段输出, 算完就忘&lt;/li&gt;
&lt;li&gt;上下文窗口:  如果你想让你的模型带有记忆, 你可以给它加上下文窗口(就是加载之前的记忆), 但是模型最多一次只能看固定的上下文长度, 它是按照 token 来计算的, 如果 token 过长之前的内容会被挤出去, 部分失忆, 所以我们需要记忆 !&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Agent 记忆四层架构&lt;a href=&quot;#agent-记忆四层架构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;存储结构&lt;a href=&quot;#存储结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;向量存储&lt;/strong&gt;：用于相似性检索的记忆表示&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分层结构&lt;/strong&gt;：按时间或重要性组织的记忆层次&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;图存储&lt;/strong&gt;：记忆间关联关系的图结构表示&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记忆模式&lt;/strong&gt;：不同类型记忆的存储范式&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;写入机制&lt;a href=&quot;#写入机制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;触发时机&lt;/strong&gt;：记忆写入的触发条件与时机&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;信息提取&lt;/strong&gt;：从交互中提取关键信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;写入决策&lt;/strong&gt;：决定哪些信息需要持久化存储&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链接生成&lt;/strong&gt;：建立记忆间的关联关系&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;记忆管理&lt;a href=&quot;#记忆管理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;遗忘机制&lt;/strong&gt;：主动或被动遗忘不重要记忆&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合并压缩&lt;/strong&gt;：相似记忆的合并与信息压缩&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记忆演化&lt;/strong&gt;：记忆随时间的更新与演变&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;读取机制&lt;a href=&quot;#读取机制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;检索测试&lt;/strong&gt;：检索系统的评估与测试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;检索增强&lt;/strong&gt;：提升检索准确性的技术&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;排序与返回&lt;/strong&gt;：检索结果的排序与返回策略&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三种位置记忆&lt;a href=&quot;#三种位置记忆&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;上下文记忆&lt;a href=&quot;#上下文记忆&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;存在当前输入窗口里面, 上下文记忆是临时的, 不可跨会话进行读取, 并且窗口大小是有限的, 每一次的对话都是一个更新&lt;/p&gt;
&lt;h3&gt;外部记忆&lt;a href=&quot;#外部记忆&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;主要是体现在当前的这个数据库里(或向量数据库), 容量是无限的, 可以跨会话回答, 修改非常灵活, 可以直接操作并且执行 CRUD, 外部记忆通常是通过检索然后拼接到上下文当中的, 主要用来存储用户的画像和一些长期事实&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;外部记忆的四种主流形态:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检索式记忆: 将每条记忆做 embedding 存进向量库, 需要时按语义相似度检索 top-k 拼进上下文, 本质就是把 RAG 用在对话历史或者用户画像上&lt;/li&gt;
&lt;li&gt;结构化记忆: 将记忆组织成键值对, 表, 图谱等结构, 适合精确, 可更新, 需要推理的关系场景中&lt;/li&gt;
&lt;li&gt;摘要式记忆: 将有限的上下文记忆当内存, 将外部记忆存储当硬盘, 当会话变长的时候, 旧内容滚动摘要压缩成要点存档, 需要时再翻页召回, 这就是 MemGPT 的核心直觉&lt;/li&gt;
&lt;li&gt;笔记式记忆: 让 agent 把重要的结论写入文件, 下次任务开始先读这份笔记, 这是编码式 agent 非常实用的方法&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;参数记忆&lt;a href=&quot;#参数记忆&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;参数记忆一般是通过训练或微调实现来的, 成本比较贵, 但是参数记忆加载非常快, 容量由参数数量来决定, 相对固定&lt;/p&gt;
&lt;h2&gt;外部记忆的四个策略&lt;a href=&quot;#外部记忆的四个策略&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;write: 写什么, 何时写?&lt;a href=&quot;#write-写什么-何时写&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;不是所有的对话都需要记忆, 只需要在对话当中抽取稳定的事实, 偏好 ,结论等, 给每条记忆重要性打分, 可以在会话结束的时候触发写入, 或者触发式(检测到某一段文字)来写入&lt;/p&gt;
&lt;h3&gt;read: 怎么找回?&lt;a href=&quot;#read-怎么找回&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&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;h3&gt;update: 信息变了如何更新?&lt;a href=&quot;#update-信息变了如何更新&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;当新信息和旧的信息冲突时, 不能两个都存在, 需要进行修改, 然 llm 来判断应该如何处理(crud)
&lt;strong&gt;四种更新优化机制:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;全量数据更新（当新信息与记忆库中的相似度小于 0.7 时，进行增量式更新）&lt;/li&gt;
&lt;li&gt;更新现有记忆（相似度大于 0.8 时，更新记忆内容）&lt;/li&gt;
&lt;li&gt;删除过时记忆（当记忆的重要性系数低于阈值时，自动删除）&lt;/li&gt;
&lt;li&gt;不执行任何操作（信息已存在且完全相同时，可触发复习机制，提高该记忆的重要性权重）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;forget: 为什么要主动忘记?&lt;a href=&quot;#forget-为什么要主动忘记&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;长期没有被命中的需要主动删除衰减, 不然数据库会越来越脏, 检索越来越差&lt;/p&gt;
&lt;h2&gt;做好离线整理&lt;a href=&quot;#做好离线整理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;人不能把每件事情都塞进长期记忆, 如果内存中只添加不整理, 会越来越乱&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;反思 Reflection&lt;/strong&gt; :  用 LLM 把一批零散观察提炼成高层洞见并存回记忆
&lt;strong&gt;睡眠时计算（「梦境」）&lt;/strong&gt;:  空闲时主动把上下文预先嚼碎（预摘要 / 预生成可能被问的 QA / 预整理）
&lt;strong&gt;后台自我评审&lt;/strong&gt; :  后台用 critic / verifier / 自动回归持续自查自纠&lt;/p&gt;</content:encoded><category>category:Agent</category><category>category:memory</category><category>tag:python</category><category>tag:agent</category></item><item><title>AI Agent 的新战场：从会动手，到被允许动手</title><link>https://star-xp.top/post/agent-news</link><guid isPermaLink="false">agent-news</guid><description>agent新战场：从会动手，到被允许动手</description><pubDate>Tue, 19 May 2026 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;小龙虾证明了 AI 可以动手, 但 2026 年 6 月真正新的变化是：大厂开始把这种“动手能力”塞进真实业务入口，并同时补上治理系统。&lt;/p&gt;
&lt;p&gt;三个最新信号：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Meta Business Agent&lt;/strong&gt;：2026-06-03 全球推出，把 AI Agent 放进 WhatsApp、Instagram、Messenger 这些商业对话入口。Meta 官方称已有超过 100 万商家使用，三大平台每天有超过 10 亿条 business threads。它能回答问题、推荐商品、预约、筛选销售线索、关闭销售。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenAI Codex 扩出编程圈&lt;/strong&gt;：2026-06-02 OpenAI 称 Codex 已有超过 500 万周活用户，知识工作者占 20%，正在用于报告、表格、合同、研究、数据分析和工作流自动化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Build 2026 推 Agent 控制层&lt;/strong&gt;：同样在 2026-06-02，微软发布/强化 Agent 365、ASSERT、Agent Control Specification、Foundry observability，核心不是“再造一个 Agent”，而是让企业能控制、审计、评估 Agent。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI Agent 的竞争正在从“能力演示”进入“入口占领 + 运行时治理”。谁掌握用户每天工作的入口，谁掌握 Agent 的控制平面，谁就可能掌握下一代软件分发权。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h1&gt;AI Agent 的新战场：从“会动手”到“被允许动手”&lt;a href=&quot;#ai-agent-的新战场从会动手到被允许动手&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;2026 年上半年，AI Agent 已经不是新鲜词。&lt;/p&gt;
&lt;p&gt;小龙虾 OpenClaw 爆火、Manus 类产品走红、Codex/Claude Code 进入开发流，大家都已经知道 AI 不再只是聊天框，而是可以接工具、跑任务、改文件、操作浏览器。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2026 年 6 月真正的新变化是：Agent 正在从个人玩具和开发者工具，进入商业入口、知识工作流和企业控制系统。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;换句话说，行业正在从第一个问题进入第二个问题。&lt;/p&gt;
&lt;p&gt;第一个问题是：Agent 能不能做事？&lt;br /&gt;
第二个问题是：Agent 在哪里做事、替谁做事、谁允许它做、出了问题谁负责？&lt;/p&gt;
&lt;h2&gt;一、小龙虾不是终点，只是证明题&lt;a href=&quot;#一小龙虾不是终点只是证明题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenClaw 的意义很大，它完成的是一个证明题：AI 可以从聊天框里出来，变成本地电脑上的执行代理。它能接通信渠道、文件系统、浏览器、命令行、插件、skills，用户可以像安排助理一样安排它执行任务。&lt;/p&gt;
&lt;p&gt;这当然重要。没有小龙虾，很多人不会直观理解 Agent 的“手脚”是什么。&lt;/p&gt;
&lt;p&gt;但到现在，问题已经在逐渐变化了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;小龙虾证明了 Agent 能动手；现在大厂在争的是，谁能让 Agent 在真实商业场景里规模化动手。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这就是 Meta、OpenAI、Microsoft 最近几天的动作为什么值得放在一起看。&lt;/p&gt;
&lt;h2&gt;二、Meta 的信号：Agent 正在进入商业对话入口&lt;a href=&quot;#二meta-的信号agent-正在进入商业对话入口&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;2026 年 6 月 3 日，Meta 发布 Meta Business Agent，并把它推向 WhatsApp、Messenger、Instagram。&lt;/p&gt;
&lt;p&gt;这件事表面上看是客服机器人升级，实际更重要。&lt;/p&gt;
&lt;p&gt;因为 WhatsApp、Instagram、Messenger 不是普通应用，它们是大量商家每天和客户沟通的入口。Meta 官方说，已经有超过 100 万商家在 WhatsApp 和 Messenger 使用 Meta Business Agent，WhatsApp、Messenger、Instagram 每天有超过 10 亿条 business threads。&lt;/p&gt;
&lt;p&gt;Meta Business Agent 能做什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;回答和企业相关的问题；&lt;/li&gt;
&lt;li&gt;根据商品目录推荐产品；&lt;/li&gt;
&lt;li&gt;预约服务；&lt;/li&gt;
&lt;li&gt;筛选潜在销售线索；&lt;/li&gt;
&lt;li&gt;决定何时转人工；&lt;/li&gt;
&lt;li&gt;关闭销售；&lt;/li&gt;
&lt;li&gt;未来连接 Shopify、Zendesk、Shopee 等系统，替企业执行更多动作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这不是“AI 聊天”。这是把 Agent 放进交易前线。&lt;/p&gt;
&lt;p&gt;过去，企业软件的入口可能是 CRM、官网、App、客服系统。现在 Meta 想让 WhatsApp/Instagram/Messenger 变成小商家的业务操作层：客户咨询、商品推荐、预约、销售、复盘，都在对话里发生。&lt;/p&gt;
&lt;p&gt;这比小龙虾更值得写，因为它说明 Agent 不再只是极客给自己装的本地助理，而是开始进入真实商业分发渠道。&lt;/p&gt;
&lt;h2&gt;三、OpenAI 的信号：Codex 正在从“写代码”变成“知识工作后台”&lt;a href=&quot;#三openai-的信号codex-正在从写代码变成知识工作后台&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;同一天，2026 年 6 月 2 日，OpenAI 发布 Codex 使用报告，称 Codex 已有超过 500 万周活用户，是 2 月桌面应用推出以来的 6 倍以上。&lt;/p&gt;
&lt;p&gt;更关键的是：OpenAI 说开发者仍是最大用户群，但知识工作者已经占 20%，且增长速度超过开发者的三倍。&lt;/p&gt;
&lt;p&gt;这些知识工作者在用 Codex 做什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;生成报告；&lt;/li&gt;
&lt;li&gt;做表格和数据分析；&lt;/li&gt;
&lt;li&gt;写合同和文档；&lt;/li&gt;
&lt;li&gt;做研究；&lt;/li&gt;
&lt;li&gt;自动化工作流；&lt;/li&gt;
&lt;li&gt;搭轻量工具；&lt;/li&gt;
&lt;li&gt;并行运行多个任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这说明 Codex 不再只是“程序员写代码更快”的工具，而在变成一种通用知识工作执行环境。&lt;/p&gt;
&lt;p&gt;这一点非常关键。&lt;/p&gt;
&lt;p&gt;过去知识工作者的瓶颈是：想法在脑子里，执行卡在工具里。要做一个分析，需要找数据、写公式、做图、写报告、发给别人改。要自动化一个流程，需要找工程师写脚本。&lt;/p&gt;
&lt;p&gt;Codex 这类 Agent 的价值不是“帮你写几行代码”，而是把很多原本需要跨工具、跨技能、跨人协作的小任务，变成可以委派的后台任务。&lt;/p&gt;
&lt;p&gt;这就是 Agent 对知识工作的真正改变：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;人不再只是在工具里操作，而是在管理一组持续工作的执行线程。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenAI 之前还开源了 Symphony，把 Linear 这类项目管理板变成 coding agents 的控制平面：每个 issue 对应一个 agent workspace，agent 持续跑，人类只审查结果。OpenAI 称某些团队 landed PR 增长达到 500%。&lt;/p&gt;
&lt;p&gt;这比“AI 会写代码”更深。它说明软件开发正在从“人使用工具”变成“人调度 Agent 队列”。&lt;/p&gt;
&lt;h2&gt;四、Microsoft 的信号：Agent 最大瓶颈不是能力，而是控制&lt;a href=&quot;#四microsoft-的信号agent-最大瓶颈不是能力而是控制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果只有 Meta 和 OpenAI 的动作，我们可以说 Agent 正在进入业务入口和知识工作流。但 Microsoft Build 2026 补上了更关键的一块：治理。&lt;/p&gt;
&lt;p&gt;微软在 2026 年 6 月 2 日发布了一组 Agent 信任与控制工具，包括 Agent 365、ASSERT、Agent Control Specification、Foundry tracing/evaluation、runtime DLP 等。&lt;/p&gt;
&lt;p&gt;其中最值得关注的是 Agent Control Specification，简称 ACS。&lt;/p&gt;
&lt;p&gt;微软对 ACS 的定位很清楚：如果 MCP 标准化了 Agent 如何连接工具，A2A 标准化了 Agent 如何彼此通信，那么 ACS 要标准化 Agent 的安全控制。&lt;/p&gt;
&lt;p&gt;ACS 不是再写一句 system prompt：“不要做坏事”。它定义的是 Agent 生命周期里的控制点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;input；&lt;/li&gt;
&lt;li&gt;LLM；&lt;/li&gt;
&lt;li&gt;state；&lt;/li&gt;
&lt;li&gt;tool execution；&lt;/li&gt;
&lt;li&gt;output。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，Agent 在接收输入、调用模型、更新状态、执行工具、输出结果的关键节点，都可以被策略检查、拦截、记录、要求人工确认。&lt;/p&gt;
&lt;p&gt;这才是企业真正需要的东西。&lt;/p&gt;
&lt;p&gt;企业不缺 demo。企业缺的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Agent 能访问哪些数据；&lt;/li&gt;
&lt;li&gt;能调用哪些工具；&lt;/li&gt;
&lt;li&gt;什么时候必须人工批准；&lt;/li&gt;
&lt;li&gt;哪些行为必须阻断；&lt;/li&gt;
&lt;li&gt;日志如何审计；&lt;/li&gt;
&lt;li&gt;出错后如何回放；&lt;/li&gt;
&lt;li&gt;多框架、多模型、多工具怎么统一治理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;微软的 Agent 365 也是这个逻辑：把 Entra、Defender、Purview 放进同一个控制面，让企业能观察、治理、保护不同框架里运行的 Agent。&lt;/p&gt;
&lt;p&gt;这说明 Agent 竞争已经进入生产化阶段。生产化的核心不是“模型更聪明”，而是“行为可控”。&lt;/p&gt;
&lt;h2&gt;五、论文界也在指向同一个结论：治理必须发生在运行时&lt;a href=&quot;#五论文界也在指向同一个结论治理必须发生在运行时&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这不是微软一家在讲。&lt;/p&gt;
&lt;p&gt;2026 年 3 月的论文《Runtime Governance for AI Agents: Policies on Paths》提出一个很关键的观点：Agent 的行为是非确定的、路径依赖的，不能只靠设计时规则治理。真正有效的治理对象应该是 execution path，也就是 Agent 已经做了什么、下一步准备做什么、当前组织状态是什么。&lt;/p&gt;
&lt;p&gt;这和传统权限控制不同。&lt;/p&gt;
&lt;p&gt;传统权限控制问的是：这个用户有没有权限访问这个 API？&lt;br /&gt;
Agent 运行时治理问的是：在当前任务路径下、基于前面已经发生的动作，这个 Agent 下一步调用这个工具是否仍然安全？&lt;/p&gt;
&lt;p&gt;这就是差异。&lt;/p&gt;
&lt;p&gt;比如一个采购 Agent：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单独看“查询供应商报价”是安全的；&lt;/li&gt;
&lt;li&gt;单独看“生成采购单”也是安全的；&lt;/li&gt;
&lt;li&gt;单独看“发邮件给供应商”也可能安全；&lt;/li&gt;
&lt;li&gt;但如果它先被某个供应商网页注入了误导信息，再绕过比价流程，再生成采购单，再发给财务审批，这条路径整体就可能不安全。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以 Agent 风险不是单点风险，而是路径风险。&lt;/p&gt;
&lt;p&gt;另一篇 2026 年 4 月论文《From Governance Norms to Enforceable Controls》也指出，现有治理标准如 ISO/NIST 很重要，但它们本身不会自动变成可执行的 runtime guardrails。需要把治理目标翻译到架构、运行时策略、人工升级、审计证据这些不同层。&lt;/p&gt;
&lt;p&gt;这正是当前热点的本质：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI governance 正在从“写原则”变成“拦动作”。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;六、真正的新战场：入口层、执行层、控制层&lt;a href=&quot;#六真正的新战场入口层执行层控制层&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;把 Meta、OpenAI、Microsoft 和论文放在一起看，可以得到一个清晰结构。&lt;/p&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;争夺的东西&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;入口层&lt;/td&gt;&lt;td&gt;Meta Business Agent 进入 WhatsApp/Instagram/Messenger&lt;/td&gt;&lt;td&gt;谁掌握用户和商家的日常对话入口&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;执行层&lt;/td&gt;&lt;td&gt;Codex 从编码扩展到知识工作、并行任务、工作流自动化&lt;/td&gt;&lt;td&gt;谁能把任务变成可委派执行线程&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;控制层&lt;/td&gt;&lt;td&gt;Microsoft ACS、Agent 365、ASSERT、Foundry observability&lt;/td&gt;&lt;td&gt;谁能定义 Agent 的权限、审计、拦截和治理标准&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;这三层合起来，才是 2026 年 6 月 AI Agent 的真实热点。&lt;/p&gt;
&lt;p&gt;不是“Agent 会不会动手”。&lt;br /&gt;
而是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agent 在哪个入口接单，在什么环境执行，由哪个控制平面监管。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这就是下一代软件平台的雏形。&lt;/p&gt;
&lt;h2&gt;七、这会改变什么&lt;a href=&quot;#七这会改变什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;第一，客服会先被重构。&lt;br /&gt;
因为客户咨询天然发生在对话里，而 Agent 最适合从对话中提取意图、调用商品库、预约系统、订单系统。&lt;/p&gt;
&lt;p&gt;第二，知识工作会变成“多线程委派”。&lt;br /&gt;
未来白领不是打开一个工具做一件事，而是同时把多个任务丢给多个 Agent：一个查资料，一个做表格，一个写报告，一个准备会议纪要。&lt;/p&gt;
&lt;p&gt;第三，企业安全边界会前移到 Agent loop。&lt;br /&gt;
传统安全系统看进程、网络、文件、账号。Agent 时代还要看：用户意图、工具调用、模型判断、状态变化、审批记录。&lt;/p&gt;
&lt;p&gt;第四，软件入口会从 App 变成 Agent。&lt;br /&gt;
用户不一定打开 CRM、ERP、表格、客服后台，而是告诉 Agent 要做什么。Agent 再去调用底层系统。&lt;/p&gt;
&lt;p&gt;这会带来一个新的平台问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;未来企业买的可能不是一个个 SaaS，而是能被 Agent 安全调用的一组能力。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;八、最大风险：Agent 会让错误变成动作&lt;a href=&quot;#八最大风险agent-会让错误变成动作&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;传统 AI 出错，通常是生成错误内容。&lt;br /&gt;
Agent 出错，可能是执行错误动作。&lt;/p&gt;
&lt;p&gt;它可能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;给客户错误承诺；&lt;/li&gt;
&lt;li&gt;推荐不存在的商品；&lt;/li&gt;
&lt;li&gt;错误预约；&lt;/li&gt;
&lt;li&gt;错误退款；&lt;/li&gt;
&lt;li&gt;泄露客户数据；&lt;/li&gt;
&lt;li&gt;调错内部系统；&lt;/li&gt;
&lt;li&gt;在错误上下文里关闭销售；&lt;/li&gt;
&lt;li&gt;因被注入而执行攻击者意图。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也是为什么运行时治理会成为热点。&lt;/p&gt;
&lt;p&gt;因为你不能只问模型“你安全吗”。你必须在它每次准备行动时问：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个动作是否符合用户真实意图？&lt;/li&gt;
&lt;li&gt;这个工具调用是否超出授权？&lt;/li&gt;
&lt;li&gt;这个数据是否能被发出去？&lt;/li&gt;
&lt;li&gt;当前路径是否已经偏离策略？&lt;/li&gt;
&lt;li&gt;是否需要人工审批？&lt;/li&gt;
&lt;li&gt;是否留下足够审计证据？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI Agent 的关键不是“让 AI 自由发挥”，而是“让 AI 在边界内自主完成任务”。&lt;/p&gt;
&lt;h2&gt;九、结论：Agent 的未来不是更像人，而是更像可审计员工&lt;a href=&quot;#九结论agent-的未来不是更像人而是更像可审计员工&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;过去我们常把 Agent 想象成“数字员工”。但真正的员工不是只靠聪明工作，而是处在组织制度里：有岗位、有权限、有审批、有绩效、有审计、有责任边界。&lt;/p&gt;
&lt;p&gt;AI Agent 也会走向这个方向。&lt;/p&gt;
&lt;p&gt;2026 年 6 月的最新信号已经很清楚：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Meta 把 Agent 放进商业对话入口；&lt;/li&gt;
&lt;li&gt;OpenAI 把 Codex 从编码工具扩成知识工作执行层；&lt;/li&gt;
&lt;li&gt;Microsoft 把 Agent 生产化的重点放在控制、评估、审计和治理；&lt;/li&gt;
&lt;li&gt;学术界开始把 runtime governance 当成核心问题，而不是附属安全模块。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI Agent 的下一场战争，不是谁的 Agent 最会动手，而是谁能让 Agent 在真实业务里被安全地允许动手。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><category>category:Agent</category><category>tag:python</category><category>tag:agent</category></item><item><title>RAG文档解析 - pypdf、LlamaParse、DeepDoc、SimpleDirectoryReader到底怎么选？</title><link>https://star-xp.top/post/file-parse</link><guid isPermaLink="false">file-parse</guid><description>RAG文档解析 - pypdf、LlamaParse、DeepDoc、SimpleDirectoryReader</description><pubDate>Tue, 05 May 2026 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近做 RAG 项目时，很多人第一步就卡在“文档解析”上。&lt;/p&gt;
&lt;p&gt;表面看，好像都是把 PDF 里的内容读出来，但不知道要如何选择 &lt;code&gt;pypdf.extract_text()&lt;/code&gt;、&lt;code&gt;LlamaParse&lt;/code&gt;、&lt;code&gt;DeepDoc&lt;/code&gt;、&lt;code&gt;SimpleDirectoryReader&lt;/code&gt; 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;SimpleDirectoryReader 是入口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pypdf.extract_text() 是底层文本抽取&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;LlamaParse 是面向 LLM 的文档解析服务&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;DeepDoc 是偏重型的文档理解流水线&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1. 如何选择?&lt;a href=&quot;#1-如何选择&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;简单 PDF，本地快速抽字，用 &lt;code&gt;pypdf.extract_text()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;需要保留标题、段落、表格、阅读顺序，做 RAG 数据预处理，优先看 &lt;code&gt;LlamaParse&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;扫描件、复杂表格、企业知识库、私有化部署场景，可以看 RAGFlow 里的 &lt;code&gt;DeepDoc&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果你只是想把一个目录里的文件统一加载进 LlamaIndex，&lt;code&gt;SimpleDirectoryReader&lt;/code&gt; 才是入口，但它本身不决定解析质量。&lt;/p&gt;
&lt;h2&gt;2. 为什么 &lt;a href=&quot;https://pypi.org/project/pypdf/&quot;&gt;pypdf&lt;/a&gt;  不是“文档理解”&lt;a href=&quot;#2-为什么-pypdf--不是文档理解&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pypdf.extract_text()&lt;/code&gt; 的定位很清楚，就是从 PDF 页面对象里提取文字。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; pypdf &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; PdfReader&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;reader &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; PdfReader(&lt;/span&gt;&lt;span&gt;&quot;a.pdf&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; page &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; reader.pages:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    text &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; page.extract_text() &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它的优势是轻、快、本地、不需要 API key。根据 PyPI，&lt;code&gt;pypdf&lt;/code&gt; 在 2026-05-09 发布到 &lt;code&gt;6.11.0&lt;/code&gt;，说明这个库仍然很活跃。&lt;/p&gt;
&lt;p&gt;但它的边界也很明显：PDF 本身不是为了机器理解设计的。pypdf 官方文档也强调，复杂 PDF 中表格、页眉页脚、阅读顺序、扫描件都会带来问题；如果页面本质是图片，pypdf 并不是 OCR 工具。&lt;/p&gt;
&lt;p&gt;新版 pypdf 也支持 &lt;code&gt;extraction_mode=&quot;layout&quot;&lt;/code&gt;，可以尽量贴近页面布局：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; page.extract_text(&lt;/span&gt;&lt;span&gt;extraction_mode&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;layout&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但要注意，这仍然不是语义解析。它只能尽力还原“看起来像什么”，不真正知道哪里是标题、哪里是表格、哪里是引用。&lt;/p&gt;
&lt;h2&gt;3. &lt;a href=&quot;https://developers.api.llamaindex.ai/cloud/llamaparse&quot;&gt;LlamaParse&lt;/a&gt;  的核心价值&lt;a href=&quot;#3-llamaparse--的核心价值&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;LlamaParse 的思路不是“把字抽出来就完事”，而是把复杂文档变成更适合 LLM 使用的数据。&lt;/p&gt;
&lt;p&gt;官方文档把它描述为把非结构化文档转成 LLM-ready data 的解析服务，支持文本、Markdown、JSON 等结果，当前 LlamaCloud 还强调 Agentic OCR 和 130+ 格式处理。&lt;/p&gt;
&lt;p&gt;这意味着它更适合下面这种链路：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;PDF / DOCX / PPTX&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;LlamaParse&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Markdown / JSON / 页面结构&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;chunking&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;embedding&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;retrieval&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;RAG answer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比如在 LlamaIndex 里，可以让 &lt;code&gt;SimpleDirectoryReader&lt;/code&gt; 把 PDF 分发给 LlamaParse：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; llama_parse &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; LlamaParse&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; llama_index.core &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; SimpleDirectoryReader&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;parser &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; LlamaParse(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    api_key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;llx-...&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    result_type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;markdown&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;documents &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; SimpleDirectoryReader(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;./data&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    file_extractor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;.pdf&quot;&lt;/span&gt;&lt;span&gt;: parser},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;).load_data()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我的判断是：如果目标是 RAG，而不是单纯抽字，LlamaParse 通常比 pypdf 更接近生产需求。&lt;/p&gt;
&lt;p&gt;但它的问题也现实：云服务、成本、延迟、数据上传、合规审查。这些不是技术小问题，而是项目能不能上线的问题。&lt;/p&gt;
&lt;h2&gt;4. &lt;a href=&quot;https://raw.githubusercontent.com/infiniflow/ragflow/main/deepdoc/README.md&quot;&gt;DeepDoc&lt;/a&gt;  更像“文档理解基础设施”&lt;a href=&quot;#4-deepdoc--更像文档理解基础设施&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这里的 DeepDoc 指 RAGFlow 中的 DeepDoc。&lt;/p&gt;
&lt;p&gt;DeepDoc 做的事情更重。官方 README 里把它分成 vision 和 parser 两部分，vision 里有 OCR、layout recognition、table structure recognition。它还会处理表格旋转、表格结构、图表、页面坐标等问题。&lt;/p&gt;
&lt;p&gt;它的 PDF parser 输出不只是纯文本，还包括：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;带页面位置的文本块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;表格裁剪图和表格内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;图片、caption、图片内文本&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以 DeepDoc 更适合这类场景：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;扫描件 PDF&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;表格密集型财报&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;合同、说明书、企业制度文档&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;需要私有化部署的知识库&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;对引用、溯源、页面定位要求高的系统&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但 DeepDoc 的代价也很明显。RAGFlow 官方自托管要求至少 4 核 CPU、16GB RAM、50GB 磁盘，并依赖 Docker。它不是一个随手 pip install 就能结束的轻量 parser，而更像企业 RAG 平台里的文档处理底座。&lt;/p&gt;
&lt;h2&gt;5. &lt;a href=&quot;https://developers.llamaindex.ai/python/framework/module_guides/loading/simpledirectoryreader/&quot;&gt;SimpleDirectoryReader&lt;/a&gt;  不负责“变聪明”&lt;a href=&quot;#5-simpledirectoryreader--不负责变聪明&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;SimpleDirectoryReader&lt;/code&gt; 很容易被误解。&lt;/p&gt;
&lt;p&gt;它的作用是读取目录、识别文件类型、调用对应 reader，最后返回 LlamaIndex 的 &lt;code&gt;Document&lt;/code&gt; 对象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; llama_index.core &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; SimpleDirectoryReader&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;documents &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; SimpleDirectoryReader(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    input_dir&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;./data&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    recursive&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    required_exts&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;.pdf&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;.docx&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;).load_data()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它可以扫描目录、递归读取、限制后缀、附加文件元数据，也可以通过 &lt;code&gt;file_extractor&lt;/code&gt; 替换默认解析器。&lt;/p&gt;
&lt;p&gt;但它自己不是高级解析器。&lt;/p&gt;
&lt;p&gt;所以真正影响质量的是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.pdf 默认 reader 是谁？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;是否替换成 LlamaParse？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;是否走 DeepDoc？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;是否需要 OCR？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;是否要保留表格结构？&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SimpleDirectoryReader&lt;/code&gt; 更像调度入口，不是解析能力本身。&lt;/p&gt;
&lt;h2&gt;6. 四者对比&lt;a href=&quot;#6-四者对比&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&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;适合场景&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;&lt;code&gt;pypdf.extract_text()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;底层 PDF 文本抽取&lt;/td&gt;&lt;td&gt;简单 PDF、本地 baseline&lt;/td&gt;&lt;td&gt;免费、本地、快&lt;/td&gt;&lt;td&gt;结构弱，不做 OCR&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;LlamaParse&lt;/code&gt;&lt;/td&gt;&lt;td&gt;云端 LLM 文档解析&lt;/td&gt;&lt;td&gt;RAG 数据预处理、复杂 PDF&lt;/td&gt;&lt;td&gt;Markdown/JSON 友好，结构更好&lt;/td&gt;&lt;td&gt;成本、延迟、数据合规&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;DeepDoc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;文档理解流水线&lt;/td&gt;&lt;td&gt;扫描件、表格、企业知识库&lt;/td&gt;&lt;td&gt;OCR、布局、表格能力强&lt;/td&gt;&lt;td&gt;部署重、维护成本高&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SimpleDirectoryReader&lt;/code&gt;&lt;/td&gt;&lt;td&gt;文件加载入口&lt;/td&gt;&lt;td&gt;LlamaIndex 多文件导入&lt;/td&gt;&lt;td&gt;简单统一，可扩展&lt;/td&gt;&lt;td&gt;解析质量取决于背后 parser&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;7. 现在的技术趋势&lt;a href=&quot;#7-现在的技术趋势&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;截至 2026-05-10，文档解析已经不只是“PDF 转文本”了。&lt;/p&gt;
&lt;p&gt;RAGFlow 已经把 DeepDoc 放在“深度文档理解”位置，并且在更新记录里提到支持 MinerU、Docling 等文档解析方法。LlamaCloud 也把 Parse、Extract、Classify、Split、Index 拆成更完整的文档智能链路。&lt;/p&gt;
&lt;p&gt;这里有个变化值得注意：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;过去：提取文本 → 分块 → 向量化&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;现在：解析结构 → 保留版面 → 表格/图片理解 → 生成可引用 chunk → 检索&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，RAG 的质量问题，很多时候不是 embedding 模型不行，而是文档一开始就被解析坏了。&lt;/p&gt;
&lt;h2&gt;8. 选型建议&lt;a href=&quot;#8-选型建议&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果只是做 demo：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;SimpleDirectoryReader + 默认 PDF reader&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;或&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pypdf.extract_text()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果要做一个像样的 RAG：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;SimpleDirectoryReader + LlamaParse&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果数据不能出内网：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Docling / MinerU / DeepDoc / 自建 OCR pipeline&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果是企业知识库，并且文档质量很差：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;RAGFlow + DeepDoc&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;或者自建 DeepDoc 类似流水线&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果文档主要是电子版 PDF，排版简单，不要一上来就上重型方案。先用 pypdf 做 baseline，把失败样本收集出来，再决定是否升级到 LlamaParse 或 DeepDoc。&lt;/p&gt;
&lt;h2&gt;结尾&lt;a href=&quot;#结尾&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这四个工具不是谁替代谁，而是处在不同的层级，选择的不是最优，而是最合适。&lt;/p&gt;
&lt;p&gt;真正合理的架构应该是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;目录入口：SimpleDirectoryReader&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;简单文件：pypdf&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;复杂文件：LlamaParse&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;扫描件/企业复杂文档：DeepDoc&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;最终目标：高质量 Document / chunk / citation&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>category:Agent</category><category>category:RAG</category><category>tag:python</category><category>tag:agent</category><category>tag:RAG</category></item><item><title>JavaScript ：从基础语法到 DOM/BOM 进阶（完整版）</title><link>https://star-xp.top/post/js-basics</link><guid isPermaLink="false">js-basics</guid><description>本文为 JavaScript 基础与进阶笔记的完全合集，涵盖书写方式、数据类型、函数对象、DOM 树操作、事件流、BOM 对象及本地存储等全部细节。</description><pubDate>Fri, 06 Feb 2026 18:30:00 GMT</pubDate><content:encoded>&lt;h2&gt;第一部分：JavaScript 基础语法&lt;a href=&quot;#第一部分javascript-基础语法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;js 的三种书写方式&lt;a href=&quot;#js-的三种书写方式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;行内：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; onclick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;alert&lt;/span&gt;&lt;span&gt;(&apos;行内&apos;)&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;内部：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;内部&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;外部：&lt;/strong&gt;
外部 js 标签中间不能书写东西&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;./01.js.html&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;js 的输入输出&lt;a href=&quot;#js-的输入输出&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;输入语法：&lt;/strong&gt;
页面打印：
&lt;code&gt;document.write(&apos;页面打印&apos;)&lt;/code&gt;
​[编辑]​
控制台打印：
&lt;code&gt;console.log(&apos;控制台打印&apos;)&lt;/code&gt;
​[编辑]​
弹出界面：
&lt;code&gt;console.log(&apos;控制台打印&apos;)&lt;/code&gt;
​[编辑]​&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入语法：&lt;/strong&gt;
&lt;code&gt;prompt(&apos;请输入信息&apos;)&lt;/code&gt;
​[编辑]​&lt;/p&gt;
&lt;h3&gt;注释&lt;a href=&quot;#注释&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;单行注释：&lt;/strong&gt;
&lt;code&gt;//我是单行注释&lt;/code&gt;
&lt;strong&gt;多行注释：&lt;/strong&gt;
&lt;code&gt;/*我是多行注释*/&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;变量&lt;a href=&quot;#变量&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;变量是用来储存数据的容器，简单理解就是一个盒子&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//声明并赋值一个年龄变量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; age &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 18&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//输出&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;alert&lt;/span&gt;&lt;span&gt;(age);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//声明一个年龄变量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; age;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//赋值&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;age &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 18&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//输出&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;alert&lt;/span&gt;&lt;span&gt;(age);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数组&lt;a href=&quot;#数组&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义数组：&lt;/strong&gt;
&lt;code&gt;let arr = [1, 2, 3, 4, 5];console.log(arr); // 输出 [1, 2, 3, 4, 5]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数组的增删改查：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;增加元素：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;push ( )&lt;/code&gt; 向数组的末尾添加一个或多个元素
&lt;code&gt;const arr = [1, 2, 3]; arr.push(4);console.log(arr); // 输出 [1, 2, 3, 4]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unshift ( )&lt;/code&gt; 向数组的开头添加一个或多个元素
&lt;code&gt;const arr = [1, 2, 3]; arr.unshift(0);console.log(arr); // 输出 [0, 1, 2, 3]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;splice ( )&lt;/code&gt; 可以在任意位置插入元素，并且可以同时删除元素。
&lt;code&gt;const arr = [1, 2, 3]; arr.splice(1, 0, 1.5); // 在索引1的位置插入1.5console.log(arr); // 输出 [1, 1.5, 2, 3]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;删除元素：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pop()&lt;/code&gt; 从数组的末尾移除一个元素，并返回该元素
&lt;code&gt;const arr = [1, 2, 3];const removed = arr.pop();console.log(arr); // 输出 [1, 2]console.log(removed); // 输出 3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shift()&lt;/code&gt; 从数组的开头移除一个元素，并返回该元素。
&lt;code&gt;const arr = [1, 2, 3];const removed = arr.shift();console.log(arr); // 输出 [2, 3]console.log(removed); // 输出 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;splice()&lt;/code&gt; 可以从数组中删除元素，并且可以同时插入元素。
&lt;code&gt;const arr = [1, 2, 3, 4, 5]; arr.splice(1, 2); // 从索引1开始删除2个元素console.log(arr); // 输出 [1, 4, 5]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改元素：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以直接通过索引访问和修改数组中的元素
&lt;code&gt;const arr = [1, 2, 3]; arr[1] = 2.5;console.log(arr); // 输出 [1, 2.5, 3]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;查询元素：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返回数组中某个元素第一次出现的位置索引，如果不存在则返回-1
&lt;code&gt;const arr = [1, 2, 3, 2, 4];const index = arr.indexOf(2);console.log(index); // 输出 1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;数据类型&lt;a href=&quot;#数据类型&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;基本数据类型：&lt;/strong&gt;
基本数据类型是值类型，它们直接存储在栈内存中，而不是通过引用访问。当赋值给另一个变量时，复制的是具体的值，而不是引用。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;number&lt;/strong&gt;: 表示数值，包括整数和浮点数。 &lt;code&gt;let num = 42;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;string&lt;/strong&gt;: 表示文本字符串。 &lt;code&gt;let str = &quot;Hello, world!&quot;;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;boolean&lt;/strong&gt;: 表示逻辑值，只有 true 和 false 两个值。 &lt;code&gt;let flag = true;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;undifined&lt;/strong&gt;: 表示尚未赋值的变量或函数返回的未定义值。 &lt;code&gt;let x;console.log(x); // 输出 undefined&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;null&lt;/strong&gt;: 表示空值或空对象指针。 &lt;code&gt;let nothing = null;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&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;let person = { name: &quot;Alice&quot;, age: 30, sayHello: function() { console.log(&quot;Hello, my name is &quot; + this.name); } };&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数组&lt;/strong&gt;: 数组是一种特殊的对象，用于存储有序的元素列表。数组的元素可以是任何类型的数据。
&lt;code&gt;let numbers = [1, 2, 3, 4, 5];let mixed = [&quot;apple&quot;, 42, true, {name: &quot;Alice&quot;}];console.log(numbers[0]); // 输出 1console.log(mixed[3].name); // 输出 &quot;Alice&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;函数&lt;/strong&gt;: 函数也是一种对象，可以作为值进行传递，并且可以作为对象的属性或方法。
&lt;code&gt;function greet(name) { console.log(&quot;Hello, &quot; + name + &quot;!&quot;); }&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;数据转换&lt;a href=&quot;#数据转换&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;隐式转换：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;11&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 11&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;//number&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;11&quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 11&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;//string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;111&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;//string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&quot;123&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;//number&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&quot;123&quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 132&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;//number&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;显示转换：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; str &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;123&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; num &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Number&lt;/span&gt;&lt;span&gt;(str); &lt;/span&gt;&lt;span&gt;//number&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; num1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt;str; &lt;/span&gt;&lt;span&gt;//number&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; num &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 123&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; str &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;(num); &lt;/span&gt;&lt;span&gt;//string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; str1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; num.&lt;/span&gt;&lt;span&gt;toStrign&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;//string&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;运算符&lt;a href=&quot;#运算符&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;算数运算符：&lt;/strong&gt;&lt;/p&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;示例&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;+&lt;/td&gt;&lt;td&gt;加法&lt;/td&gt;&lt;td&gt;5 + 3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;减法&lt;/td&gt;&lt;td&gt;5 - 3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;_&lt;/td&gt;&lt;td&gt;乘法&lt;/td&gt;&lt;td&gt;5 _ 3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;/&lt;/td&gt;&lt;td&gt;除法&lt;/td&gt;&lt;td&gt;5 / 3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;%&lt;/td&gt;&lt;td&gt;取模（求余数）&lt;/td&gt;&lt;td&gt;5 % 3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;**&lt;/td&gt;&lt;td&gt;幂运算（次方）&lt;/td&gt;&lt;td&gt;2 ** 3&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;比较运算符：&lt;/strong&gt;&lt;/p&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;示例&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;==&lt;/td&gt;&lt;td&gt;等于（值相等即可）&lt;/td&gt;&lt;td&gt;5 == “5”&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;===&lt;/td&gt;&lt;td&gt;严格等于（值和类型都相等）&lt;/td&gt;&lt;td&gt;5 === “5”&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;!=&lt;/td&gt;&lt;td&gt;不等于&lt;/td&gt;&lt;td&gt;5 != 3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;!==&lt;/td&gt;&lt;td&gt;严格不等于&lt;/td&gt;&lt;td&gt;5 !== “5”&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&amp;lt;&lt;/td&gt;&lt;td&gt;小于&lt;/td&gt;&lt;td&gt;5 &amp;lt; 3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&amp;gt;&lt;/td&gt;&lt;td&gt;大于&lt;/td&gt;&lt;td&gt;5 &amp;gt; 3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&amp;lt;=&lt;/td&gt;&lt;td&gt;小于等于&lt;/td&gt;&lt;td&gt;5 &amp;lt;= 5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&amp;gt;=&lt;/td&gt;&lt;td&gt;大于等于&lt;/td&gt;&lt;td&gt;5 &amp;gt;= 5&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;逻辑运算符：&lt;/strong&gt;&lt;/p&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;示例&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&amp;amp;&amp;amp;&lt;/td&gt;&lt;td&gt;逻辑与（AND）&lt;/td&gt;&lt;td&gt;true &amp;amp;&amp;amp; false&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;逻辑非（NOT）&lt;/td&gt;&lt;td&gt;!true&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;一元运算符：&lt;/strong&gt;&lt;/p&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;示例&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;++&lt;/td&gt;&lt;td&gt;前置/后置自增&lt;/td&gt;&lt;td&gt;++x 或 x++&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;—&lt;/td&gt;&lt;td&gt;前置/后置自减&lt;/td&gt;&lt;td&gt;—x 或 x—&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;+&lt;/td&gt;&lt;td&gt;正号（强制转换为数字）&lt;/td&gt;&lt;td&gt;+5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;负号（取反）&lt;/td&gt;&lt;td&gt;-5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;!&lt;/td&gt;&lt;td&gt;逻辑非&lt;/td&gt;&lt;td&gt;!true&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;typeof&lt;/td&gt;&lt;td&gt;获取变量的类型&lt;/td&gt;&lt;td&gt;typeof x&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;delete&lt;/td&gt;&lt;td&gt;删除对象的属性&lt;/td&gt;&lt;td&gt;delete obj.prop&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;函数&lt;a href=&quot;#函数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;函数声明：&lt;/strong&gt;
&lt;code&gt;function greet(name) { console.log(&quot;Hello, &quot; + name + &quot;!&quot;); }&lt;/code&gt;
&lt;strong&gt;函数表达式：&lt;/strong&gt;
&lt;code&gt;const greet = function(name) { console.log(&quot;Hello, &quot; + name + &quot;!&quot;); };&lt;/code&gt;
&lt;strong&gt;立即执行表达式：&lt;/strong&gt;
&lt;code&gt;(function(name) { console.log(&quot;Hello, &quot; + name + &quot;!&quot;); })(&quot;Charlie&quot;);&lt;/code&gt;
&lt;strong&gt;箭头函数：&lt;/strong&gt;
&lt;code&gt;const greet = (name) =&amp;gt; { console.log(&quot;Hello, &quot; + name + &quot;!&quot;); };&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;对象&lt;a href=&quot;#对象&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;对象的创建：&lt;/strong&gt;
使用字面量方式创建：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//定义对象&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; obj &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name: &lt;/span&gt;&lt;span&gt;&quot;阿伟&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  age: &lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  phone: &lt;/span&gt;&lt;span&gt;12323232323&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(obj);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用构造函数创建：
&lt;code&gt;const person = new Object(); person.name = &quot;Bob&quot;; person.age = 25;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对象增删改查：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;增加属性：&lt;/strong&gt;
使用标点： &lt;code&gt;const person = {}; person.name = &quot;Alice&quot;; person.age = 30;&lt;/code&gt;
使用括号： &lt;code&gt;const person = {}; person[&quot;name&quot;] = &quot;Alice&quot;; person[&quot;age&quot;] = 30;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;修改属性：&lt;/strong&gt;
&lt;code&gt;const person = { name: &quot;Alice&quot;, age: 30 }; person.age = 31;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删除属性：&lt;/strong&gt;
&lt;code&gt;const person = { name: &quot;Alice&quot;, age: 30 }; delete person.age;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查找对象属性：&lt;/strong&gt;
&lt;code&gt;Object.keys(person); // 输出 [&quot;name&quot;, &quot;age&quot;]&lt;/code&gt;
&lt;code&gt;Object.values(person); // 输出 [&quot;Alice&quot;, 30]&lt;/code&gt;
&lt;code&gt;Object.entries(person); // 输出 [[&quot;name&quot;, &quot;Alice&quot;], [&quot;age&quot;, 30]]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;遍历对象：&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; person&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { name: &lt;/span&gt;&lt;span&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span&gt;, age: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;, city: &lt;/span&gt;&lt;span&gt;&quot;New York&quot;&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; person) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (person.&lt;/span&gt;&lt;span&gt;hasOwnProperty&lt;/span&gt;&lt;span&gt;(key)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;}: ${&lt;/span&gt;&lt;span&gt;person&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;第二部分：JavaScript 进阶 DOM 与 BOM&lt;a href=&quot;#第二部分javascript-进阶-dom-与-bom&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;DOM&lt;a href=&quot;#dom&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;概念：&lt;/strong&gt;
DOM（文档对象模型）是一种编程接口…[省略重复概念描述]…DOM 把文档视为树形结构。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DOM 树节点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;元素节点、属性节点、文本节点、注释节点、文档节点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;获取 dom 节点&lt;a href=&quot;#获取-dom-节点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 获取 id 名的 dom 节点：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; divById&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;my_div&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; divById_&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;#div&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;divById.style.backgroundColor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;yellow&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 获取 class 类名的 dom 节点:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; divsByClass&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementsByClassName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;div&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; divsByClass_&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.div&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; divsByClass_all&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelectorAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.div&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;divsByClass[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].style.backgroundColor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;lightblue&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;*&lt;em&gt;3. 获得 data-&lt;em&gt;为名的节点：&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; one&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;div&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(one.dataset.id); &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;为 dom 节点添加删除切换 class 类&lt;a href=&quot;#为-dom-节点添加删除切换-class-类&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;box.classList.add(&apos;active&apos;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;box.classList.remove(&apos;active&apos;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;box.classList.toggle(&apos;active&apos;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;定时器&lt;a href=&quot;#定时器&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;延迟函数：&lt;/strong&gt;
&lt;code&gt;setTimeout(greet, 5000);&lt;/code&gt;
&lt;code&gt;clearTimeout(timeoutId);&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;事件监听&lt;a href=&quot;#事件监听&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 鼠标事件&lt;/strong&gt;
&lt;code&gt;click&lt;/code&gt;, &lt;code&gt;dblclick&lt;/code&gt;, &lt;code&gt;mousedown&lt;/code&gt;, &lt;code&gt;mouseup&lt;/code&gt;, &lt;code&gt;mousemove&lt;/code&gt;, &lt;code&gt;mouseover&lt;/code&gt;, &lt;code&gt;mouseout&lt;/code&gt;, &lt;code&gt;mouseenter&lt;/code&gt; (推荐), &lt;code&gt;mouseleave&lt;/code&gt; (推荐)。
&lt;strong&gt;2. 键盘事件&lt;/strong&gt;
&lt;code&gt;keydown&lt;/code&gt;, &lt;code&gt;keyup&lt;/code&gt;。
&lt;strong&gt;3. 表单事件&lt;/strong&gt;
&lt;code&gt;submit&lt;/code&gt;, &lt;code&gt;change&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;focus&lt;/code&gt;, &lt;code&gt;blur&lt;/code&gt;, &lt;code&gt;select&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;事件流&lt;a href=&quot;#事件流&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;捕获阶段&lt;/strong&gt;: 从 document 开始向下。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标阶段&lt;/strong&gt;: 事件到达目标。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;冒泡阶段&lt;/strong&gt;: 从目标向上。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;capture 参数：&lt;/strong&gt;
&lt;code&gt;addEventListener(..., ..., true)&lt;/code&gt; 捕获阶段执行。
&lt;code&gt;addEventListener(..., ..., false)&lt;/code&gt; 冒泡阶段执行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阻止冒泡：&lt;/strong&gt;
&lt;code&gt;e.stopPropagation()&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;事件解绑&lt;a href=&quot;#事件解绑&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;button.onclick = null&lt;/code&gt;
&lt;code&gt;button.removeEventListener(&apos;click&apos;, fu)&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;阻止表单默认提交行为&lt;a href=&quot;#阻止表单默认提交行为&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;e.preventDefault()&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;页面尺寸事件&lt;a href=&quot;#页面尺寸事件&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scrollLeft&lt;/code&gt; / &lt;code&gt;scrollTop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;offsetLeft&lt;/code&gt; / &lt;code&gt;offsetTop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clientWidth&lt;/code&gt; / &lt;code&gt;clientHeight&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;offsetWidth&lt;/code&gt; / &lt;code&gt;offsetHeight&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;日期对象&lt;a href=&quot;#日期对象&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;const date = new Date()&lt;/code&gt;
&lt;code&gt;getFullYear()&lt;/code&gt;, &lt;code&gt;getMonth() + 1&lt;/code&gt;, &lt;code&gt;getDate()&lt;/code&gt;, &lt;code&gt;getDay()&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;时间戳&lt;a href=&quot;#时间戳&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Date.now()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+new Date()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;date.getTime()&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;BOM&lt;a href=&quot;#bom&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定时器函数：&lt;/strong&gt;
&lt;code&gt;setInterval(sayHello, 2000);&lt;/code&gt;
&lt;code&gt;clearInterval(intervalId);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;location 对象：&lt;/strong&gt;
&lt;code&gt;location.href = &apos;跳转地址&apos;&lt;/code&gt;
&lt;code&gt;location.reload()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;history 对象：&lt;/strong&gt;
&lt;code&gt;history.back()&lt;/code&gt; / &lt;code&gt;history.forward()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;localStorage 对象：&lt;/strong&gt;
&lt;code&gt;localStorage.setItem(&apos;test&apos;, &apos;dom&apos;)&lt;/code&gt;
&lt;code&gt;localStorage.getItem(&apos;test&apos;)&lt;/code&gt;&lt;/p&gt;</content:encoded><category>category:前端</category><category>category:JavaScript</category><category>tag:JavaScript</category><category>tag:DOM</category><category>tag:BOM</category><category>tag:编程基础</category></item><item><title>大文件上传：分片上传与断点续传的深度实现</title><link>https://star-xp.top/post/large-file-upload-sharding</link><guid isPermaLink="false">large-file-upload-sharding</guid><description>深入探讨 2GB+ 大文件的分片切割、SHA-256 哈希校验、指数退避重试机制以及后端流式合并与安全防护。</description><pubDate>Sun, 01 Feb 2026 22:31:24 GMT</pubDate><content:encoded>&lt;p&gt;上一篇我们实现了并发控制和安全防线，解决了多文件上传的问题。但如果面试官继续追问：“如果用户要上传一个 2GB 的视频文件怎么办？“或者”上传到一半网络断了，用户需要从头开始吗？“这就需要分片上传和断点续传了。&lt;/p&gt;
&lt;h2&gt;核心概念&lt;a href=&quot;#核心概念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;分片上传 - 大文件切割&lt;a href=&quot;#分片上传---大文件切割&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;传统的文件上传是将整个文件一次性发送到服务器，这对于大文件来说有几个致命问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;浏览器内存溢出：一次性读取 2GB 文件到内存，浏览器直接崩溃&lt;/li&gt;
&lt;li&gt;请求超时：大文件传输时间长，容易触发服务器超时限制&lt;/li&gt;
&lt;li&gt;失败代价高：上传 99% 时网络断开，所有进度全部丢失&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;分片上传的核心思路是：&lt;strong&gt;将大文件切成小块（如 2MB），逐个上传，最后在服务器端合并&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;┌─────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│                    2GB 视频文件                       │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└─────────────────────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                         ↓ Blob.slice()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│ 2MB  │ │ 2MB  │ │ 2MB  │ │ ...  │ │ 2MB  │  共 1024 个分片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└──────┘ └──────┘ └──────┘ └──────┘ └──────┘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ↓        ↓        ↓        ↓        ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;┌─────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│              服务器 chunks/{hash}/ 目录               │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   000000   000001   000002   ...   001023           │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└─────────────────────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                         ↓ 合并&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;┌─────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│                    完整视频文件                       │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└─────────────────────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;断点续传 - 网络中断恢复&lt;a href=&quot;#断点续传---网络中断恢复&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;断点续传的原理很简单：&lt;strong&gt;上传前先问服务器”你已经收到了哪些分片？“，然后只上传缺失的部分&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 伪代码：断点续传核心逻辑&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; uploadedChunks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; 查询服务器已有分片&lt;/span&gt;&lt;span&gt;(fileHash);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; totalChunks; i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (uploadedChunks.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(i)) &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 跳过已上传的&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; 上传分片&lt;/span&gt;&lt;span&gt;(chunks[i]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; 合并分片&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;文件哈希 - 唯一标识&lt;a href=&quot;#文件哈希---唯一标识&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;如何判断两次上传的是同一个文件？我们需要计算文件的哈希值作为唯一标识。这里使用 &lt;strong&gt;SHA-256&lt;/strong&gt; 而不是 MD5，因为现代 CPU 对 SHA-256 有硬件加速，实际速度比 MD5 快 2-4 倍。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 使用 Web Crypto API 计算 SHA-256&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; hashBuffer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; crypto.subtle.&lt;/span&gt;&lt;span&gt;digest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;SHA-256&quot;&lt;/span&gt;&lt;span&gt;, fileBuffer);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;前端实现&lt;a href=&quot;#前端实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我们在现有代码基础上，新增分片上传的核心函数。整体思路是：先计算文件哈希作为唯一标识，然后将文件切成固定大小的分片，逐个上传到服务器，最后通知服务器合并。&lt;/p&gt;
&lt;h3&gt;分片配置&lt;a href=&quot;#分片配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;首先我们需要确定分片的大小。这是一个需要权衡的决策点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;太小（如 256KB）：分片数量多，HTTP 请求开销大，每个请求都有 TCP 握手和 HTTP 头部的额外成本&lt;/li&gt;
&lt;li&gt;太大（如 10MB）：单个分片失败后重传代价高，在不稳定网络下容易超时&lt;/li&gt;
&lt;li&gt;2MB：适合大多数网络环境，是一个经过实践验证的平衡点&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 分片大小：固定 2MB，简单可靠&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; CHUNK_SIZE&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; 1024&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; 1024&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;计算文件哈希&lt;a href=&quot;#计算文件哈希&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文件哈希是整个分片上传和断点续传的基石。我们使用浏览器原生的 &lt;strong&gt;Web Crypto API&lt;/strong&gt; 来计算 SHA-256 哈希值。这个 API 的优势在于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;原生实现，性能远超 JS 库&lt;/li&gt;
&lt;li&gt;现代 CPU 对 SHA-256 有硬件加速（Intel SHA-NI 指令集）&lt;/li&gt;
&lt;li&gt;返回的是 ArrayBuffer，需要转换为十六进制字符串便于传输&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 使用 Web Crypto API 计算 SHA-256（比 MD5 更快）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; calculateHash&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; File&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 将文件读取为 ArrayBuffer，这一步会将整个文件加载到内存&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; buffer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; file.&lt;/span&gt;&lt;span&gt;arrayBuffer&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 调用原生加密 API 计算哈希&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; hashBuffer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; crypto.subtle.&lt;/span&gt;&lt;span&gt;digest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;SHA-256&quot;&lt;/span&gt;&lt;span&gt;, buffer);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 将 ArrayBuffer 转换为十六进制字符串&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; Array.&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Uint8Array&lt;/span&gt;&lt;span&gt;(hashBuffer))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; b.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;padStart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;切割文件为分片&lt;a href=&quot;#切割文件为分片&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文件切割使用的是 &lt;strong&gt;Blob.slice()&lt;/strong&gt; 方法。这个方法有一个非常重要的特性：它是&lt;strong&gt;惰性的&lt;/strong&gt;（lazy）。调用 &lt;code&gt;slice()&lt;/code&gt; 并不会立即读取文件内容到内存，而是创建一个指向原文件特定区域的引用。只有在实际使用这个分片（比如通过 FormData 发送）时，才会真正读取对应的字节。&lt;/p&gt;
&lt;p&gt;这意味着即使是一个 10GB 的文件，切成 5000 个分片，内存中也只是存储了 5000 个轻量级的 Blob 引用，而不是 10GB 的数据。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 使用 Blob.slice() 切割文件，不会一次性读取整个文件到内存&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; createChunks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; File&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Blob&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; chunks&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Blob&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; start &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 循环切割，每次取 CHUNK_SIZE 大小的片段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  while&lt;/span&gt;&lt;span&gt; (start &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; file.size) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // slice(start, end) 返回 [start, end) 区间的数据引用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    chunks.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(file.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(start, start &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; CHUNK_SIZE&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    start &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; CHUNK_SIZE&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; chunks;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;上传单个分片（带重试）&lt;a href=&quot;#上传单个分片带重试&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;网络环境是不可预测的，单个分片上传可能因为网络抖动、服务器繁忙等原因失败。为了提高上传的健壮性，我们实现了&lt;strong&gt;指数退避重试&lt;/strong&gt;策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一次失败：等待 1 秒后重试&lt;/li&gt;
&lt;li&gt;第二次失败：等待 2 秒后重试&lt;/li&gt;
&lt;li&gt;第三次失败：等待 4 秒后重试&lt;/li&gt;
&lt;li&gt;超过最大重试次数：抛出错误，由上层处理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种策略的好处是，在服务器临时过载的情况下，逐渐增加的等待时间给了服务器恢复的机会，同时避免了频繁重试造成的雪崩效应。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; uploadChunk&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  chunk&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Blob&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  index&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  hash&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  filename&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  retryCount &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 构建 FormData，这是分片上传的标准格式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; formData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; FormData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  formData.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;chunk&quot;&lt;/span&gt;&lt;span&gt;, chunk);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  formData.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;index&quot;&lt;/span&gt;&lt;span&gt;, index.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  formData.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;hash&quot;&lt;/span&gt;&lt;span&gt;, hash);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  formData.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;filename&quot;&lt;/span&gt;&lt;span&gt;, filename);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; attempt &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;; attempt &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; retryCount; attempt&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; axios.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;http://localhost:3000/upload/chunk&quot;&lt;/span&gt;&lt;span&gt;, formData, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        timeout: &lt;/span&gt;&lt;span&gt;60000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// 60秒超时，大分片需要足够的传输时间&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 成功则返回&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (attempt &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; retryCount) &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; error;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 指数退避：1s, 2s, 4s&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; delay&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;pow&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, attempt);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setTimeout&lt;/span&gt;&lt;span&gt;(r, delay));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;完整上传流程（含断点续传）&lt;a href=&quot;#完整上传流程含断点续传&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;现在我们把所有模块组合起来，实现完整的上传流程。这个流程包含六个关键步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;计算哈希&lt;/strong&gt;：生成文件的唯一标识，用于后续的分片管理和断点续传&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;初始化状态&lt;/strong&gt;：在响应式 Map 中记录上传状态，用于 UI 展示&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询已有分片&lt;/strong&gt;：这是断点续传的核心，询问服务器已经收到了哪些分片&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上传缺失分片&lt;/strong&gt;：跳过已存在的分片，只上传缺失的部分&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;请求合并&lt;/strong&gt;：所有分片上传完成后，通知服务器将分片合并为完整文件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新状态&lt;/strong&gt;：标记上传成功或失败&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;整个过程中，我们使用 Vue 3 的 &lt;code&gt;reactive&lt;/code&gt; 来管理上传状态。这里有一个技巧：使用 &lt;code&gt;Map&lt;/code&gt; 而不是普通对象，是因为 Map 的键可以是任意值（我们用文件哈希作为键），而且 Vue 3 对 Map 有完整的响应式支持。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 上传状态接口定义&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; UploadState&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  file&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; File&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  hash&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  status&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;uploading&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;success&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;error&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  progress&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  error&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 使用 Map 存储多个文件的上传状态，键为文件哈希&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; uploadStates&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; reactive&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Map&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;UploadState&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Map&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; uploadFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; File&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 1. 计算文件哈希 - 这是后续所有操作的基础&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; hash&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; calculateHash&lt;/span&gt;&lt;span&gt;(file);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; chunks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; createChunks&lt;/span&gt;&lt;span&gt;(file);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; totalChunks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; chunks.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 2. 初始化上传状态 - 用于 UI 展示&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  uploadStates.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(hash, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    file,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    hash,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    status: &lt;/span&gt;&lt;span&gt;&quot;uploading&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    progress: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 3. 【断点续传关键】查询服务器已有哪些分片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 如果是新上传，返回空数组；如果是续传，返回已有的分片索引&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; axios.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`http://localhost:3000/upload/status/${&lt;/span&gt;&lt;span&gt;hash&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; uploaded&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Set&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;&amp;gt;(res.data.uploadedChunks);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      `文件 ${&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;} 已上传 ${&lt;/span&gt;&lt;span&gt;uploaded&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;totalChunks&lt;/span&gt;&lt;span&gt;} 个分片`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 4. 只上传缺失的分片 - 这就是断点续传的实现&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; completed &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; uploaded.size;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; totalChunks; i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (uploaded.&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(i)) &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 跳过已上传的分片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; uploadChunk&lt;/span&gt;&lt;span&gt;(chunks[i], i, hash, file.name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      completed&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 实时更新进度，让用户看到上传进展&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; uploadStates.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(hash);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (state) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        state.progress &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;round&lt;/span&gt;&lt;span&gt;((completed &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; totalChunks) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 5. 所有分片上传完成，请求服务器合并&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 传入预期大小用于服务器端校验&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; axios.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;http://localhost:3000/upload/merge&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      hash,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      filename: file.name,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      totalChunks,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      expectedSize: file.size,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 6. 标记上传成功&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; uploadStates.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(hash);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (state) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      state.status &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;success&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      state.progress &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 错误处理：记录错误信息，UI 可以展示重试按钮&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; uploadStates.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(hash);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (state) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      state.status &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;error&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      state.error &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; error &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; error.message &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;上传失败&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;进度显示组件&lt;a href=&quot;#进度显示组件&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;有了上传状态数据，我们需要一个组件来展示。这个组件遍历 &lt;code&gt;uploadStates&lt;/code&gt; Map，为每个正在上传的文件显示进度条和状态。&lt;/p&gt;
&lt;p&gt;组件的几个设计要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;v-for&lt;/code&gt; 遍历 Map 时，解构为 &lt;code&gt;[hash, state]&lt;/code&gt; 键值对&lt;/li&gt;
&lt;li&gt;进度条使用 CSS &lt;code&gt;width&lt;/code&gt; 百分比实现，配合 &lt;code&gt;transition&lt;/code&gt; 实现平滑动画&lt;/li&gt;
&lt;li&gt;错误状态下显示重试按钮，提升用户体验&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;upload-list&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; v-for&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;[hash, state] in uploadStates&quot;&lt;/span&gt;&lt;span&gt; :key=&quot;hash&quot;&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;upload-item&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;file-info&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;filename&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{{ state.file.name }}&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;status&quot;&lt;/span&gt;&lt;span&gt; :class=&quot;state.status&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          {{ state.status &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;uploading&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; &apos;上传中&apos;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; state.status &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;success&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; &apos;成功&apos;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &apos;失败&apos;&lt;/span&gt;&lt;span&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;progress-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;progress-fill&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          :style=&quot;{&lt;/span&gt;&lt;span&gt; width:&lt;/span&gt;&lt;span&gt; state.progress&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; &apos;%&apos;&lt;/span&gt;&lt;span&gt; }&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          :class=&quot;{&lt;/span&gt;&lt;span&gt; error:&lt;/span&gt;&lt;span&gt; state.status&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; &apos;error&apos;&lt;/span&gt;&lt;span&gt; }&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;progress-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{{ state.progress }}%&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; v-if&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;state.status === &apos;error&apos;&quot;&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;error-info&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;{{ state.error }}&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; @click=&quot;retryUpload(state.file)&quot;&amp;gt;重试&amp;lt;/button&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;style&lt;/span&gt;&lt;span&gt; scoped&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.upload-item {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  padding: 12px;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background: #f9f9f9;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  border&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;radius: 8px;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  margin&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;bottom: 10px;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.progress-bar {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  height: 8px;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background: #e0e0e0;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  border&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;radius: 4px;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  overflow: hidden;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  margin: 8px &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.progress-fill {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  height: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background: linear&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;gradient&lt;/span&gt;&lt;span&gt;(90deg, #4caf50, #8bc34a);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  transition: width 0.2s;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.progress-fill.error {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background: #f44336;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.status.uploading { color: #1976d2; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.status.success { color: #4caf50; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.status.error { color: #f44336; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;后端实现&lt;a href=&quot;#后端实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;后端需要新增三个核心接口来配合前端的分片上传：&lt;strong&gt;接收分片&lt;/strong&gt;、&lt;strong&gt;查询状态&lt;/strong&gt;、&lt;strong&gt;合并分片&lt;/strong&gt;。此外还需要处理文件名安全和过期分片清理。&lt;/p&gt;
&lt;h3&gt;目录结构&lt;a href=&quot;#目录结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在开始编码前，我们先明确服务器端的文件组织结构。分片临时存储在 &lt;code&gt;chunks/&lt;/code&gt; 目录下，以文件哈希为子目录名，每个分片以索引命名。合并完成的文件存储在 &lt;code&gt;uploads/&lt;/code&gt; 目录。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;server/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ├── uploads/      # 完整文件存储&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ├── chunks/       # 临时分片存储&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  │   └── {hash}/   # 每个文件的分片目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  │       ├── 000000&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  │       ├── 000001&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  │       └── ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  └── index.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;接收分片接口&lt;a href=&quot;#接收分片接口&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是分片上传的入口接口。每次请求接收一个分片，将其存储到对应的目录中。&lt;/p&gt;
&lt;p&gt;关键设计点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;以哈希为目录名&lt;/strong&gt;：相同文件的分片会自动归类到同一目录，便于管理和合并&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;零填充命名&lt;/strong&gt;：使用 &lt;code&gt;padStart(6, &apos;0&apos;)&lt;/code&gt; 确保分片按数字顺序排列，避免 &lt;code&gt;1, 10, 2&lt;/code&gt; 这样的字典序问题&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用 &lt;code&gt;fs.renameSync&lt;/code&gt;&lt;/strong&gt;：Multer 已经将文件保存到临时目录，我们只需要移动到目标位置，比复制更高效&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 首先确保分片存储目录存在&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; chunksDir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(__dirname, &lt;/span&gt;&lt;span&gt;&quot;chunks&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;fs.&lt;/span&gt;&lt;span&gt;existsSync&lt;/span&gt;&lt;span&gt;(chunksDir)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fs.&lt;/span&gt;&lt;span&gt;mkdirSync&lt;/span&gt;&lt;span&gt;(chunksDir);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 接收单个分片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/upload/chunk&quot;&lt;/span&gt;&lt;span&gt;, upload.&lt;/span&gt;&lt;span&gt;single&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;chunk&quot;&lt;/span&gt;&lt;span&gt;), (&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;hash&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;filename&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; req.body;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 参数校验：确保必要信息都已提供&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;req.file &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;hash &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ msg: &lt;/span&gt;&lt;span&gt;&quot;参数不完整&quot;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 以文件哈希为文件夹名存储分片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 这样相同文件的分片会自动归类在一起&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; chunkDir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(chunksDir, hash);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;fs.&lt;/span&gt;&lt;span&gt;existsSync&lt;/span&gt;&lt;span&gt;(chunkDir)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fs.&lt;/span&gt;&lt;span&gt;mkdirSync&lt;/span&gt;&lt;span&gt;(chunkDir, { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 使用零填充命名（000000, 000001...），确保排序正确&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 6位数字支持最多 999999 个分片，对于 2MB 分片大小，可支持约 2TB 文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; chunkPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(chunkDir, &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;(index).&lt;/span&gt;&lt;span&gt;padStart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fs.&lt;/span&gt;&lt;span&gt;renameSync&lt;/span&gt;&lt;span&gt;(req.file.path, chunkPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ msg: &lt;/span&gt;&lt;span&gt;&quot;分片上传成功&quot;&lt;/span&gt;&lt;span&gt;, index: &lt;/span&gt;&lt;span&gt;parseInt&lt;/span&gt;&lt;span&gt;(index) });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查询上传状态接口（断点续传核心）&lt;a href=&quot;#查询上传状态接口断点续传核心&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这个接口是实现断点续传的关键。前端在开始上传前调用此接口，获取服务器已经收到的分片列表，然后跳过这些分片，只上传缺失的部分。&lt;/p&gt;
&lt;p&gt;接口设计非常简单：根据文件哈希找到对应的分片目录，读取目录中的所有文件名（即分片索引），返回给前端。如果目录不存在，说明是新上传，返回空数组。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 查询已上传的分片列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 这个接口是断点续传的核心，前端据此决定哪些分片需要上传&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/upload/status/:hash&quot;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;hash&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; req.params;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; chunkDir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(chunksDir, hash);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 目录不存在说明是新上传，返回空数组&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;fs.&lt;/span&gt;&lt;span&gt;existsSync&lt;/span&gt;&lt;span&gt;(chunkDir)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ uploadedChunks: [] });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 读取目录中的所有分片文件，提取索引&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 过滤条件：只保留纯数字命名的文件（排除 .DS_Store 等系统文件）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; uploadedChunks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;readdirSync&lt;/span&gt;&lt;span&gt;(chunkDir)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;span&gt;+$&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;test&lt;/span&gt;&lt;span&gt;(name))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(name))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;sort&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; a &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; b);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ uploadedChunks });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;合并分片接口&lt;a href=&quot;#合并分片接口&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;当所有分片上传完成后，前端调用此接口通知服务器进行合并。这是整个上传流程中最关键的一步，需要处理多个边界情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;分片完整性检查&lt;/strong&gt;：确保收到的分片数量与预期一致&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流式合并&lt;/strong&gt;：使用 &lt;code&gt;createWriteStream&lt;/code&gt; 逐个写入分片，避免一次性加载所有分片到内存&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大小校验&lt;/strong&gt;：合并后检查文件大小是否与预期一致，防止数据丢失&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全校验&lt;/strong&gt;：复用现有的二进制魔数校验，确保文件类型合法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;清理工作&lt;/strong&gt;：合并成功后删除临时分片目录，释放磁盘空间&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误回滚&lt;/strong&gt;：如果合并过程中出错，删除不完整的目标文件&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 合并所有分片为完整文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/upload/merge&quot;&lt;/span&gt;&lt;span&gt;, express.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;hash&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;filename&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;totalChunks&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;expectedSize&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; req.body;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; chunkDir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(chunksDir, hash);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 首先检查分片目录是否存在&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;fs.&lt;/span&gt;&lt;span&gt;existsSync&lt;/span&gt;&lt;span&gt;(chunkDir)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ msg: &lt;/span&gt;&lt;span&gt;&quot;分片目录不存在&quot;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 检查分片完整性：实际分片数量必须等于预期数量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; existingChunks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;readdirSync&lt;/span&gt;&lt;span&gt;(chunkDir)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;span&gt;+$&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;test&lt;/span&gt;&lt;span&gt;(name))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;sort&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(a) &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(b));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (existingChunks.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; totalChunks) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      msg: &lt;/span&gt;&lt;span&gt;&quot;分片不完整&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      expected: totalChunks,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      received: existingChunks.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 安全处理文件名，防止路径穿越攻击&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; safeFilename&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; sanitizeFilename&lt;/span&gt;&lt;span&gt;(filename);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; filePath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(uploadDir, &lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}-${&lt;/span&gt;&lt;span&gt;safeFilename&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 使用流式写入合并分片，避免内存溢出&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 对于 2GB 的文件，如果一次性读取所有分片，内存会直接爆掉&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; writeStream&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;createWriteStream&lt;/span&gt;&lt;span&gt;(filePath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 按顺序读取并写入每个分片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; totalChunks; i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; chunkPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(chunkDir, &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;(i).&lt;/span&gt;&lt;span&gt;padStart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; chunkBuffer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;readFileSync&lt;/span&gt;&lt;span&gt;(chunkPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      writeStream.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(chunkBuffer);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 等待写入流完成&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;reject&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      writeStream.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;finish&quot;&lt;/span&gt;&lt;span&gt;, resolve);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      writeStream.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;error&quot;&lt;/span&gt;&lt;span&gt;, reject);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      writeStream.&lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 验证合并后的文件大小是否与预期一致&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 这一步可以检测出传输过程中的数据丢失&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; stats&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;statSync&lt;/span&gt;&lt;span&gt;(filePath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (expectedSize &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; stats.size &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; expectedSize) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      fs.&lt;/span&gt;&lt;span&gt;unlinkSync&lt;/span&gt;&lt;span&gt;(filePath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ msg: &lt;/span&gt;&lt;span&gt;&quot;文件大小不匹配，合并失败&quot;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 复用现有的二进制魔数校验，确保文件类型合法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 这是最后一道安全防线，防止恶意文件伪装&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; buffer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Buffer.&lt;/span&gt;&lt;span&gt;alloc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; fd&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;openSync&lt;/span&gt;&lt;span&gt;(filePath, &lt;/span&gt;&lt;span&gt;&quot;r&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fs.&lt;/span&gt;&lt;span&gt;readSync&lt;/span&gt;&lt;span&gt;(fd, buffer, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fs.&lt;/span&gt;&lt;span&gt;closeSync&lt;/span&gt;&lt;span&gt;(fd);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;isJPG&lt;/span&gt;&lt;span&gt;(buffer) &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;isPNG&lt;/span&gt;&lt;span&gt;(buffer) &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;isPDF&lt;/span&gt;&lt;span&gt;(buffer)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      fs.&lt;/span&gt;&lt;span&gt;unlinkSync&lt;/span&gt;&lt;span&gt;(filePath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;403&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ msg: &lt;/span&gt;&lt;span&gt;&quot;安全警告：文件类型验证失败&quot;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 合并成功，清理临时分片目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fs.&lt;/span&gt;&lt;span&gt;rmSync&lt;/span&gt;&lt;span&gt;(chunkDir, { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      msg: &lt;/span&gt;&lt;span&gt;&quot;文件合并成功&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      filename: path.&lt;/span&gt;&lt;span&gt;basename&lt;/span&gt;&lt;span&gt;(filePath),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      size: stats.size,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 出错时清理可能已创建的不完整文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (fs.&lt;/span&gt;&lt;span&gt;existsSync&lt;/span&gt;&lt;span&gt;(filePath)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      fs.&lt;/span&gt;&lt;span&gt;unlinkSync&lt;/span&gt;&lt;span&gt;(filePath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    res.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ msg: &lt;/span&gt;&lt;span&gt;&quot;合并失败: &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; error.message });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;文件名安全处理&lt;a href=&quot;#文件名安全处理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文件名安全是一个容易被忽视但非常重要的安全点。攻击者可能通过构造特殊的文件名来实现&lt;strong&gt;路径穿越攻击&lt;/strong&gt;（Path Traversal），例如 &lt;code&gt;../../etc/passwd&lt;/code&gt; 这样的文件名，如果不经处理直接拼接路径，可能会将文件写入到系统敏感目录。&lt;/p&gt;
&lt;p&gt;我们的安全处理策略包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unicode 规范化&lt;/strong&gt;：防止通过不同编码形式绕过检查&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;移除危险字符&lt;/strong&gt;：删除 &lt;code&gt;&amp;lt;&amp;gt;:&quot;/\|?*&lt;/code&gt; 等在文件系统中有特殊含义的字符&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提取文件名&lt;/strong&gt;：使用 &lt;code&gt;path.basename()&lt;/code&gt; 确保只保留文件名部分&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长度限制&lt;/strong&gt;：过长的文件名可能导致文件系统错误&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; sanitizeFilename&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;filename&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // Unicode NFC 规范化，防止字符编码绕过&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 例如 é 可以表示为单个字符或 e + 重音符的组合&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; safe &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; filename.&lt;/span&gt;&lt;span&gt;normalize&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;NFC&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 移除在 Windows/Linux 文件系统中有特殊含义的危险字符&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 同时移除控制字符 (0x00-0x1f)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  safe &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; safe.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;[&amp;lt;&amp;gt;:&quot;/&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;|?*\x00-\x1f]&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;_&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 只保留文件名部分，丢弃任何路径信息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 这是防止路径穿越攻击的关键步骤&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  safe &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;basename&lt;/span&gt;&lt;span&gt;(safe);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 限制文件名长度，大多数文件系统限制在 255 字节&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 我们保守地限制在 200 字符&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (safe.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; 200&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; ext&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;extname&lt;/span&gt;&lt;span&gt;(safe);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    safe &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; safe.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; ext.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; ext;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; safe &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &quot;unnamed&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;启动时清理过期分片&lt;a href=&quot;#启动时清理过期分片&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;用户可能在上传过程中关闭浏览器，或者上传失败后没有重试，这些情况会在服务器上留下未完成的分片目录。如果不清理，随着时间推移会占用大量磁盘空间。&lt;/p&gt;
&lt;p&gt;我们的策略是在服务器启动时执行一次清理，删除超过 24 小时的未完成上传。这个时间阈值的选择考虑了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;太短：用户可能第二天继续上传，不应该删除&lt;/li&gt;
&lt;li&gt;太长：占用磁盘空间&lt;/li&gt;
&lt;li&gt;24 小时：一个合理的平衡点&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 清理超过 24 小时的未完成上传&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 这些是用户中途放弃或上传失败后遗留的分片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cleanupOrphanedChunks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; maxAgeMs&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 24&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; 60&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; 60&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 24 小时&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; now&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;fs.&lt;/span&gt;&lt;span&gt;existsSync&lt;/span&gt;&lt;span&gt;(chunksDir)) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; sessions&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;readdirSync&lt;/span&gt;&lt;span&gt;(chunksDir);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; sessionId&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; sessions) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; sessionPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(chunksDir, sessionId);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 检查目录的最后修改时间&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; stats&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;statSync&lt;/span&gt;&lt;span&gt;(sessionPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (now &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; stats.mtimeMs &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; maxAgeMs) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 超过 24 小时，删除整个分片目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        fs.&lt;/span&gt;&lt;span&gt;rmSync&lt;/span&gt;&lt;span&gt;(sessionPath, { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`[清理] 已删除过期分片: ${&lt;/span&gt;&lt;span&gt;sessionId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`[清理] 处理 ${&lt;/span&gt;&lt;span&gt;sessionId&lt;/span&gt;&lt;span&gt;} 时出错:`&lt;/span&gt;&lt;span&gt;, error.message);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 服务器启动时执行一次清理&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cleanupOrphanedChunks&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;完整流程图&lt;a href=&quot;#完整流程图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;最后，让我们通过一个完整的流程图来回顾整个分片上传和断点续传的过程：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;用户选择文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;计算文件 SHA-256 哈希（生成唯一标识）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;查询服务器 GET /upload/status/{hash}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;获取已上传分片列表 [0, 1, 2, ...]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;切割文件为 2MB 分片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;循环上传缺失分片 POST /upload/chunk&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ├── 分片 0 (已存在，跳过)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ├── 分片 1 (已存在，跳过)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ├── 分片 2 → 上传 → 更新进度&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ├── 分片 3 → 上传 → 更新进度&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  └── ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;所有分片上传完成&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;请求合并 POST /upload/merge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;服务器合并分片 + 大小校验 + 安全校验&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;清理临时分片目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;返回上传成功&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下一篇我们将实现文件管理功能：列表展示、下载、删除，完成文件上传系统的最后一块拼图。&lt;/p&gt;</content:encoded><category>category:后端</category><category>category:NodeJs</category><category>tag:大文件上传</category><category>tag:分片上传</category></item><item><title>如何用AI写代码? vibe coding</title><link>https://star-xp.top/post/ai-coding</link><guid isPermaLink="false">ai-coding</guid><description>如何用AI写代码? vibe coding</description><pubDate>Mon, 05 Jan 2026 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;写在前面&lt;a href=&quot;#写在前面&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;vibe coding 不是“让 AI 替我写代码”这么简单。&lt;/p&gt;
&lt;p&gt;更准确地说，它是一种新的软件生产方式：人把意图、约束、上下文、验收标准和工程判断交给 AI，AI 把这些东西转化成原型、代码、文档、测试和可运行系统。它降低了从想法到代码的门槛，也放大了每一个模糊需求、错误假设和缺失测试。&lt;/p&gt;
&lt;p&gt;所以我对 vibe coding 的理解不是“更随意地写代码”，而是“更严肃地表达需求”。过去工程师的主要成本在写代码，现在主要成本开始转移到：定义问题、拆解模块、写清约束、审查结果、建立测试、处理联调、沉淀规则、持续迭代。&lt;/p&gt;
&lt;p&gt;AI 可以承担大量执行工作，但它不能承担责任。真正的责任仍然在使用 AI 的人身上。&lt;/p&gt;
&lt;h2&gt;1. vibe coding 到底是什么&lt;a href=&quot;#1-vibe-coding-到底是什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Collins Dictionary 把 “vibe coding” 选为 2025 年年度词，并把它描述为一种通过 AI 把自然语言转成计算机代码的软件开发方式。这个词由 Andrej Karpathy 在 2025 年初带火，用来描述一种“把需求说出来，让大模型生成代码，然后持续对话迭代”的开发状态。&lt;/p&gt;
&lt;p&gt;但是在工程项目里，不能只理解它的“vibe”。如果只是凭感觉让 AI 写，最后会得到一个看起来能跑、但没人真正理解、没人敢改、没人敢上线的系统。&lt;/p&gt;
&lt;p&gt;我更愿意把 vibe coding 分成两层：&lt;/p&gt;
&lt;p&gt;第一层是原型阶段的 vibe：范围可以宽，方向可以发散，重点是快速探索可能性。比如 UI 原型、交互方式、页面结构、产品概念、信息架构，这些阶段可以让 AI 多给方案，多试风格，多做变体。&lt;/p&gt;
&lt;p&gt;第二层是工程阶段的 coding：范围必须收窄，约束必须清晰，结果必须可验证。比如登录、支付、权限、数据库、AI agent 状态流转、任务队列、文件上传、安全策略，这些不能只靠感觉，必须靠规格、测试和审查。&lt;/p&gt;
&lt;p&gt;一句话：vibe coding 的前半段可以发散，后半段必须收敛。&lt;/p&gt;
&lt;h2&gt;2. 我理解的开发过程：从原型到系统&lt;a href=&quot;#2-我理解的开发过程从原型到系统&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;一个实际项目用 AI 开发，大概会经历两条线。&lt;/p&gt;
&lt;p&gt;第一条线是产品和 UI 线。&lt;/p&gt;
&lt;p&gt;它通常不是一次到位，而是从草图开始：先做一个能表达主流程的粗糙原型，再补状态、补页面、补交互细节；需求变化后继续调整，直到团队能围绕同一个可见版本讨论。&lt;/p&gt;
&lt;p&gt;这个阶段的重点不是代码质量，而是把想法变成可讨论的东西。很多需求只靠文字说不清楚，必须先看到页面、流程、状态、边界情况，团队才知道自己到底要什么。&lt;/p&gt;
&lt;p&gt;第二条线是工程实现线。&lt;/p&gt;
&lt;p&gt;这条线不能直接从原型跳到代码。更稳的顺序是：先把原型翻译成可实现方案，再拆出模块、接口、数据、权限和状态；随后从一个最小闭环开始实现，逐步补后端、前端、联调、AI 能力、测试、上线和反馈。&lt;/p&gt;
&lt;p&gt;这里最重要的是“可实现”。原型是想象，方案是桥梁，代码是落地。没有方案直接让 AI 写，很容易得到一个局部漂亮、整体混乱的项目。&lt;/p&gt;
&lt;p&gt;如果项目里包含真正的 AI 能力，我现在会把投入成本大概排成这样：&lt;/p&gt;
&lt;p&gt;AI 搭建 &amp;gt; 文档 &amp;gt; 测试 &amp;gt; 前后端联调 &amp;gt; 后端非 AI 部分 &amp;gt; 前端。&lt;/p&gt;
&lt;p&gt;这不是绝对规律，但它提醒我们一件事：AI 开发里最贵的往往不是“代码从无到有”，而是“系统从能演示到可信赖”。页面和接口可以很快生成，真正消耗时间的是输出可控、状态可追踪、失败可恢复、测试能覆盖、协议能联调、文档能支撑后续迭代。&lt;/p&gt;
&lt;p&gt;所以 AI 开发项目应该从小闭环开始。&lt;/p&gt;
&lt;p&gt;不要一开始就让 AI 实现一个完整 SaaS、完整 agent、完整后台、完整权限、完整支付。先做一个最小闭环：一个用户、一个核心流程、一个可验证结果、一个失败处理、一个测试入口。小闭环跑通以后，再扩展模块。&lt;/p&gt;
&lt;h2&gt;3. 倒金字塔：越往后越窄&lt;a href=&quot;#3-倒金字塔越往后越窄&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我认为 vibe coding 的形状是一个倒金字塔。&lt;/p&gt;
&lt;p&gt;最开始很宽：想法、风格、交互、模块边界、技术选型都可以开放，让 AI 帮你头脑风暴。&lt;/p&gt;
&lt;p&gt;中间开始收窄：确定页面、数据结构、接口协议、权限模型、状态机、任务拆分。&lt;/p&gt;
&lt;p&gt;最后必须很窄：一个 bug、一个接口、一个组件、一个测试、一个边界条件、一个 PR。&lt;/p&gt;
&lt;p&gt;很多 AI 开发失败，是因为一直停留在倒金字塔的上半部分。需求一直很宽，提示词一直很泛，验收一直很模糊，却期待 AI 交付一个稳定系统。&lt;/p&gt;
&lt;p&gt;AI 的选择空间越大，它越容易走向你没想到的方向。做原型时，这是优点；做工程时，这是风险。&lt;/p&gt;
&lt;p&gt;约束不是限制创造力，约束是把 AI 的能力导向正确结果。&lt;/p&gt;
&lt;h2&gt;4. AI 写代码的核心材料&lt;a href=&quot;#4-ai-写代码的核心材料&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;用 AI 写代码，不是只写一句 prompt。比较稳定的组合应该是：&lt;/p&gt;
&lt;p&gt;可实现文档 + 任务提示词 + 项目公共规则 + 可复用 Skill + 插件和工具 + review。&lt;/p&gt;
&lt;p&gt;其中最重要的是“可实现文档”。&lt;/p&gt;
&lt;p&gt;一个可实现文档至少应该回答：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个功能解决什么问题？&lt;/li&gt;
&lt;li&gt;用户路径是什么？&lt;/li&gt;
&lt;li&gt;哪些状态必须出现？&lt;/li&gt;
&lt;li&gt;哪些边界条件必须处理？&lt;/li&gt;
&lt;li&gt;涉及哪些页面、接口、表、任务、权限？&lt;/li&gt;
&lt;li&gt;输入输出是什么？&lt;/li&gt;
&lt;li&gt;不做什么？&lt;/li&gt;
&lt;li&gt;怎么验收？&lt;/li&gt;
&lt;li&gt;怎么测试？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 不是读心工具。你给它“做一个导入功能”，它会自行决定文件格式、校验方式、失败策略和页面交互；你给它“在运营后台增加客户线索 CSV 导入，先预览前 20 行，不预览不落库，确认后创建 import job，重复邮箱跳过并返回失败清单，复用现有批处理队列，测试覆盖空文件、字段缺失、部分成功和重复提交”，它才有可能做出接近工程要求的结果。&lt;/p&gt;
&lt;h2&gt;5. 一个好提示词的结构&lt;a href=&quot;#5-一个好提示词的结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我现在不太建议把提示词理解成固定八股。更好的方式是按问题裁剪，但大部分工程任务都离不开五类信息：&lt;/p&gt;
&lt;p&gt;目标：这次到底要交付什么。&lt;/p&gt;
&lt;p&gt;上下文：现有系统里已经有什么，不要让 AI 从零发明。&lt;/p&gt;
&lt;p&gt;边界：哪些可以改，哪些不能碰，哪些场景暂时不做。&lt;/p&gt;
&lt;p&gt;验收：怎样证明它完成了，最好能落到命令、页面路径或测试用例。&lt;/p&gt;
&lt;p&gt;输出：最后要汇报什么，避免 AI 只说“完成了”。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;你接手的是一个 B2B CRM 的运营后台。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;目标：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;新增“客户线索 CSV 导入”的最小闭环：上传文件 -&amp;gt; 后端解析 -&amp;gt; 页面预览 -&amp;gt; 用户确认 -&amp;gt; 批量写入 -&amp;gt; 返回导入结果。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;先读代码：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; imports 相关 route&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; lead service&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 数据库 schema&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 现有后台表格组件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 项目里已有的 job / queue 用法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这次只做：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 支持 UTF-8 CSV。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 必填字段是 companyName、contactName、email。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 预览阶段只校验字段和格式，不写数据库。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 确认导入后创建 import job。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 重复 email 跳过，并在结果里返回行号和原因。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这次不做：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; Excel 导入。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 字段自由映射。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 超大文件分片上传。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 导入历史详情页。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;实现约束：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 复用现有后台上传组件和错误提示样式。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 不引入新的 CSV 解析库，除非项目没有可用方案，并说明原因。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 不改 lead 表的无关字段。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 后端错误格式必须和现有 API 保持一致。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;验收：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 一个正常 CSV 可以预览并导入。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 缺少 email 的行不能导入，并在预览里标出原因。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 重复提交不会创建重复线索。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 至少运行导入相关的单元测试或接口测试。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;最后输出：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 修改了哪些文件。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 新增或调整了哪些接口。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 运行了什么验证。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 哪些能力仍然没做。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这类提示词看起来长，但它真正减少的是 AI 的自由发挥空间。不是每个任务都要写这么多；小 bug 可以短，大模块必须清楚。&lt;/p&gt;
&lt;p&gt;提示词不是魔法，它更像一份压缩版任务单。任务单越清楚，AI 越少替你做危险的业务假设。&lt;/p&gt;
&lt;h2&gt;6. 复杂模块不要一次追求完美&lt;a href=&quot;#6-复杂模块不要一次追求完美&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;复杂模块适合循序渐进，不适合一口吃完。&lt;/p&gt;
&lt;p&gt;以“AI 工单摘要”为例：&lt;/p&gt;
&lt;p&gt;第一步，能用：&lt;/p&gt;
&lt;p&gt;客服在工单详情页点击“生成摘要”，系统把对话内容发给模型，并返回一段可编辑摘要。&lt;/p&gt;
&lt;p&gt;第二步，好用：&lt;/p&gt;
&lt;p&gt;增加生成中状态、失败重试、空对话提示、摘要字数限制，以及人工编辑后的保存入口。&lt;/p&gt;
&lt;p&gt;第三步，快用：&lt;/p&gt;
&lt;p&gt;对同一工单的短时间重复生成做缓存；对长对话先做截断或分段摘要，避免接口超时和成本失控。&lt;/p&gt;
&lt;p&gt;第四步，爱用：&lt;/p&gt;
&lt;p&gt;支持按“客户情绪、待办事项、风险等级”拆分摘要，并把高风险工单自动推到主管审核队列。&lt;/p&gt;
&lt;p&gt;这个顺序的关键是：先闭环，再体验；先正确，再优雅；先可验证，再扩展。&lt;/p&gt;
&lt;p&gt;很多人用 AI 写代码时容易反过来：一开始就让 AI 生成“完整、优雅、可扩展、企业级、支持所有场景”的实现。结果就是代码量巨大、抽象过早、bug 很隐蔽、review 很痛苦。&lt;/p&gt;
&lt;p&gt;AI 会放大你的表达。如果你的目标是“全都要”，它就会真的给你一堆东西。&lt;/p&gt;
&lt;h2&gt;7. SDD：让规格成为源头&lt;a href=&quot;#7-sdd让规格成为源头&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在 AI 开发里，SDD 可以理解为 Spec-Driven Development，也就是规格驱动开发。&lt;/p&gt;
&lt;p&gt;传统开发里，代码往往是最主要的事实来源；而在 AI 协作里，规格应该变成更靠前的事实来源。因为 AI 生成代码的速度太快，如果没有规格约束，代码会比理解先增长。&lt;/p&gt;
&lt;p&gt;一个实用的 SDD 不一定很重。它可以很短，但必须能指导实现。&lt;/p&gt;
&lt;p&gt;我建议每个模块至少有一份这样的规格：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 模块名称&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 目标&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这个模块解决什么问题，最终用户是谁。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 非目标&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;明确这次不做什么，避免 AI 自行扩展。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 用户流程&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;用步骤描述用户从入口到结果的路径。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 数据模型&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;涉及哪些表、字段、状态、枚举。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 接口契约&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;请求、响应、错误码、权限要求。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 状态流转&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;列出 pending / running / succeeded / failed / canceled 等状态，以及谁能触发状态变化。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 边界条件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;空数据、重复提交、超时、并发、权限不足、外部服务失败。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 验收标准&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;什么情况算完成，什么情况算失败。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 测试计划&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;单元测试、集成测试、端到端测试、手工验证路径。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对 AI 模块尤其要写状态流转。比如一次 AI run，至少要明确：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;run 如何创建；&lt;/li&gt;
&lt;li&gt;run 什么时候进入 running；&lt;/li&gt;
&lt;li&gt;工具调用是否可重试；&lt;/li&gt;
&lt;li&gt;用户取消时如何处理；&lt;/li&gt;
&lt;li&gt;模型超时如何处理；&lt;/li&gt;
&lt;li&gt;部分输出是否保存；&lt;/li&gt;
&lt;li&gt;失败后用户看到什么；&lt;/li&gt;
&lt;li&gt;日志和 trace 存在哪里；&lt;/li&gt;
&lt;li&gt;哪些数据不能进入 prompt；&lt;/li&gt;
&lt;li&gt;哪些动作必须人工确认。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 模块最怕“看起来能跑”。看起来能跑不代表可恢复、可追踪、可审计、可扩展。&lt;/p&gt;
&lt;h2&gt;8. CLAUDE.md：公共规则要写下来&lt;a href=&quot;#8-claudemd公共规则要写下来&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;公共提示词应该沉淀到 CLAUDE.md 或类似的项目规则文件里。&lt;/p&gt;
&lt;p&gt;它不应该写成一篇大作文，而应该写成稳定、简洁、可验证的工程规则。比如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;## 四个工作原则&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 1. 编码前思考&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;不要假设，不要隐藏困惑，必须呈现关键权衡。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 开始修改前，先用 2-5 句话说明理解、假设和可能风险。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 如果需求存在多种解释，列出解释并说明推荐选项；无法合理判断时先问清楚。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 如果用户要求的方案明显复杂、脆弱或与现有架构冲突，先提出异议并给出更简单方案。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 不确定数据库字段、路由前缀、认证语义、AI run 状态流转时，必须先查代码再改。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 2. 简洁优先&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;用最少代码解决当前问题，不做过度推测。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 不添加需求之外的功能、配置项、抽象层或通用框架。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 不为一次性逻辑创建抽象。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 不为当前业务不可能发生的场景堆叠错误处理。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 如果实现开始变得臃肿，停下来简化；资深工程师会觉得复杂就重写得更直接。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 优先复用现有 services、routes、stores、components 和类型定义。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 3. 精准修改&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;只碰必须碰的代码，只清理自己造成的问题。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 每一行修改都必须能直接追溯到用户请求。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 不顺手重构、不顺手格式化、不改相邻无关代码。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 匹配所在文件现有风格，即使你更偏好另一种写法。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 发现无关死代码或坏味道时，在回复中指出即可，不主动删除。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 如果你的改动导致导入、变量、函数变成孤儿代码，要同步删除这些由本次改动造成的无用代码。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 4. 目标驱动执行&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;把任务转化为可验证目标，并循环验证直到达成。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 修 bug: 先明确复现条件，再修复，再验证复现条件消失。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 加校验: 优先补充或运行覆盖无效输入的检查。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 重构: 保证重构前后的行为和测试结果一致。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 多步骤任务要写短计划，每步包含验证方式。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 完成前至少运行与改动相关的最小验证；如果不能运行，说明原因和剩余风险。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;9. Skill：把经验变成可复用能力&lt;a href=&quot;#9-skill把经验变成可复用能力&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Skill 的价值在于跨会话、跨项目复用经验。&lt;/p&gt;
&lt;p&gt;如果你发现自己经常复制同一段提示词，就应该考虑把它变成 Skill。比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;code-review skill：检查 bug、边界条件、测试缺口和安全风险；&lt;/li&gt;
&lt;li&gt;api-design skill：根据项目规范设计接口；&lt;/li&gt;
&lt;li&gt;db-migration skill：检查 migration、索引、回滚和兼容性；&lt;/li&gt;
&lt;li&gt;ai-run-debug skill：排查 agent 状态、tool call、trace 和 prompt；&lt;/li&gt;
&lt;li&gt;frontend-polish skill：检查移动端、空状态、加载态、错误态、可访问性；&lt;/li&gt;
&lt;li&gt;release-check skill：上线前检查环境变量、迁移、监控和回滚。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个 Skill 可以很简单：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;code-quality-review&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;通用代码质量审查 skill。用于 review 代码、检查封装性/复用性/可维护性、发现过度抽象、职责混乱、边界泄漏、错误处理薄弱、类型设计问题、可读性问题，或在提交前做工程质量检查。适用于前端、后端、全栈、脚本和库代码。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Code Quality Review&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 工作方式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;1.&lt;/span&gt;&lt;span&gt; 先确认本次审查范围：整个 diff、某个文件、某个模块，还是某个设计方案。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;2.&lt;/span&gt;&lt;span&gt; 先读项目约定：&lt;/span&gt;&lt;span&gt;`CLAUDE.md`&lt;/span&gt;&lt;span&gt;、README、package scripts、测试配置、lint/format 配置。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;3.&lt;/span&gt;&lt;span&gt; 再读代码入口和调用链，不只看被改文件。重点看数据从哪里来、在哪里变形、在哪里产生副作用。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;4.&lt;/span&gt;&lt;span&gt; 以 bug、维护风险和边界问题优先，不把个人风格偏好伪装成问题。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;5.&lt;/span&gt;&lt;span&gt; 输出时按严重程度排序：正确性/安全性/数据一致性 &amp;gt; 可维护性 &amp;gt; 可读性 &amp;gt; 风格。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;6.&lt;/span&gt;&lt;span&gt; 如果用户要求修改，就直接修；如果用户只要求 review，就只给 findings，不擅自改代码。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 审查维度&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 职责：函数、组件、service、hook、class 是否各自只做一类事。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 边界：UI、业务、数据访问、网络、存储、权限、配置是否混在一起。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 抽象：是否解决了真实重复和复杂度，还是为了“看起来高级”而抽象。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 复用：重复逻辑是否该抽 helper；相似但语义不同的逻辑是否不该硬合并。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 类型：类型是否表达业务不变量，是否滥用 &lt;/span&gt;&lt;span&gt;`any`&lt;/span&gt;&lt;span&gt;、宽泛 object、字符串魔法值。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 错误处理：是否吞错、重复 catch、把用户错误当系统错误、丢失上下文。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 状态：是否存在竞态、旧请求污染新状态、缓存失效、状态来源不唯一。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 数据：是否有 N+1、分页缺失、事务边界缺失、跨租户/跨用户泄漏。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 可测试性：核心逻辑是否被 UI/IO 绑死，是否有可独立验证的边界。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 何时读取参考&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;需要更细的检查清单时，读取 &lt;/span&gt;&lt;span&gt;`references/review-checklist.md`&lt;/span&gt;&lt;span&gt;。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 输出格式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;如果是 review，使用：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```text&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;发现：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- [P1] 问题标题：文件:行。说明影响、触发条件、建议修复。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- [P2] ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;开放问题：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;总体判断：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果没有发现问题，明确说“没有发现明确 bug 或高风险设计问题”，再列剩余风险，例如“未运行测试”或“只审查了局部 diff”。&lt;/p&gt;
&lt;h2&gt;修改原则&lt;a href=&quot;#修改原则&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;小步改动，保持现有项目风格。&lt;/li&gt;
&lt;li&gt;优先删除不必要复杂度，再考虑新增抽象。&lt;/li&gt;
&lt;li&gt;不做无关重构，不顺手格式化全文件。&lt;/li&gt;
&lt;li&gt;把新抽象命名成业务语义，不用 &lt;code&gt;manager&lt;/code&gt;、&lt;code&gt;helper&lt;/code&gt;、&lt;code&gt;utils&lt;/code&gt; 兜底命名。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Skill 的本质是把个人经验产品化。它让 AI 不只是“会写代码”，而是逐渐学会“按你的工程习惯工作”。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 10. 四个工作原则&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;如果把上面的内容再压缩，我会把 vibe coding 的工程纪律总结成四条。这四条不是为了限制 AI，而是为了让 AI 的速度不会冲垮项目质量。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 10.1 先把不确定性摊开&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AI 开发最容易出问题的地方，不是模型不会写，而是它会把不确定的东西写成确定的代码。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;所以在动手前，应该先把三件事说清楚：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- 我理解这次要解决什么问题；&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- 我基于哪些现有事实做判断；&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- 还有哪些地方不确定，准备怎么验证。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;比如用户说“给报表加一个导出功能”，这句话至少有几种解释：导出当前筛选结果、导出全部数据、导出异步任务、导出带权限脱敏的数据。AI 如果直接写，很可能选错。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;更好的工作方式是先收窄：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;我先确认导出语义：这里应该导出当前筛选后的结果，而不是全量数据；导出字段沿用表格列，但联系电话和邮箱需要脱敏；文件生成走现有 report job，避免同步接口超时。接下来我会先查现有报表查询和 job 代码，再改导出入口。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特别是这些内容，不能靠猜：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据库字段；&lt;/li&gt;
&lt;li&gt;路由前缀；&lt;/li&gt;
&lt;li&gt;认证语义；&lt;/li&gt;
&lt;li&gt;权限边界；&lt;/li&gt;
&lt;li&gt;AI run 状态流转；&lt;/li&gt;
&lt;li&gt;队列重试策略；&lt;/li&gt;
&lt;li&gt;外部服务回调；&lt;/li&gt;
&lt;li&gt;支付和订单状态；&lt;/li&gt;
&lt;li&gt;文件上传和存储路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 最危险的能力不是不会做，而是它会在不知道的时候继续编。&lt;/p&gt;
&lt;h3&gt;10.2 先用小解法打通&lt;a href=&quot;#102-先用小解法打通&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;AI 很擅长把小需求写成大系统。它会自然倾向于补配置、补抽象、补扩展点、补框架感。问题是很多扩展点在当前阶段根本用不上，只会增加 review 和维护成本。&lt;/p&gt;
&lt;p&gt;所以默认策略应该是：先写一条最短可验证路径。&lt;/p&gt;
&lt;p&gt;比如要做“库存不足提醒”，第一版不需要先做一套通用规则引擎。更合理的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;后台配置一个固定阈值；&lt;/li&gt;
&lt;li&gt;每次库存变更后检查是否低于阈值；&lt;/li&gt;
&lt;li&gt;低于阈值时写入一条提醒；&lt;/li&gt;
&lt;li&gt;前端在商品详情展示提醒状态；&lt;/li&gt;
&lt;li&gt;测试覆盖库存从高到低、重复提醒、库存恢复。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;等到真的出现多个仓库、多个品类、多个阈值策略，再考虑抽象。不要为了“未来可能”牺牲当前清晰度。&lt;/p&gt;
&lt;p&gt;资深工程师对 AI 代码的一个重要动作，就是把“看起来很完整”的代码压回“刚好够用”的代码。&lt;/p&gt;
&lt;h3&gt;10.3 控制改动半径&lt;a href=&quot;#103-控制改动半径&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;AI 一次能改很多文件，这既是能力也是风险。工程上要关心的不只是功能有没有做出来，还要关心 diff 能不能被人理解。&lt;/p&gt;
&lt;p&gt;我会用“改动半径”来判断一次 AI 修改是否健康：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;半径 1：只改当前函数或组件，最容易 review；&lt;/li&gt;
&lt;li&gt;半径 2：改当前模块的 service、route、test，可以接受；&lt;/li&gt;
&lt;li&gt;半径 3：跨模块改公共类型、状态、协议，需要更强测试；&lt;/li&gt;
&lt;li&gt;半径 4：顺手重构架构、格式化大量文件，通常应该拆开。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;比如修一个“筛选条件刷新后丢失”的 bug，合理范围可能是查询参数同步和对应测试；如果 AI 顺手重写了整个表格状态管理，就算最后能跑，也会让风险变大。&lt;/p&gt;
&lt;p&gt;发现无关问题可以记录在回复里，或者单独开任务。不要把“我顺手看到了”变成“我顺手改了”。&lt;/p&gt;
&lt;h3&gt;10.4 用验证关门&lt;a href=&quot;#104-用验证关门&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;AI 说完成了没有意义，验证完成才有意义。&lt;/p&gt;
&lt;p&gt;不同类型任务应该有不同的关门方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修 bug：复现条件消失；&lt;/li&gt;
&lt;li&gt;做接口：请求、响应、错误码和权限都被验证；&lt;/li&gt;
&lt;li&gt;做 UI：加载、空态、错误态、移动端至少看过关键路径；&lt;/li&gt;
&lt;li&gt;做 AI 功能：正常输出、工具失败、模型超时、人工取消都能解释；&lt;/li&gt;
&lt;li&gt;做重构：行为不变，测试结果不变。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;比如修“订单列表偶发重复行”，不要只说“我加了去重”。更完整的闭环应该是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;复现：快速切换分页和筛选时，同一个订单会被追加两次。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;根因：请求返回顺序不可控，旧请求覆盖了新筛选状态。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;修复：给列表请求增加 request key，只接受最后一次请求结果。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;验证：新增并运行并发请求顺序测试，手工快速切换筛选没有再出现重复行。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;vibe coding 的完成标准不是代码生成完，而是风险被关闭到当前阶段可以接受。&lt;/p&gt;
&lt;h2&gt;11. Owner 意识：AI 不负责，人负责&lt;a href=&quot;#11-owner-意识ai-不负责人负责&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AI 需要人去规划、决策和 review。&lt;/p&gt;
&lt;p&gt;即使现在很多人在用 AI 开发代码，团队里也必须有人做 owner。owner 不是职位，而是一种工作方式。&lt;/p&gt;
&lt;p&gt;一个合格的 owner 至少要盯住六件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目标是否真的清楚，而不是只听起来合理；&lt;/li&gt;
&lt;li&gt;任务是否已经拆到 AI 可以稳定执行的粒度；&lt;/li&gt;
&lt;li&gt;风险是否及时暴露给团队，而不是藏在聊天记录里；&lt;/li&gt;
&lt;li&gt;结果是否被验收，而不是只看 AI 的完成声明；&lt;/li&gt;
&lt;li&gt;经验是否回流到文档、规则或 Skill；&lt;/li&gt;
&lt;li&gt;从需求、实现、联调、测试、上线到反馈，是否有人持续看完整链路。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 可以帮你写代码、补文档、查 bug、跑测试、生成方案，但它不会为线上事故负责，也不会为需求误解负责，更不会为团队协作成本负责。&lt;/p&gt;
&lt;p&gt;所以越是使用 AI，越要有 owner 意识。&lt;/p&gt;
&lt;p&gt;AI 降低了执行成本，但没有降低责任成本。&lt;/p&gt;
&lt;h2&gt;12. 团队中怎么用 AI&lt;a href=&quot;#12-团队中怎么用-ai&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;个人用 AI 写 demo，可以随意一点；团队用 AI 写生产代码，必须严谨。&lt;/p&gt;
&lt;p&gt;团队里需要统一几件事：&lt;/p&gt;
&lt;p&gt;第一，统一公共规则。&lt;/p&gt;
&lt;p&gt;哪些文件可以改，哪些命令必须跑，哪些安全边界不能碰，哪些代码风格必须遵守，这些应该写进 CLAUDE.md 或项目规则。&lt;/p&gt;
&lt;p&gt;第二，统一任务颗粒度。&lt;/p&gt;
&lt;p&gt;不要把“实现完整订单系统”丢给 AI。应该拆成：订单创建、库存锁定、支付回调、订单取消、超时关闭、退款、后台查询、测试覆盖。&lt;/p&gt;
&lt;p&gt;第三，统一 review 标准。&lt;/p&gt;
&lt;p&gt;AI 生成代码不能跳过 review。甚至更需要 review，因为它可能写出“看起来很合理但刚好错一点”的代码。&lt;/p&gt;
&lt;p&gt;第四，统一验收方式。&lt;/p&gt;
&lt;p&gt;每个模块完成时，都应该有可运行的测试、可点击的路径、可复现的验证方式或清晰的剩余风险说明。&lt;/p&gt;
&lt;p&gt;第五，统一沉淀机制。&lt;/p&gt;
&lt;p&gt;一次 AI 犯错不要只在聊天里纠正。重复出现的问题应该进入 CLAUDE.md、Skill、模板或 checklist。&lt;/p&gt;
&lt;p&gt;团队用 AI 的关键不是每个人都变快，而是团队整体吞吐变高、返工变少、质量不下降。&lt;/p&gt;
&lt;h2&gt;13. 不同阶段应该给 AI 不同角色&lt;a href=&quot;#13-不同阶段应该给-ai-不同角色&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;不要让 AI 在所有任务里都扮演同一个“全能工程师”。不同阶段要给它不同角色。&lt;/p&gt;
&lt;p&gt;原型阶段：&lt;/p&gt;
&lt;p&gt;你是产品设计师和前端原型工程师，目标是快速生成可讨论的 UI，不追求最终架构。&lt;/p&gt;
&lt;p&gt;方案阶段：&lt;/p&gt;
&lt;p&gt;你是技术负责人，目标是把原型拆成可实现模块，指出风险和依赖，给出阶段计划。&lt;/p&gt;
&lt;p&gt;后端阶段：&lt;/p&gt;
&lt;p&gt;你是后端工程师，必须复用现有 service、route、schema 和测试工具，不允许发明新架构。&lt;/p&gt;
&lt;p&gt;前端阶段：&lt;/p&gt;
&lt;p&gt;你是前端工程师，必须匹配现有组件风格，覆盖加载态、空态、错误态和移动端布局。&lt;/p&gt;
&lt;p&gt;联调阶段：&lt;/p&gt;
&lt;p&gt;你是全栈联调工程师，目标是检查接口契约、字段命名、错误码、状态同步和真实用户路径。&lt;/p&gt;
&lt;p&gt;AI 搭建阶段：&lt;/p&gt;
&lt;p&gt;你是 AI 系统工程师，重点是 prompt、tool、memory、state machine、trace、eval、fallback 和人工确认边界。&lt;/p&gt;
&lt;p&gt;测试阶段：&lt;/p&gt;
&lt;p&gt;你是测试工程师，重点是复现路径、边界输入、回归风险、端到端闭环和最小验证命令。&lt;/p&gt;
&lt;p&gt;Review 阶段：&lt;/p&gt;
&lt;p&gt;你是严格的 code reviewer，只关注 bug、风险、回归和测试缺口，不做泛泛评价。&lt;/p&gt;
&lt;p&gt;角色越清晰，AI 的输出越稳定。&lt;/p&gt;
&lt;h2&gt;14. 文档为什么排在高投入&lt;a href=&quot;#14-文档为什么排在高投入&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AI 时代，文档不是“写给人看的附属品”，而是“写给人和 AI 共同使用的控制面”。&lt;/p&gt;
&lt;p&gt;好的文档可以让 AI 更快进入项目上下文，减少错误假设。坏的文档会把 AI 带偏，而且偏得很自信。&lt;/p&gt;
&lt;p&gt;项目里至少应该有几类文档：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;产品需求文档：说明用户、目标、流程和非目标；&lt;/li&gt;
&lt;li&gt;技术方案文档：说明模块、接口、数据、状态和依赖；&lt;/li&gt;
&lt;li&gt;AI 规格文档：说明 prompt、tools、memory、eval、状态机和安全边界；&lt;/li&gt;
&lt;li&gt;联调文档：说明接口契约、错误码、字段和测试账号；&lt;/li&gt;
&lt;li&gt;测试文档：说明测试范围、命令、手工路径和已知风险；&lt;/li&gt;
&lt;li&gt;运维文档：说明环境变量、部署、监控、告警和回滚。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;文档不需要很长，但必须准确。尤其是给 AI 用的文档，要短、具体、结构化、可验证。&lt;/p&gt;
&lt;h2&gt;15. 测试会变得更重要&lt;a href=&quot;#15-测试会变得更重要&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AI 写代码越快，测试越重要。&lt;/p&gt;
&lt;p&gt;因为代码生成速度上来了，但理解速度、review 速度、联调速度没有同比提升。如果没有测试，团队会被大量“差一点正确”的代码淹没。&lt;/p&gt;
&lt;p&gt;Stack Overflow 2025 Developer Survey 里，开发者对 AI 的主要挫败感之一就是“AI 给出的方案几乎正确但不完全正确”，另一个高频问题是调试 AI 生成代码更耗时。这和实际体感非常一致。&lt;/p&gt;
&lt;p&gt;测试在 AI 开发里有两个作用。&lt;/p&gt;
&lt;p&gt;第一，它是验收工具。&lt;/p&gt;
&lt;p&gt;AI 说完成了不算，测试过了才接近完成。&lt;/p&gt;
&lt;p&gt;第二，它是约束工具。&lt;/p&gt;
&lt;p&gt;当你让 AI 修改代码时，已有测试会告诉它哪些行为不能破坏。没有测试的项目，AI 会更容易在无意中改变行为。&lt;/p&gt;
&lt;p&gt;最实用的策略不是一开始追求 100% 覆盖率，而是优先覆盖：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心用户路径；&lt;/li&gt;
&lt;li&gt;权限和认证；&lt;/li&gt;
&lt;li&gt;支付、订单、余额等资金相关逻辑；&lt;/li&gt;
&lt;li&gt;AI tool 调用和状态流转；&lt;/li&gt;
&lt;li&gt;数据写入和删除；&lt;/li&gt;
&lt;li&gt;bug 修复的复现用例；&lt;/li&gt;
&lt;li&gt;前后端接口契约；&lt;/li&gt;
&lt;li&gt;高风险边界条件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 生成代码的效率越高，测试就越像刹车和方向盘，而不是额外负担。&lt;/p&gt;
&lt;h2&gt;16. 前后端联调的核心问题&lt;a href=&quot;#16-前后端联调的核心问题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AI 可以很快生成前端，也可以很快生成后端，但它不一定能保证两边真的对上。&lt;/p&gt;
&lt;p&gt;联调最常见的问题包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;字段名不一致；&lt;/li&gt;
&lt;li&gt;枚举值不一致；&lt;/li&gt;
&lt;li&gt;错误码不一致；&lt;/li&gt;
&lt;li&gt;空状态没处理；&lt;/li&gt;
&lt;li&gt;分页协议不一致；&lt;/li&gt;
&lt;li&gt;时间格式不一致；&lt;/li&gt;
&lt;li&gt;权限失败时前端没有路径；&lt;/li&gt;
&lt;li&gt;后端返回 nullable，前端按必填处理；&lt;/li&gt;
&lt;li&gt;前端缓存导致状态不同步；&lt;/li&gt;
&lt;li&gt;AI 生成 mock 后忘记接真实接口。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以联调阶段要重点检查“契约”，不是只看代码。&lt;/p&gt;
&lt;p&gt;一个简单有效的联调 prompt：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;你是前后端联调工程师。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;请检查当前功能的前端调用和后端接口是否一致。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;重点检查：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; URL 和 method&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; request body / query&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; response 字段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; enum / status&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; error format&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; auth / permission&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; loading / empty / error states&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; mock 是否已替换为真实接口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;输出：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 不一致项&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 影响&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 推荐修复位置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 最小验证方式&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;联调不是最后才做的事。每个小闭环都应该尽早联调。&lt;/p&gt;
&lt;h2&gt;17. 工程阶段怎么约束 AI&lt;a href=&quot;#17-工程阶段怎么约束-ai&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;工程阶段要从“让 AI 发挥”切换成“让 AI 遵守”。&lt;/p&gt;
&lt;p&gt;你应该给它明确边界：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不准改无关文件；&lt;/li&gt;
&lt;li&gt;不准引入新依赖，除非说明理由；&lt;/li&gt;
&lt;li&gt;不准发明数据库字段；&lt;/li&gt;
&lt;li&gt;不准绕过现有权限；&lt;/li&gt;
&lt;li&gt;不准跳过测试；&lt;/li&gt;
&lt;li&gt;不准吞掉错误；&lt;/li&gt;
&lt;li&gt;不准把 mock 当真实实现；&lt;/li&gt;
&lt;li&gt;不准用大范围重构掩盖小问题；&lt;/li&gt;
&lt;li&gt;不准在没有确认的情况下执行危险命令；&lt;/li&gt;
&lt;li&gt;不准把敏感信息放进 prompt、日志或前端。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 不怕规则多，怕规则模糊。规则要短、硬、可检查。&lt;/p&gt;
&lt;h2&gt;最终结论&lt;a href=&quot;#最终结论&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;vibe coding 是一个机会，也是一种压力。&lt;/p&gt;
&lt;p&gt;它让一个人可以更快做出原型，让小团队可以做过去需要更多人才能做的事情，也让非专业开发者有机会把想法变成软件。&lt;/p&gt;
&lt;p&gt;但它不会自动带来高质量工程。AI 会放大人的能力，也会放大人的模糊、懒惰和侥幸。&lt;/p&gt;
&lt;p&gt;所以真正值得追求的不是“让 AI 多写代码”，而是建立一套人和 AI 协作的工程系统：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用原型发现需求；&lt;/li&gt;
&lt;li&gt;用文档收敛共识；&lt;/li&gt;
&lt;li&gt;用提示词表达任务；&lt;/li&gt;
&lt;li&gt;用 CLAUDE.md 固化公共规则；&lt;/li&gt;
&lt;li&gt;用 Skill 复用经验；&lt;/li&gt;
&lt;li&gt;用 SDD 约束复杂模块；&lt;/li&gt;
&lt;li&gt;用小闭环降低风险；&lt;/li&gt;
&lt;li&gt;用测试验证结果；&lt;/li&gt;
&lt;li&gt;用 review 保持质量；&lt;/li&gt;
&lt;li&gt;用 owner 意识承担责任。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;vibe coding 的终点，不是不会写代码的人随便做软件。&lt;/p&gt;
&lt;p&gt;它的终点应该是：会规划、会判断、会验收、会负责的人，借助 AI 更快地把正确的东西做出来。&lt;/p&gt;</content:encoded><category>category:Agent</category><category>category:Vibe coding</category><category>tag:python</category><category>tag:agent</category></item><item><title>前端流式输出实现：基于 Fetch API 的 SSE 实践</title><link>https://star-xp.top/post/frontend-streaming-sse</link><guid isPermaLink="false">frontend-streaming-sse</guid><description>深度解析什么是流式输出、为什么需要它，以及前端如何使用 Fetch API 配合 Streams API 实现健壮的 SSE 数据解析与业务逻辑分发。</description><pubDate>Sat, 03 Jan 2026 21:56:39 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;写在前面&lt;/strong&gt;：
流式输出就和它的名字一样，呈现一种水流形式一点一点进行输出。它是服务端对客户端的一种响应形式，这种输出使得响应数据可以一点点加载到页面上，避免用户等待很长时间，极大提高了用户体验。本文主要讲前端如何实现 SSE（Server-Sent Events）输出加载。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;流式输出的概念&lt;a href=&quot;#流式输出的概念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;什么是流式输出?&lt;a href=&quot;#什么是流式输出&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;流式输出就和它的名字一样，呈现一种水流形式一点一点进行输出。它是服务端对客户端的一种响应形式，这种输出使得响应数据可以一点点加载到页面上，避免用户等待很长时间，极大提高了用户体验。&lt;/p&gt;
&lt;p&gt;图中所示就是一种典型的流式输出（用户和聊天机器人聊天的网络请求响应）：
SSE 是单向输出，仅支持服务器向客户端发送消息。但在 AI 时代，用户通常需要向服务端传输复杂的 Body 信息数据（如 Prompt、上下文历史等），因此大多数现代流式输出方案都是基于 &lt;strong&gt;POST&lt;/strong&gt; 形式的 HTTP 请求实现的。&lt;/p&gt;
&lt;h3&gt;为什么要有流式输出?&lt;a href=&quot;#为什么要有流式输出&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;即时反馈&lt;/strong&gt;：早期的大语言模型生成内容时是逐个 Token 产生的，如果等待内容全部生成才显示，用户需要忍受漫长的空白等待。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;降低感知延迟&lt;/strong&gt;：流式输出可以解决这个问题，显著减少用户感知的延迟，极大提高了用户的响应感官速度。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;流式输出的实现&lt;a href=&quot;#流式输出的实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;核心实现方案：Fetch API&lt;a href=&quot;#核心实现方案fetch-api&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;对于这种不寻常的输出，前端应该怎么做? SSE 有它自己独特的响应接收形式，我们需要使用最底层的请求方式来实现它。我们通过 &lt;strong&gt;Fetch API&lt;/strong&gt; 来实现。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EventSource 的局限性&lt;/strong&gt;：原生 &lt;code&gt;EventSource&lt;/code&gt; 只支持 GET 请求，无法在 Body 中发送 JSON 数据，且不支持自定义 Headers（如 &lt;code&gt;Authorization&lt;/code&gt;），这在现代鉴权系统中是致命的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fetch 的优势&lt;/strong&gt;：Fetch 支持 POST，支持自定义 Headers，且通过 &lt;code&gt;response.body.getReader()&lt;/code&gt; 可以实现与 SSE 完全一致的流式读取。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;实现步骤详解&lt;a href=&quot;#实现步骤详解&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;第一步: 建立连接&lt;/strong&gt;
流式输出虽然是获取数据，但由于需要携带复杂的 Data，通常使用 &lt;strong&gt;POST&lt;/strong&gt; 方法并配置相应的请求头。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步: 使用 Streams API 实现流式读取&lt;/strong&gt;
传统的 API 是等待整个 Response 下载完成，但使用 Stream 可以实现：后端生成一个字，前端收到一个字。内存占用极低，且用户能立即看到反馈。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; reader&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; response.body.&lt;/span&gt;&lt;span&gt;getReader&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;done&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; reader.&lt;/span&gt;&lt;span&gt;read&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>category:Vue</category><category>category:前端</category><category>category:Agent</category><category>tag:SSE</category><category>tag:LLM</category></item><item><title>RAG</title><link>https://star-xp.top/post/rag-basic</link><guid isPermaLink="false">rag-basic</guid><description>程序员的 RAG 入门指南：让大模型拥有自己的知识库</description><pubDate>Sat, 03 Jan 2026 21:56:39 GMT</pubDate><content:encoded>&lt;p&gt;最近 AI 模型逐渐火热，作为一名程序员当然要追寻时代的潮流，学习了部分 AI 知识，其中 RAG 和 Agent 都比较出名。趁着这个时间，我来简单讲解一下 RAG，后续可能还会继续学习并分享这部分内容。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;什么是 RAG？&lt;/code&gt;&lt;a href=&quot;#什么是-rag&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;RAG 全称 Retrieval-Augmented Generation，也就是 &lt;strong&gt;&lt;code&gt;检索增强生成&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;简单来说，RAG 就是给大模型配了一个 &lt;strong&gt;&lt;code&gt;外挂知识库&lt;/code&gt;&lt;/strong&gt;。模型自己不知道的东西，先去知识库里面翻一翻，找到相关的资料，然后再基于这些资料来回答你的问题。&lt;/p&gt;
&lt;p&gt;我们都知道 LLM（大语言模型）是有局限的，它的底层是 Transformer 架构，本质上是一个”文字接龙”游戏 —— 根据前面的文字预测后面最可能出现的文字。这就导致一个问题：当模型遇到自己不懂的东西时，它不会说”我不知道”，而是会&lt;strong&gt;一本正经地胡说八道&lt;/strong&gt;，也就是所谓的”幻觉”。&lt;/p&gt;
&lt;p&gt;RAG 的诞生就很好地解决了这个问题。它的核心思路特别朴素：&lt;strong&gt;你不是容易编？那我先帮你把答案找好，你照着答案说就行了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;打个比方：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;没有 RAG 的大模型&lt;/strong&gt; = 闭卷考试，全靠记忆力，记不住就瞎编&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有 RAG 的大模型&lt;/strong&gt; = 开卷考试，可以翻书找答案，准确率大幅提升&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;RAG 可以解决什么问题？&lt;/code&gt;&lt;a href=&quot;#rag-可以解决什么问题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1. 幻觉问题&lt;a href=&quot;#1-幻觉问题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;大模型最大的痛点就是&lt;code&gt;幻觉&lt;/code&gt; —— 对用户的回答胡编乱造，而且编得特别像那么回事，让你真假难辨。&lt;/p&gt;
&lt;p&gt;比如你问它”某某公司 2024 年的营收是多少”，它可能会非常自信地给你一个数字，但这个数字完全是它编的。有了 RAG 之后，模型会先去知识库里找这家公司的财报数据，基于真实数据回答，准确性大幅提升。&lt;/p&gt;
&lt;h3&gt;2. 知识过时&lt;a href=&quot;#2-知识过时&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;模型训练完成之后，它所知道的东西都是当时训练时候接触到的信息，并&lt;code&gt;不会自动进化&lt;/code&gt;。比如一个 2023 年训练的模型，你问它 2024 年发生了什么，它是真的不知道。&lt;/p&gt;
&lt;p&gt;RAG 的&lt;code&gt;知识库&lt;/code&gt;可以随时更新。你只需要把最新的文档扔进知识库，模型就能”学到”最新的知识，不需要重新训练。&lt;/p&gt;
&lt;h3&gt;3. 没有私有数据&lt;a href=&quot;#3-没有私有数据&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是企业场景中最核心的需求。你公司的内部文档、技术手册、客户资料，这些东西不可能出现在大模型的训练数据中。通过知识库，我们可以搭建一个自己的&lt;code&gt;私人模型&lt;/code&gt;，给公司或者团队进行使用。&lt;/p&gt;
&lt;p&gt;举个例子：你把公司的员工手册放进知识库，新员工就可以直接问 AI”年假有多少天”、“报销流程是什么”，AI 会基于员工手册给出准确的回答。&lt;/p&gt;
&lt;h3&gt;4. 答案溯源&lt;a href=&quot;#4-答案溯源&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;对于一些严肃的问答场景（法律、医疗、金融），必须要能&lt;code&gt;追溯答案来源&lt;/code&gt;。RAG 可以在回答中标注”这段话来自哪个文档的哪一页”，让用户自己去核实。这样就避免了模型瞎编造成严重的后果。&lt;/p&gt;
&lt;p&gt;比如回答法律问题时引用具体的法条，回答医疗问题时引用临床指南，这些引用都是可以追溯验证的。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;RAG 和微调有什么区别？&lt;/code&gt;&lt;a href=&quot;#rag-和微调有什么区别&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;你可能会问：要让模型学会新知识，直接微调不行吗？为什么还要搞 RAG？&lt;/p&gt;
&lt;p&gt;这两个方案各有优劣，简单对比一下：&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;RAG（检索增强）&lt;/th&gt;&lt;th&gt;微调（Fine-tuning）&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;成本&lt;/td&gt;&lt;td&gt;低，只需要搭建检索系统&lt;/td&gt;&lt;td&gt;高，需要 GPU 训练&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;高，基于原文回答&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;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;数据在自己的库里，可控&lt;/td&gt;&lt;td&gt;数据需要参与训练&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;一句话总结：&lt;strong&gt;&lt;code&gt;RAG 是给模型&quot;查资料&quot;，微调是给模型&quot;补课&quot;&lt;/code&gt;&lt;/strong&gt;。大多数企业场景下，RAG 是更实用的选择。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;RAG 的核心概念&lt;/code&gt;&lt;a href=&quot;#rag-的核心概念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在讲工作原理之前，先搞清楚几个核心概念，不然后面可能看不懂。&lt;/p&gt;
&lt;h3&gt;Embedding（向量嵌入）&lt;a href=&quot;#embedding向量嵌入&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是 RAG 里面最重要的概念。&lt;/p&gt;
&lt;p&gt;我们人类理解文字靠语义，但计算机只认数字。Embedding 就是把一段文字变成一组数字（向量），而且&lt;strong&gt;意思相近的文字，变成的数字也相近&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“苹果手机” → [0.2, 0.8, 0.1, 0.5, …]&lt;/li&gt;
&lt;li&gt;“iPhone” → [0.21, 0.79, 0.12, 0.48, …]&lt;/li&gt;
&lt;li&gt;“今天天气不错” → [0.9, 0.1, 0.7, 0.3, …]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你会发现”苹果手机”和”iPhone”的向量非常接近，但跟”今天天气不错”差得很远。这就是 Embedding 的魔力 —— 它把语义相似性转化成了数学上的距离问题。&lt;/p&gt;
&lt;h3&gt;向量数据库&lt;a href=&quot;#向量数据库&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;有了向量，总得有地方存吧？向量数据库就是专门存储和检索向量的数据库。&lt;/p&gt;
&lt;p&gt;普通数据库的查询是”帮我找 name = ‘张三’ 的记录”，向量数据库的查询是”帮我找和这个向量最相似的 Top 10 个向量”。&lt;/p&gt;
&lt;h3&gt;相似度计算&lt;a href=&quot;#相似度计算&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;怎么衡量两个向量”像不像”？主要有两种方法：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;余弦相似度&lt;/code&gt;&lt;/strong&gt;：计算两个向量之间的夹角。夹角越小，越相似。值域是 -1 到 1，1 表示完全相同。这是最常用的方法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;欧氏距离&lt;/code&gt;&lt;/strong&gt;：计算两个向量之间的直线距离。距离越小，越相似。&lt;/p&gt;
&lt;p&gt;大多数 RAG 系统用的是余弦相似度。&lt;/p&gt;
&lt;h3&gt;分块（Chunking）&lt;a href=&quot;#分块chunking&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一篇文档可能有几万字，但用户的问题通常只对应其中一小段。如果把整篇文档丢给模型，既浪费 token，又可能干扰模型找到重点。&lt;/p&gt;
&lt;p&gt;所以我们需要把文档切成小块，每块存一个向量。检索的时候只返回最相关的几个小块，而不是整篇文档。&lt;/p&gt;
&lt;p&gt;切块看起来简单，实际上很有讲究：切太大，检索不精准；切太小，语义不完整。一般每块 100-500 个 token 比较合适。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;RAG 的工作原理&lt;/code&gt;&lt;a href=&quot;#rag-的工作原理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;RAG 的工作原理主要分为三步，我们一步一步来看。&lt;/p&gt;
&lt;h3&gt;第一步：处理文档（离线阶段）&lt;a href=&quot;#第一步处理文档离线阶段&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;上传文档 → 解析文档 → 文本分块 → 向量化 → 向量存储&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步是在用户提问之前就完成的，属于”备货”阶段。&lt;/p&gt;
&lt;h4&gt;上传文档&lt;a href=&quot;#上传文档&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;用户把文档（PDF、Word、Excel、Markdown 等）上传到系统。&lt;/p&gt;
&lt;h4&gt;解析文档&lt;a href=&quot;#解析文档&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;不同格式的文档，解析方式完全不同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PDF&lt;/code&gt;&lt;/strong&gt;：这是最复杂的。PDF 本质上是一种”画图指令”，它告诉渲染器在哪个坐标画什么字，并不像 Word 那样有明确的段落结构。所以 PDF 解析需要做&lt;strong&gt;版面分析&lt;/strong&gt; —— 用 AI 模型识别页面上哪些是标题、哪些是正文、哪些是表格、哪些是图片。对于扫描件类型的 PDF，还需要 OCR（光学字符识别）把图片中的文字识别出来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Word&lt;/code&gt;&lt;/strong&gt;：结构相对清晰，可以直接提取段落、标题、表格等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Excel&lt;/code&gt;&lt;/strong&gt;：按行或按表格提取，通常转成文字描述。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Markdown/TXT&lt;/code&gt;&lt;/strong&gt;：最简单，直接按段落分割。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解析质量直接决定了后续所有环节的上限。这个阶段如果把文字弄错了、把表格弄乱了，后面检索再准也没用。所以业界有一句话：&lt;strong&gt;RAG 的第一道坎是解析&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;文本分块&lt;a href=&quot;#文本分块&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;解析出来的文本需要切成小块。主要有三种分块策略：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;固定大小分块&lt;/code&gt;&lt;/strong&gt;：按固定的 token 数（比如每 128 个 token）切一刀。优点是简单，缺点是可能把一句话从中间切断。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;基于分隔符分块&lt;/code&gt;&lt;/strong&gt;：按句号、换行符、章节标题等自然边界切分。比固定大小更合理，但可能导致块大小不均匀。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;语义分块&lt;/code&gt;&lt;/strong&gt;：用 Embedding 模型计算相邻句子的语义相似度，在相似度突然下降的位置切分。理论上最优，但计算成本高。&lt;/p&gt;
&lt;p&gt;实际项目中，通常是把这些策略混合使用 —— 先按自然段落分割，再按 token 数合并或拆分，确保每个块既有完整语义又不会太长。&lt;/p&gt;
&lt;h4&gt;向量化&lt;a href=&quot;#向量化&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;把每个文本块送进 Embedding 模型，得到一个高维向量（比如 1024 维的浮点数数组）。&lt;/p&gt;
&lt;p&gt;这一步的关键是&lt;strong&gt;选对模型&lt;/strong&gt;。Embedding 模型的质量直接决定了”语义相似的内容能不能被检索到”。如果模型觉得”盈利增长”和”利润提升”不像，那检索就废了。&lt;/p&gt;
&lt;h4&gt;向量存储&lt;a href=&quot;#向量存储&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;把文本块的原文和对应的向量一起存入向量数据库（或带向量能力的搜索引擎）。每条记录大概长这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;id&quot;: &quot;chunk_001&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;content&quot;: &quot;公司2023年营收达到50亿元，同比增长15%...&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;vector&quot;: [0.12, 0.34, 0.56, ...],    // 1024维向量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;metadata&quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;source&quot;: &quot;年报.pdf&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;page&quot;: 5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到这里，“备货”就完成了。知识库已经准备好了，等待用户来提问。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;第二步：查询检索（在线阶段）&lt;a href=&quot;#第二步查询检索在线阶段&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;用户查询 → 查询向量化 → 相似度检索 → 结果重排 → 上下文组装&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;用户查询&lt;a href=&quot;#用户查询&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;用户输入一个问题，比如”公司去年的营收增长了多少？“&lt;/p&gt;
&lt;h4&gt;查询向量化&lt;a href=&quot;#查询向量化&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;把用户的问题用&lt;strong&gt;同一个&lt;/strong&gt; Embedding 模型转成向量。这里有一个关键点：查询和文档&lt;strong&gt;必须用同一个模型&lt;/strong&gt;，否则它们的向量在不同的空间里，无法比较相似度。&lt;/p&gt;
&lt;h4&gt;相似度检索&lt;a href=&quot;#相似度检索&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;拿着查询向量去向量数据库里找最相似的文本块。&lt;/p&gt;
&lt;p&gt;这一步有两种主流策略：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;纯向量检索&lt;/code&gt;&lt;/strong&gt;：只比较向量的余弦相似度。优点是能理解语义（“营收”和”收入”能匹配上），缺点是对具体的数字、人名等精确信息不敏感。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;混合检索（推荐）&lt;/code&gt;&lt;/strong&gt;：向量检索 + 关键词检索同时进行，然后把两者的结果融合。这样既能匹配语义，又能精确匹配关键词，两全其美。&lt;/p&gt;
&lt;p&gt;举个例子：用户问”2023 年 Q3 营收”&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;向量检索能匹配到”第三季度收入”（语义相似）&lt;/li&gt;
&lt;li&gt;关键词检索能精确匹配到含有”2023”和”Q3”的文本块&lt;/li&gt;
&lt;li&gt;两者结合，效果最好&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;结果重排（Rerank）&lt;a href=&quot;#结果重排rerank&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;检索返回的结果可能有几十条，但质量参差不齐。重排（Rerank）就是对这些初步结果做精细化排序，把最相关的排到前面。&lt;/p&gt;
&lt;p&gt;简单理解，检索是”海选”，重排是”决赛”。&lt;/p&gt;
&lt;p&gt;重排模型（Cross-Encoder）比普通的 Embedding 模型（Bi-Encoder）更精确，因为它会把查询和文档拼在一起一起看，能更好地理解两者之间的关系。但代价是速度慢，所以只能对少量候选做重排，不能对全量数据做。&lt;/p&gt;
&lt;p&gt;这就形成了经典的 &lt;strong&gt;&lt;code&gt;漏斗架构&lt;/code&gt;&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;全量文档 → 粗排（向量检索，快但粗）→ 精排（Rerank模型，慢但精）→ Top-K 结果&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;上下文组装&lt;a href=&quot;#上下文组装&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;把排序后的 Top-K 个文本块（通常 3-5 个）组装成”参考资料”，准备塞进提示词里。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;第三步：增强生成&lt;a href=&quot;#第三步增强生成&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;构建提示词 → 大语言模型 → 最终答案&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;构建提示词&lt;a href=&quot;#构建提示词&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;把用户的问题和检索到的参考资料按照一定的模板组装成提示词。一个典型的提示词长这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;你是一个专业的助手，请基于以下参考资料回答用户的问题。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;如果参考资料中没有相关信息，请诚实地说&quot;我不知道&quot;。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;参考资料：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[1] 公司2023年营收达到50亿元，同比增长15%...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[2] 第三季度单季营收18亿元，创历史新高...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[3] 公司预计2024年营收目标为60亿元...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;用户问题：公司去年的营收增长了多少？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;请基于以上参考资料回答。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示词设计有几个要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;明确角色&lt;/code&gt;&lt;/strong&gt;：告诉模型它是什么身份&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;给出规则&lt;/code&gt;&lt;/strong&gt;：特别是”不知道就说不知道”这条，防止幻觉&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;标注来源&lt;/code&gt;&lt;/strong&gt;：要求模型在回答中引用参考资料的编号，方便溯源&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;参考资料在前，问题在后&lt;/code&gt;&lt;/strong&gt;：这样模型先”看”完资料再思考问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;大语言模型生成&lt;a href=&quot;#大语言模型生成&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;提示词发送给 LLM，模型基于参考资料生成回答。由于参考资料是真实的文档内容，模型的回答会更加准确、更有依据。&lt;/p&gt;
&lt;h4&gt;最终答案&lt;a href=&quot;#最终答案&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;模型返回的回答可能长这样：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;根据参考资料，公司 2023 年营收达到 50 亿元，同比增长了 15% [1]。其中第三季度单季营收 18 亿元，创下历史新高 [2]。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;方括号里的数字就是引用标注，用户可以点击查看对应的原文，验证答案是否准确。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;RAG 常见的坑&lt;/code&gt;&lt;a href=&quot;#rag-常见的坑&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;坑 1：分块太大或太小&lt;a href=&quot;#坑-1分块太大或太小&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;分块太大（比如一整页），检索出来一大段文字，真正有用的可能就一两句，大量无关信息会干扰模型判断。分块太小（比如一两句话），语义不完整，模型理解不了上下文。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：从 128-256 个 token 开始尝试，根据实际效果调整。&lt;/p&gt;
&lt;h3&gt;坑 2：解析质量差&lt;a href=&quot;#坑-2解析质量差&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;特别是 PDF 文档。很多 PDF 看起来排版整齐，但内部结构混乱 —— 多栏排版、表格嵌套、扫描件等情况，简单的文本提取工具根本处理不好。如果解析出来的文本顺序是乱的、表格变成了一堆散乱的数字，后面再怎么优化也是白搭。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：选用专业的 PDF 解析方案（比如 DeepDoc、Unstructured），而不是简单的 PyPDF2。&lt;/p&gt;
&lt;h3&gt;坑 3：没有做重排序&lt;a href=&quot;#坑-3没有做重排序&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;只靠向量检索的初步结果就直接用，精度通常不够。加一个 Rerank 模型做精排，往往能显著提升效果，是性价比很高的优化手段。&lt;/p&gt;
&lt;h3&gt;坑 4：提示词设计不当&lt;a href=&quot;#坑-4提示词设计不当&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;最常见的错误是没有告诉模型”不知道就说不知道”。模型会默认尽力回答，即使参考资料里没有相关信息也会编造。明确的指令约束非常重要。&lt;/p&gt;
&lt;h3&gt;坑 5：Embedding 模型不匹配&lt;a href=&quot;#坑-5embedding-模型不匹配&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;用英文 Embedding 模型处理中文文档，或者查询和文档用了不同的 Embedding 模型，都会导致检索效果大幅下降。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：中文场景选中文或多语言模型，查询和文档必须用同一个模型。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;a href=&quot;#总结&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;RAG 的核心思想其实非常简单：&lt;strong&gt;先帮模型找到答案，再让模型组织语言&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它不是什么高深莫测的黑科技，而是一个非常工程化的解决方案。每一步（解析、分块、向量化、检索、重排、生成）都有成熟的工具和方案可以选用。&lt;/p&gt;
&lt;p&gt;对于程序员来说，RAG 是一个值得深入学习的方向。它不需要你有深度学习的理论基础（那是训练模型的人需要的），更多的是 &lt;strong&gt;&lt;code&gt;系统设计和工程优化&lt;/code&gt;&lt;/strong&gt; 能力 —— 怎么解析文档更准确、怎么分块更合理、怎么检索更精准、怎么提示词更有效。&lt;/p&gt;
&lt;p&gt;如果你想动手实践，推荐的技术栈：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;文档解析&lt;/code&gt;&lt;/strong&gt;：DeepDoc / Unstructured / LlamaParse&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Embedding 模型&lt;/code&gt;&lt;/strong&gt;：text-embedding-v3（阿里云） / BGE（开源）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;向量存储&lt;/code&gt;&lt;/strong&gt;：Elasticsearch / Milvus / Chroma&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;LLM&lt;/code&gt;&lt;/strong&gt;：DeepSeek / Qwen / GPT-4&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;框架&lt;/code&gt;&lt;/strong&gt;：LangChain / LlamaIndex / RAGFlow&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:Vue</category><category>category:前端</category><category>category:Agent</category><category>tag:RAG</category><category>tag:LLM</category></item><item><title>为什么我们不能直接双击打开 HTML？</title><link>https://star-xp.top/post/why-not-double-click-html</link><guid isPermaLink="false">why-not-double-click-html</guid><description>深度解析 file:// 与 http:// 协议的区别，探讨浏览器安全沙箱、绝对路径解析及 ES 模块化在本地环境下的限制。</description><pubDate>Sun, 30 Nov 2025 18:04:54 GMT</pubDate><content:encoded>&lt;h2&gt;现象描述&lt;a href=&quot;#现象描述&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;当我们刚开始学习 HTML 时，最直观的操作就是在文件夹中直接双击 &lt;code&gt;index.html&lt;/code&gt;。这时浏览器地址栏显示的是：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;file:///C:/Users/YourName/Project/index.html&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;虽然页面能打开，但经常遇到莫名其妙的 Bug：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;图片显示不出来&lt;/strong&gt;（尤其是使用了 &lt;code&gt;/&lt;/code&gt; 开头的绝对路径时）。&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;AJAX 请求直接报错&lt;/strong&gt;（CORS 错误）。&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;ES6 模块 (&lt;code&gt;import/export&lt;/code&gt;) 无法运行&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而当我们使用 VS Code 的 Live Server 插件，或者前端框架的 &lt;code&gt;npm run dev&lt;/code&gt; 后，浏览器自动弹出的地址是：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;http://127.0.0.1:5500/index.html&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;神奇的是，这时候一切都正常了。这背后的根本原因，在于 &lt;strong&gt;协议&lt;/strong&gt; 的不同。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;深度解析：file:// 协议 (本地文件模式)&lt;a href=&quot;#深度解析file-协议-本地文件模式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;当你双击文件时，浏览器使用的是 &lt;code&gt;file://&lt;/code&gt; 协议。在这个模式下，浏览器把你当作一个&lt;strong&gt;文件查看器&lt;/strong&gt;，而不是一个&lt;strong&gt;网页浏览器&lt;/strong&gt;。为了保护你的电脑安全，浏览器实施了极其严格的“沙箱限制”。&lt;/p&gt;
&lt;h3&gt;限制一：路径解析逻辑不同 (图片为何加载失败？)&lt;a href=&quot;#限制一路径解析逻辑不同-图片为何加载失败&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是新手最容易踩的坑。如果你的代码写的是绝对路径：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/logo.png&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;在 file:// 模式下：&lt;/strong&gt; 浏览器看到的“根”是你电脑的文件系统根目录（Windows 的 C 盘）。浏览器会尝试去访问：&lt;code&gt;file:///C:/logo.png&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果：&lt;/strong&gt; 你的 C 盘根目录下显然没有这张图，所以图片加载失败。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;限制二：安全策略不允许网络请求&lt;a href=&quot;#限制二安全策略不允许网络请求&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原因：&lt;/strong&gt; 浏览器的同源策略。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现象：&lt;/strong&gt; 浏览器禁止 JS 通过 &lt;code&gt;fetch&lt;/code&gt; 或 &lt;code&gt;XMLHttpRequest&lt;/code&gt; 去读取本地文件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;为什么：&lt;/strong&gt; 试想一下，如果随便下载一个网页保存到本地，双击打开后，它就能读取你硬盘里的 &lt;code&gt;C:/Passwords.txt&lt;/code&gt; 并发送给黑客，那太可怕了。所以浏览器直接禁用了这种能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;限制三：现代模块化无法使用&lt;a href=&quot;#限制三现代模块化无法使用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;如果你使用了 &lt;code&gt;&amp;lt;script type=&quot;module&quot;&amp;gt;&lt;/code&gt; (ES Modules)，在 &lt;code&gt;file://&lt;/code&gt; 下会直接报错。因为浏览器认为让一个本地文件去“加载”另一个本地脚本是不安全的。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;深度解析：http:// 协议 (Live Server 模式)&lt;a href=&quot;#深度解析http-协议-live-server-模式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;当你启动 Live Server 时，你实际上是在本地电脑上搭建了一个小型的 &lt;strong&gt;Web 服务器&lt;/strong&gt;。浏览器通过 &lt;code&gt;http://&lt;/code&gt; 协议与这个服务器通信，这才是标准的“网页浏览”环境。&lt;/p&gt;
&lt;h3&gt;优势一：正确的“根目录”映射&lt;a href=&quot;#优势一正确的根目录映射&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理：&lt;/strong&gt; Live Server 会把你当前的&lt;strong&gt;项目文件夹&lt;/strong&gt;，映射为服务器的&lt;strong&gt;网站根目录 (Web Root)&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果：&lt;/strong&gt; 当你请求 &lt;code&gt;&amp;lt;img src=&quot;/logo.png&quot;&amp;gt;&lt;/code&gt; 时，浏览器向服务器请求“网站根目录下的 logo.png”。服务器会去你的项目文件夹里找 &lt;code&gt;logo.png&lt;/code&gt; 并返回。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结论：&lt;/strong&gt; 图片成功加载。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;优势二：正常的网络权限&lt;a href=&quot;#优势二正常的网络权限&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;http://&lt;/code&gt; 环境下，浏览器认为这是一个正常的网站。只要遵守同源策略，你的 &lt;code&gt;fetch&lt;/code&gt; 请求、&lt;code&gt;import&lt;/code&gt; 模块化语法都可以正常工作。这完全模拟了代码上线后的真实运行环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;总结对比表&lt;a href=&quot;#总结对比表&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;特性&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;file:// (双击打开)&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;http:// (Live Server)&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;根目录 (/) 指向&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;电脑磁盘根目录 (如 C:)&lt;/td&gt;&lt;td&gt;你的项目文件夹&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;AJAX / Fetch&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;被浏览器拦截 (CORS)&lt;/td&gt;&lt;td&gt;正常工作&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;ES Modules&lt;/strong&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;strong&gt;适用场景&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;仅用于查看纯文本/简单 Demo&lt;/td&gt;&lt;td&gt;&lt;strong&gt;必须&lt;/strong&gt;用于正式开发&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;思考：为什么要有这种区分？&lt;a href=&quot;#思考为什么要有这种区分&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这并不是浏览器的 Bug，而是有意为之的安全特性。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;file://&lt;/code&gt; 协议的设计初衷是让你查看本地文件（比如打开一个 PDF，看一张本地照片），而不是让你运行一个复杂的 Web 应用。一旦涉及到资源引用、数据交互或模块化，我们就必须建立一个符合 Web 标准看齐的服务器环境（即 &lt;code&gt;http://&lt;/code&gt;），这不仅是为了功能正常，更是为了模拟真实的用户访问场景。&lt;/p&gt;</content:encoded><category>category:前端</category><category>category:HTML</category><category>tag:浏览器协议</category><category>tag:Web安全</category><category>tag:Live Server</category><category>tag:路径解析</category></item><item><title>Vue的响应式原理</title><link>https://star-xp.top/post/reactive</link><guid isPermaLink="false">reactive</guid><description>Vue 响应式原理深度解析：从 Vue2 到Vue3 。</description><pubDate>Wed, 05 Nov 2025 17:30:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;p&gt;想必大家在学习 Vue 的时候都会有这样的疑问：在学习原生 JavaScript 时，不论修改什么内容，通常只有在页面刷新时值才会更新。但在 Vue 项目中，数据变化却能实现页面的&lt;strong&gt;实时更新&lt;/strong&gt;。这就是 Vue 的核心特性——&lt;strong&gt;响应式数据更新&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Vue 2 响应式原理&lt;a href=&quot;#vue-2-响应式原理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;核心实现：Object.defineProperty()&lt;a href=&quot;#核心实现objectdefineproperty&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Vue 2 通过 &lt;code&gt;Object.defineProperty()&lt;/code&gt; 来实现响应式。当你创建一个 Vue 实例并将数据添加到 &lt;code&gt;data&lt;/code&gt; 选项中时，Vue 会遍历这些数据，并使用 &lt;code&gt;Object.defineProperty()&lt;/code&gt; 为每个属性添加 &lt;strong&gt;getter&lt;/strong&gt; 和 &lt;strong&gt;setter&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Getter&lt;/strong&gt;: 用于追踪依赖（依赖收集）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Setter&lt;/strong&gt;: 用于在属性被修改时通知变更。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;中枢神经：Watcher&lt;a href=&quot;#中枢神经watcher&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Watcher&lt;/code&gt; 是 Vue 2 响应式系统的核心，扮演着中枢神经的角色，承担三大核心职责：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;收集依赖&lt;/strong&gt;：记录哪些组件用到了这个数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;变更检测&lt;/strong&gt;：感知数据的变化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新调度&lt;/strong&gt;：通知视图进行重新渲染。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;局限性：无法检测数组和对象的新增&lt;a href=&quot;#局限性无法检测数组和对象的新增&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Vue 2 &lt;strong&gt;不能&lt;/strong&gt;检测到通过索引直接修改数组或动态添加对象属性的变化。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原因&lt;/strong&gt;：索引赋值不会触发 &lt;code&gt;setter&lt;/code&gt; 函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：Vue 2 重写了数组的 7 个方法（&lt;code&gt;push&lt;/code&gt;, &lt;code&gt;pop&lt;/code&gt;, &lt;code&gt;shift&lt;/code&gt;, &lt;code&gt;unshift&lt;/code&gt;, &lt;code&gt;splice&lt;/code&gt;, &lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;reverse&lt;/code&gt;）来手动触发响应式更新。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例代码对比&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;app&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt; v-for&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;(item, index) in list&quot;&lt;/span&gt;&lt;span&gt; :key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;index&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{{ item }}&amp;lt;/&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; @click&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;updateByIndex&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;直接索引修改（无效）&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; @click&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;updateBySplice&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;用 splice 修改（有效）&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  new&lt;/span&gt;&lt;span&gt; Vue&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    el: &lt;/span&gt;&lt;span&gt;&quot;#app&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    data: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      list: [&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    methods: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 直接使用索引赋值：页面不会更新&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      updateByIndex&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.list[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;数组已变但页面未更新：&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.list);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 使用 splice 方法：页面实时更新&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      updateBySplice&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.list.&lt;/span&gt;&lt;span&gt;splice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;页面同步更新：&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.list);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Vue 3 响应式原理&lt;a href=&quot;#vue-3-响应式原理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;核心实现：Proxy&lt;a href=&quot;#核心实现proxy&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Vue 3 改用 ES6 的 &lt;code&gt;Proxy&lt;/code&gt; 特性来实现响应式对象。&lt;code&gt;Proxy&lt;/code&gt; 可以直接代理整个对象，而不是像 &lt;code&gt;defineProperty&lt;/code&gt; 那样只能代理属性。&lt;/p&gt;
&lt;h3&gt;ref 与 reactive 的区别&lt;a href=&quot;#ref-与-reactive-的区别&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;reactive&lt;/code&gt;&lt;/strong&gt;: 只能用于数组和对象类型。它底层完全基于 &lt;code&gt;Proxy&lt;/code&gt; 实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ref&lt;/code&gt;&lt;/strong&gt;: 可以用于所有类型。&lt;/li&gt;
&lt;li&gt;如果封装的是&lt;strong&gt;简单类型&lt;/strong&gt;（如 String, Number），Vue 3 会回退使用 &lt;code&gt;getter/setter&lt;/code&gt;（&lt;code&gt;.value&lt;/code&gt; 访问），因为对简单类型使用 &lt;code&gt;Proxy&lt;/code&gt; 性能开销反而更大。&lt;/li&gt;
&lt;li&gt;如果封装的是&lt;strong&gt;复杂类型&lt;/strong&gt;，&lt;code&gt;ref&lt;/code&gt; 内部依然会调用 &lt;code&gt;reactive&lt;/code&gt;（即 &lt;code&gt;Proxy&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;核心机制：track 与 trigger&lt;a href=&quot;#核心机制track-与-trigger&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;track&lt;/code&gt;&lt;/strong&gt;: 依赖收集函数。在 &lt;code&gt;get&lt;/code&gt; 阶段运行，记录当前是哪个副作用函数（Effect）在访问数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;trigger&lt;/code&gt;&lt;/strong&gt;: 依赖触发函数。在 &lt;code&gt;set&lt;/code&gt; 阶段运行，当数据变化时，通知所有收集到的依赖重新执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Vue 3 简化版原理实现&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; targetMap&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WeakMap&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// 存储对象的所有依赖&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; activeEffect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 当前正在运行的副作用函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 依赖收集&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; track&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (activeEffect) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; depsMap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; targetMap.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(target);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;depsMap) targetMap.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(target, (depsMap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Map&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; dep &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; depsMap.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(key);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;dep) depsMap.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(key, (dep &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Set&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dep.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(activeEffect);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 触发更新&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; trigger&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; depsMap&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; targetMap.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(target);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (depsMap) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; dep&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; depsMap.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(key);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (dep) dep.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;effect&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; effect&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 创建响应式对象&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; reactive&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Proxy&lt;/span&gt;&lt;span&gt;(target, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;receiver&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      track&lt;/span&gt;&lt;span&gt;(target, key);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Reflect.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(target, key, receiver);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; typeof&lt;/span&gt;&lt;span&gt; res &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;object&quot;&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; res &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; reactive&lt;/span&gt;&lt;span&gt;(res) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; res;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;receiver&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; oldValue&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; target[key];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Reflect.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(target, key, value, receiver);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (oldValue &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; value) &lt;/span&gt;&lt;span&gt;trigger&lt;/span&gt;&lt;span&gt;(target, key);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; result;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Vue 2 vs Vue 3 核心区别汇总&lt;a href=&quot;#vue-2-vs-vue-3-核心区别汇总&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;特性&lt;/th&gt;&lt;th&gt;Vue 2&lt;/th&gt;&lt;th&gt;Vue 3&lt;/th&gt;&lt;th&gt;核心差异说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;实现原理&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;Object.defineProperty&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;Proxy&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Proxy 直接代理整个对象，无需递归初始化属性。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;数组响应式&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;需重写数组方法&lt;/td&gt;&lt;td&gt;原生支持&lt;/td&gt;&lt;td&gt;Vue 3 可直接通过索引 &lt;code&gt;arr[0]=1&lt;/code&gt; 或修改 &lt;code&gt;length&lt;/code&gt; 触发更新。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;动态新增属性&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;需用 &lt;code&gt;Vue.set()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;直接赋值生效&lt;/td&gt;&lt;td&gt;Vue 3 中 &lt;code&gt;obj.newProp = value&lt;/code&gt; 自动触发响应式。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;初始化性能&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;递归遍历所有属性&lt;/td&gt;&lt;td&gt;按需惰性代理&lt;/td&gt;&lt;td&gt;Vue 3 极大减少了大型对象的初始化开销。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;集合支持&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;不支持&lt;/td&gt;&lt;td&gt;支持 Map/Set 等&lt;/td&gt;&lt;td&gt;Vue 3 原生支持 &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;WeakMap&lt;/code&gt; 的响应式。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;API 灵活性&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;仅限于 &lt;code&gt;data&lt;/code&gt; 选项&lt;/td&gt;&lt;td&gt;&lt;code&gt;ref&lt;/code&gt; / &lt;code&gt;reactive&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Vue 3 的响应式 API 可脱离组件单独使用。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;调试能力&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;较弱&lt;/td&gt;&lt;td&gt;增强的调试钩子&lt;/td&gt;&lt;td&gt;提供 &lt;code&gt;onTrack&lt;/code&gt; 和 &lt;code&gt;onTrigger&lt;/code&gt; 钩子，方便追踪变化。&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;：Vue 3 通过 &lt;code&gt;Proxy&lt;/code&gt; 解决了 Vue 2 中最令人头疼的数组/对象新增属性不响应的问题，并通过惰性代理和精简的依赖追踪机制，带来了更强劲的性能表现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;</content:encoded><category>category:前端</category><category>category:Vue</category><category>tag:Vue2</category><category>tag:Vue3</category></item><item><title>JavaScript 垃圾回收机制</title><link>https://star-xp.top/post/gc</link><guid isPermaLink="false">gc</guid><description>JavaScript垃圾回收机制。</description><pubDate>Wed, 05 Nov 2025 10:44:20 GMT</pubDate><content:encoded>&lt;h2&gt;垃圾回收的概念&lt;a href=&quot;#垃圾回收的概念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;什么是垃圾回收机制&lt;a href=&quot;#什么是垃圾回收机制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;GC&lt;/code&gt; 即 &lt;code&gt;Garbage Collection&lt;/code&gt; ，程序工作过程中会产生很多”垃圾”，这些垃圾是程序不用的内存或者是之前用过了，以后不会再用的内存空间，而 &lt;code&gt;GC&lt;/code&gt; 就是负责回收垃圾的，因为他工作在引擎内部，所以对于我们前端来说，&lt;code&gt;GC&lt;/code&gt; 过程是相对比较无感的，这一套引擎执行而对我们又相对无感的操作也就是常说的 &lt;code&gt;垃圾回收机制&lt;/code&gt;
不是所有语言都有 &lt;code&gt;GC&lt;/code&gt;，一般的高级语言里面会自带 &lt;code&gt;GC&lt;/code&gt;，比如 &lt;code&gt;Java、Python、JavaScript&lt;/code&gt; 等，也有无 &lt;code&gt;GC&lt;/code&gt; 的语言，比如 &lt;code&gt;C、C++&lt;/code&gt; 等，那这种就需要我们程序员手动管理内存了，相对比较麻烦
在像 C/C++ 这样的语言中，开发者需要手动分配（&lt;code&gt;malloc&lt;/code&gt;）和释放（&lt;code&gt;free&lt;/code&gt;）内存。这种方式非常灵活，性能也高，但有两个致命缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;忘记释放&lt;/strong&gt;：会导致&lt;strong&gt;内存泄漏&lt;/strong&gt;，程序占用的内存会随着时间推移越来越多，最终可能导致程序崩溃或系统变慢。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提前释放&lt;/strong&gt;：或释放了多次，会导致&lt;strong&gt;悬挂指针&lt;/strong&gt;，当程序尝试访问一个已经被释放的内存地址时，会引发不可预知的错误（通常是程序崩溃）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JavaScript 中存在两种变量：&lt;code&gt;局部变量&lt;/code&gt;和&lt;code&gt;全局变量&lt;/code&gt;。全局变量的生命周期会持续到页面卸载；而局部变量声明在函数中，它的生命周期从函数执行开始，直到函数执行结束，在这个过程中，局部变量会在堆或栈中存储它们的值，当函数执行结束后，这些局部变量不再被使用，它们所占有的空间就会被释放。（不过，当局部变量被外部函数使用时，其中一种情况就是&lt;code&gt;闭包&lt;/code&gt;，在函数执行结束后，函数外部的变量依然指向函数内部的局部变量，此时局部变量依然在被使用，所以不会回收。）&lt;/p&gt;
&lt;h2&gt;垃圾回收的实现&lt;a href=&quot;#垃圾回收的实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;核心理念&lt;a href=&quot;#核心理念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;GC 机制的核心思想是&lt;code&gt;可达性&lt;/code&gt;。简单来说，就是判断一个对象是否“可达”，如果不可达，它就是“垃圾”。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可达的对象&lt;/strong&gt;：以某种方式被访问或使用的对象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不可达的对象&lt;/strong&gt;：无法被访问到的对象，可以被安全地回收。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GC 会有一系列的&lt;strong&gt;根&lt;/strong&gt;对象，它们是可达性的起点。在 JavaScript 中，主要的根包括：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;全局对象&lt;/code&gt;：比如浏览器环境下的 &lt;code&gt;window&lt;/code&gt; 对象，Node.js 环境下的 &lt;code&gt;global&lt;/code&gt; 对象。&lt;/strong&gt;
因为全局对象在应用程序的整个生命周期内都存在。只要你的网页开着，&lt;code&gt;window&lt;/code&gt; 对象就永远不会消失。它是所有全局变量和内置 API（如 &lt;code&gt;setTimeout&lt;/code&gt;, &lt;code&gt;localStorage&lt;/code&gt;）的宿主。如果它被回收了，整个 JavaScript 环境就都将崩塌。因此，&lt;strong&gt;全局对象是 GC 最重要、最基础的一个“根”&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;函数调用栈&lt;/code&gt;：当前正在执行的函数中的局部变量和参数。&lt;/strong&gt;
因为&lt;strong&gt;当前正在执行的代码和它所依赖的数据，理所当然是“存活”的&lt;/strong&gt;。调用栈代表了程序执行的“此时此刻”。如果这些正在使用的变量被回收了，程序将立即出错。因此，调用栈中所有栈帧里的变量和参数，都被视为临时的“根”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;活跃的 DOM 树&lt;/code&gt;：页面上存在的 DOM 元素。&lt;/strong&gt;
因为 DOM 节点是构成用户界面的实体。用户能看到、能与之交互的元素，必须始终存在于内存中，浏览器需要依据它来进行绘制和响应事件。因此，所有在 DOM 树上的节点都被认为是“可达的”。&lt;/p&gt;
&lt;p&gt;垃圾回收器会从这些&lt;code&gt;“根”&lt;/code&gt;出发，沿着引用链进行遍历。所有能从“根”访问到的对象，都会被认为是“活”的（可达的）；反之，所有无法从“根”访问到的对象，就会被认为是“死”的（不可达的），并成为垃圾回收的目标。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 创建一个根对象，并被变量 user 引用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; user &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name: &lt;/span&gt;&lt;span&gt;&quot;Alice&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 原来的 { name: &quot;Alice&quot; } 对象失去了引用，因此它变成了不可达对象，等待被回收。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;user &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;主流垃圾回收算法&lt;a href=&quot;#主流垃圾回收算法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;浏览器通常使用的垃圾回收方法有两种：&lt;code&gt;标记清除&lt;/code&gt;，&lt;code&gt;引用计数&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;标记清除&lt;/strong&gt;
这是现代浏览器中最常用的垃圾回收算法。它完美地解决了&lt;code&gt;循环引用&lt;/code&gt;的问题。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理&lt;/strong&gt;：分为两个阶段：
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;标记阶段&lt;/strong&gt;：垃圾回收器从&lt;code&gt;“根”对象&lt;/code&gt;开始，遍历所有可达的对象，并在这些对象上打上一个“标记”，表示它们是存活的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;清除阶段&lt;/strong&gt;：垃圾回收器遍历整个&lt;code&gt;堆内存&lt;/code&gt;，所有&lt;strong&gt;没有被标记&lt;/strong&gt;的对象都被视为垃圾，并被回收，其占用的内存被释放。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：可以解决循环引用的问题。因为即使 &lt;code&gt;objA&lt;/code&gt; 和 &lt;code&gt;objB&lt;/code&gt; 互相引用，但如果它们都无法从“根”访问到，那么它们就都不会被标记，最终会被一起清除。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; createCircularReference&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; objA &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; objB &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  objA.b &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; objB; &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  objB.a &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; objA; &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;createCircularReference&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;执行效率问题&lt;/strong&gt;：GC 执行时，需要&lt;code&gt;暂停整个程序&lt;/code&gt;的运行，如果堆内存很大，标记和清除会很耗时。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内存碎片化&lt;/strong&gt;：清除后，会产生大量不连续的内存碎片。如果之后需要分配一个大对象，可能会因为没有足够大的&lt;code&gt;连续空间&lt;/code&gt;而失败。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;引用计数&lt;/strong&gt;
这是早期的一种 GC 算法，思想非常简单。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理&lt;/strong&gt;：为每个对象维护一个&lt;code&gt;“引用计数器”&lt;/code&gt;。当有一个引用指向该对象时，计数器加 1；当引用被移除时，计数器减 1。当计数器变为 0 时，表示该对象不再被需要，可以被回收。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：实现简单，垃圾可以被&lt;code&gt;立即回收&lt;/code&gt;，不会有“暂停”的感觉。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;致命缺点&lt;/strong&gt;：无法处理&lt;code&gt;循环引用&lt;/code&gt;。
看下面的例子：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种情况下，就要手动释放变量占用的内存：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;obj1.a &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;  null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;obj2.a &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;  null&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;V8 引擎的优化：分代回收&lt;a href=&quot;#v8-引擎的优化分代回收&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为了解决标记-清除算法的效率问题，Google 的 V8 引擎（用于 Chrome 和 Node.js）采用了一种更先进的策略：&lt;code&gt;分代回收&lt;/code&gt;。
这个策略基于一个重要的观察：“大部分对象都是朝生夕死的”。也就是说，很多对象在创建后很快就不再被使用，而少数对象会存活很长时间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;V8 将堆内存分为两个主要区域：&lt;/strong&gt;
&lt;strong&gt;新生代：&lt;code&gt;Scavenge&lt;/code&gt; 算法&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;：存放生命周期短的对象，空间较小（通常为 1-8MB），垃圾回收频繁且&lt;code&gt;速度快&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内部结构&lt;/strong&gt;：新生代内存被平分为两个相等的空间：&lt;code&gt;From 空间（使用中）&lt;/code&gt; 和 &lt;code&gt;To 空间（空闲）&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回收过程&lt;/strong&gt;：
&lt;ol&gt;
&lt;li&gt;新对象首先被分配在 From 空间。&lt;/li&gt;
&lt;li&gt;当 From 空间快要被占满时，触发一次新生代的 GC。&lt;/li&gt;
&lt;li&gt;GC 会检查 From 空间中的存活对象，并将它们复制到 To 空间。&lt;/li&gt;
&lt;li&gt;复制完成后，From 空间剩下的所有对象都是垃圾。整个 From 空间被一次性清空。&lt;/li&gt;
&lt;li&gt;From 空间和 To 空间的角色互换，等待下一次 GC。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;晋升&lt;/strong&gt;：如果一个对象在新生代中经过了多次 Scavenge 依然存活，那么它被认为是生命周期较长的对象，会被“晋升”到老生代中。此外，如果复制一个对象到 To 空间时，To 空间的使用率超过了 25%，该对象也会被直接晋升到老生代。
&lt;strong&gt;老生代：标记-清除与 标记-整理&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;：存放生命周期长或体积大的对象，空间较大，GC&lt;code&gt;频率较低&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回收过程&lt;/strong&gt;：
&lt;ol&gt;
&lt;li&gt;主要使用&lt;strong&gt;标记-清除&lt;/strong&gt;算法，流程如前所述。&lt;/li&gt;
&lt;li&gt;为了解决内存碎片化问题，V8 引入了&lt;strong&gt;标记-整理&lt;/strong&gt;算法。它在标记阶段之后，不是直接清除垃圾，而是将所有存活的对象向内存的一端移动，然后直接清理掉边界之外的所有内存。这样就得到了连续的空闲空间。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;减少垃圾回收&lt;a href=&quot;#减少垃圾回收&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;手动处理&lt;a href=&quot;#手动处理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;虽然浏览器可以进行垃圾自动回收，但是当代码比较复杂时，垃圾回收所带来的代价比较大，所以应该尽量减少垃圾回收。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对&lt;code&gt;数组&lt;/code&gt;进行优化：&lt;/strong&gt; 在清空一个数组时，最简单的方法就是给其赋值为[ ]，但是与此同时会创建一个新的空对象，可以将数组的长度设置为 0，以此来达到清空数组的目的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对&lt;code&gt;对象&lt;/code&gt;进行优化：&lt;/strong&gt; 对象尽量复用，对于不再使用的对象，就将其设置为 null，尽快被回收。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;避免意外的全局变量&lt;/strong&gt;：
始终使用 &lt;code&gt;const&lt;/code&gt; 或 &lt;code&gt;let&lt;/code&gt; 声明变量，开启严格模式（&lt;code&gt;&apos;use strict&apos;;&lt;/code&gt;）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; leakyFunction&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 如果没有 &apos;let&apos; 或 &apos;const&apos;, a 会被创建为全局变量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 它将永远不会被回收，除非手动设为 null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  a &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; BigObject&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;警惕闭包&lt;/strong&gt;：
&lt;code&gt;闭包&lt;/code&gt;是 JavaScript 的强大特性，但也很容易造成内存泄漏。闭包可以使其父函数中的变量在函数执行结束后仍然存活。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; createClosure&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; largeData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Array&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1e6&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;*&apos;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 这个返回的函数持有了对 largeData 的引用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  	...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; largeData.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; myClosure &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; createClosure&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 即使 createClosure 执行完毕，largeData 也不会被回收，因为它被 myClosure 引用。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 如果不再需要它，应手动解除引用。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;myClosure &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;定时器和事件监听器&lt;/strong&gt;：
&lt;code&gt;setInterval&lt;/code&gt;, &lt;code&gt;setTimeout&lt;/code&gt; 和 &lt;code&gt;addEventListener&lt;/code&gt; 如果不被正确清理，它们的回调函数和其引用的外部变量都不会被回收。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; element &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;my-button&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; largeData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; BigObject&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // do something with largeData&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;element.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;click&apos;&lt;/span&gt;&lt;span&gt;, onClick);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 正确做法：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// element.removeEventListener(&apos;click&apos;, onClick);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// element = null;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在组件销毁或元素移除时，务必使用 &lt;code&gt;clearInterval&lt;/code&gt;, &lt;code&gt;clearTimeout&lt;/code&gt; 和 &lt;code&gt;removeEventListener&lt;/code&gt; 清理掉相关的定时器和监听器。&lt;/p&gt;</content:encoded><category>category:前端</category><category>category:JavaScript</category><category>tag:JavaScript</category><category>tag:ES6</category></item><item><title>JavaScript 继承详讲</title><link>https://star-xp.top/post/extends</link><guid isPermaLink="false">extends</guid><description>JavaScript继承详细讲解, 开启你的继承之旅。</description><pubDate>Wed, 05 Nov 2025 10:44:20 GMT</pubDate><content:encoded>&lt;p&gt;在 JavaScript 中，&lt;code&gt;继承&lt;/code&gt;是非常核心重要的，今天我要和大家分享一下 JavaScript 中的继承都有那些，以及对应的一些优缺点。&lt;/p&gt;
&lt;h2&gt;原型链继承&lt;a href=&quot;#原型链继承&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;原型链继承是 JavaScript 中实现继承的一种基本方式， 它利用原型机制让一个对象能够访问另一个对象的属性和方法。&lt;/p&gt;
&lt;p&gt;原型链継承的&lt;code&gt;核心思想&lt;/code&gt;是： 将父类的实例作为子类的原型对象，这样，子类实例就可以通过原型链访问父类的属性和方法&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;Animal&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.colors &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;red&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;blue&apos;&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Animal&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sayName&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;My name is &apos;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.type &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;cat&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Cat;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;meow&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Meow!&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.&lt;/span&gt;&lt;span&gt;sayName&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.&lt;/span&gt;&lt;span&gt;meow&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么&lt;code&gt;constructor&lt;/code&gt;要进行修复：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat.&lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; Animal); &lt;/span&gt;&lt;span&gt;// true - 错误！&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat.&lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; Cat); &lt;/span&gt;&lt;span&gt;// false - 不正确&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;原型链结构分析&lt;a href=&quot;#原型链结构分析&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;cat1 (Cat 实例)
├── 自身属性: type = ‘cat’
└── &lt;strong&gt;proto&lt;/strong&gt; → Cat.prototype (Animal 实例)
├── constructor: Cat
├── meow: function
└── &lt;strong&gt;proto&lt;/strong&gt; → Animal.prototype
├── constructor: Animal
├── sayName: function
└── &lt;strong&gt;proto&lt;/strong&gt; → Object.prototype&lt;/p&gt;
&lt;h3&gt;原型链继承的特点&lt;a href=&quot;#原型链继承的特点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&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;ol&gt;
&lt;li&gt;引用类型属性共享问题&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.colors.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;green&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat2.colors); &lt;/span&gt;&lt;span&gt;// [&apos;red&apos;, &apos;blue&apos;, &apos;green&apos;] - 被影响了！&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;无法向父类构造函数传参&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 无法在创建Cat实例时给Animal传参&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Tom&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 这里的参数无法传递给Animal&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;无法实现多继承&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Child&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Parent1&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 无法同时设置：Child.prototype = new Parent2();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验证继承关系的方法 :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; Object&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isPrototypeOf&lt;/span&gt;&lt;span&gt;(cat)); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Animal&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isPrototypeOf&lt;/span&gt;&lt;span&gt;(cat)); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;构造函数继承&lt;a href=&quot;#构造函数继承&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;构造函数继承（Constructor Inheritance）是 JavaScript 中实现继承的重要方式之一，它通过&lt;strong&gt;在子类构造函数中调用父类构造函数&lt;/strong&gt;来实现继承。&lt;/p&gt;
&lt;p&gt;在子类构造函数中，使用&lt;code&gt;call()&lt;/code&gt;或&lt;code&gt;apply()&lt;/code&gt;方法调用父类构造函数，将父类的属性和方法绑定到子类实例上：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Child&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Parent.&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 关键步骤&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 子类自己的属性初始化&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;示例&lt;a href=&quot;#示例&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &quot;Animal&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.colors &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;red&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;blue&quot;&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sayName&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;My name is &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 调用父类构造函数，继承属性&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Animal.&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.type &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;cat&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;meow&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Meow!&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Tom&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.&lt;/span&gt;&lt;span&gt;sayName&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.&lt;/span&gt;&lt;span&gt;meow&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;构造函数继承的特点&lt;a href=&quot;#构造函数继承的特点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&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;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Tom&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Jerry&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.colors.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;green&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.colors); &lt;/span&gt;&lt;span&gt;// [&apos;red&apos;, &apos;blue&apos;, &apos;green&apos;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat2.colors); &lt;/span&gt;&lt;span&gt;// [&apos;red&apos;, &apos;blue&apos;] - 互不影响&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;支持向父类传递参数&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Animal.&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, name); &lt;/span&gt;&lt;span&gt;// 传递name参数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;可实现多继承&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; FlyingAnimal&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.canFly &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fly&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Flying!&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Animal.&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  FlyingAnimal.&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 多继承&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 将方法定义在原型上&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Animal&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sayName&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;My name is &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Tom&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat.&lt;/span&gt;&lt;span&gt;sayName&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// TypeError: cat.sayName is not a function&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;方法无法复用&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每个实例都会创建自己的方法副本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.sayName &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; cat2.sayName); &lt;/span&gt;&lt;span&gt;// false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;无法使用 instanceof 检查继承关系&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1 &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;组合继承&lt;a href=&quot;#组合继承&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;组合继承（Combination Inheritance）是 JavaScript 中最常用的继承模式，它结合了&lt;strong&gt;原型链继承&lt;/strong&gt;和&lt;strong&gt;构造函数继承&lt;/strong&gt;的优点，同时规避了它们的缺点。&lt;/p&gt;
&lt;h3&gt;核心概念&lt;a href=&quot;#核心概念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;组合继承的核心思想是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;使用构造函数继承&lt;/strong&gt;来继承父类的实例属性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用原型链继承&lt;/strong&gt;来继承父类的原型方法&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;实现步骤&lt;a href=&quot;#实现步骤&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &quot;Animal&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.colors &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;red&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;blue&quot;&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Animal&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sayName&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;My name is &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;age&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Animal.&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.age &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; age &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.type &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;cat&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 7. 修复constructor指向&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Cat;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;meow&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Meow! I am &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.age &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot; years old.&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;组合继承的优势&lt;a href=&quot;#组合继承的优势&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;解决引用类型共享问题&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Tom&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Jerry&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.colors.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;green&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.colors); &lt;/span&gt;&lt;span&gt;// [&apos;red&apos;, &apos;blue&apos;, &apos;green&apos;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat2.colors); &lt;/span&gt;&lt;span&gt;// [&apos;red&apos;, &apos;blue&apos;] - 互不影响&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;支持向父类传递参数&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Garfield&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat.&lt;/span&gt;&lt;span&gt;sayName&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// &quot;My name is Garfield&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;方法复用&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.sayName &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; cat2.sayName); &lt;/span&gt;&lt;span&gt;// true (共享原型方法)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;正确的继承关系检查&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1 &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1 &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.&lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; Cat); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;组合继承的缺点&lt;a href=&quot;#组合继承的缺点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;父类构造函数被调用两次&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Animal constructor called&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Animal.&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, name); &lt;/span&gt;&lt;span&gt;// 第一次调用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// 第二次调用&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;原型对象上存在冗余属性&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Tom&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat); &lt;/span&gt;&lt;span&gt;// 实例自身有name和colors&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(Object.&lt;/span&gt;&lt;span&gt;getPrototypeOf&lt;/span&gt;&lt;span&gt;(cat)); &lt;/span&gt;&lt;span&gt;// 原型上也有name和colors&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;原型式继承&lt;a href=&quot;#原型式继承&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;原型式继承（Prototypal Inheritance）是一种不涉及构造函数的继承方式，它直接基于现有对象创建新对象，是 JavaScript 中最纯粹的面向对象继承方式。&lt;/p&gt;
&lt;p&gt;原型式继承不关注构造函数，而是关注对象之间的关系。它的核心是：&lt;strong&gt;创建一个新对象，并将一个已有的对象作为这个新对象的原型&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;示例&lt;a href=&quot;#示例-1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; animalPrototype&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  isAlive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  colors: [&lt;/span&gt;&lt;span&gt;&quot;black&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;white&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  sayHello&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`Hello, I have ${&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;colors&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot; and &quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;} fur.`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Object.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(animalPrototype);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Tom&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.age &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.&lt;/span&gt;&lt;span&gt;sayHello&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.isAlive);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.name);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;原型式继承的特点&lt;a href=&quot;#原型式继承的特点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&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;：它完美地体现了 JavaScript 的原型思想——任何对象都可以是另一个对象的原型，而无需“类”作为中介。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语法简单直接&lt;/strong&gt;：一行 &lt;code&gt;Object.create()&lt;/code&gt; 即可建立继承关系，非常清晰。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可以继承普通对象&lt;/strong&gt;：继承的来源可以是一个简单的字面量对象，而不仅限于构造函数的实例。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;引用类型属性共享问题&lt;/strong&gt;：这是它与“原型链继承”共有的致命弱点。所有继承自同一个原型的实例，都会共享原型上的引用类型属性。如果一个实例修改了该属性，会影响到所有其他实例。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Object.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(animalPrototype);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.colors.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;yellow&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// cat2 的 colors 属性也受到了影响&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat2.colors); &lt;/span&gt;&lt;span&gt;// 输出: [&apos;black&apos;, &apos;white&apos;, &apos;yellow&apos;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;属性初始化复杂&lt;/strong&gt;：由于没有构造函数来统一处理初始化，每创建一个新对象后，都需要手动为其添加新的实例属性（如 &lt;code&gt;cat1.name = &apos;Tom&apos;&lt;/code&gt;），如果属性多，会比较繁琐。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;寄生式继承&lt;a href=&quot;#寄生式继承&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;寄生式继承（Parasitic Inheritance）是 JavaScript 中一种特殊的继承模式，它基于原型式继承，通过”寄生”的方式增强对象的功能。&lt;/p&gt;
&lt;h3&gt;核心概念&lt;a href=&quot;#核心概念-1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;寄生式继承的核心思想是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基于现有对象创建新对象&lt;/strong&gt;（使用原型式继承）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;增强新对象的功能&lt;/strong&gt;（添加新属性和方法）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;返回增强后的对象&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;基本实现&lt;a href=&quot;#基本实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; createEnhancedObject&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;original&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; clone&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Object.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(original);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  clone.&lt;/span&gt;&lt;span&gt;sayHello&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Hello, I am &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; clone;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 使用示例&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; person&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name: &lt;/span&gt;&lt;span&gt;&quot;John&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  age: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; enhancedPerson&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; createEnhancedObject&lt;/span&gt;&lt;span&gt;(person);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;enhancedPerson.&lt;/span&gt;&lt;span&gt;sayHello&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(enhancedPerson.age);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;寄生式继承的优缺点&lt;a href=&quot;#寄生式继承的优缺点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;简单灵活&lt;/strong&gt;：不需要定义构造函数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;功能增强&lt;/strong&gt;：可以自由添加新功能&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;对象定制&lt;/strong&gt;：每个对象可以有独特的增强&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;兼容性好&lt;/strong&gt;：适用于各种 JavaScript 环境
&lt;strong&gt;缺点&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;方法无法复用&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; obj1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; createEnhancedObject&lt;/span&gt;&lt;span&gt;({});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; obj2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; createEnhancedObject&lt;/span&gt;&lt;span&gt;({});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(obj1.sayHello &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; obj2.sayHello); &lt;/span&gt;&lt;span&gt;// false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;引用类型共享问题&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; original&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { list: [&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;] };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; obj1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; createEnhancedObject&lt;/span&gt;&lt;span&gt;(original);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;obj1.list.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; obj2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; createEnhancedObject&lt;/span&gt;&lt;span&gt;(original);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(obj2.list); &lt;/span&gt;&lt;span&gt;// [1, 2]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;无法使用 instanceof 检查类型&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(enhancedPerson &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; Object&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 但无法检查是否是特定&quot;类型&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;寄生组合式继承&lt;a href=&quot;#寄生组合式继承&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;寄生组合式继承（Parasitic Combination Inheritance）是 JavaScript 中最完善的继承模式，它结合了&lt;strong&gt;构造函数继承&lt;/strong&gt;和&lt;strong&gt;原型链继承&lt;/strong&gt;的优点，同时完美规避了它们的缺点。&lt;/p&gt;
&lt;h3&gt;核心概念&lt;a href=&quot;#核心概念-2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;寄生组合式继承的核心思想是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;使用构造函数继承&lt;/strong&gt;来继承父类的实例属性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用寄生式继承&lt;/strong&gt;来继承父类的原型方法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免调用父类构造函数两次&lt;/strong&gt;（解决组合继承的主要缺点）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;示例&lt;a href=&quot;#示例-2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &quot;Animal&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.colors &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;red&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;blue&quot;&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Animal&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sayName&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;My name is &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;age&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Animal.&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.age &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; age &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.type &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;cat&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Cat;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;meow&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Meow! I am &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.age &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot; years old.&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;寄生组合式继承的优势&lt;a href=&quot;#寄生组合式继承的优势&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;解决引用类型共享问题&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Tom&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Jerry&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.colors.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;green&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.colors); &lt;/span&gt;&lt;span&gt;// [&apos;red&apos;, &apos;blue&apos;, &apos;green&apos;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat2.colors); &lt;/span&gt;&lt;span&gt;// [&apos;red&apos;, &apos;blue&apos;] - 互不影响&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;支持向父类传递参数&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Garfield&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat.&lt;/span&gt;&lt;span&gt;sayName&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// &quot;My name is Garfield&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;方法复用&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.sayName &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; cat2.sayName); &lt;/span&gt;&lt;span&gt;// true (共享原型方法)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;正确的继承关系检查&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1 &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1 &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.&lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; Cat); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;寄生组合式继承的缺点&lt;a href=&quot;#寄生组合式继承的缺点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;父类构造函数被调用两次&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Animal constructor called&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Animal.&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, name); &lt;/span&gt;&lt;span&gt;// 第一次调用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// 第二次调用&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;原型对象上存在冗余属性&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Tom&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat); &lt;/span&gt;&lt;span&gt;// 实例自身有name和colors&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(Object.&lt;/span&gt;&lt;span&gt;getPrototypeOf&lt;/span&gt;&lt;span&gt;(cat)); &lt;/span&gt;&lt;span&gt;// 原型上也有name和colors&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Class 继承&lt;a href=&quot;#class-继承&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;class&lt;/code&gt; 语法是 JavaScript 现有原型继承模型的“语法糖”（Syntactic Sugar）。它并没有引入新的继承机制，而是提供了一套更清晰、更简洁的语法来操作原型和构造函数。
ES6 Class 继承的核心思想是：使用 &lt;code&gt;extends&lt;/code&gt; 关键字来实现类之间的继承关系。它极大地简化了继承的写法，并解决了传统原型链继承的一些痛点。&lt;/p&gt;
&lt;h3&gt;示例&lt;a href=&quot;#示例-3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &quot;Animal&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.colors &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;red&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;blue&quot;&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  sayName&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;My name is &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; Animal&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    super&lt;/span&gt;&lt;span&gt;(name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.type &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; type &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &quot;cat&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  meow&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Meow!&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Tom&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;橘猫&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.&lt;/span&gt;&lt;span&gt;sayName&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.&lt;/span&gt;&lt;span&gt;meow&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.type);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Class 继承的特点&lt;a href=&quot;#class-继承的特点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;语法清晰，符合直觉&lt;/strong&gt;：&lt;code&gt;class&lt;/code&gt; 和 &lt;code&gt;extends&lt;/code&gt; 的写法让代码更易于阅读和理解，对有其他面向对象语言（如 Java、C++、Python）背景的开发者非常友好。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;解决了原型链继承的核心痛点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;完美解决引用属性共享问题&lt;/strong&gt;：父类的实例属性（如 &lt;code&gt;this.colors&lt;/code&gt;）是在子类 &lt;code&gt;constructor&lt;/code&gt; 中通过 &lt;code&gt;super()&lt;/code&gt; 调用时才创建的，每个子类实例都有自己的一份，互不影响。
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Tom&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cat2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Cat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Jerry&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat1.colors.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;green&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat1.colors); &lt;/span&gt;&lt;span&gt;// [&apos;red&apos;, &apos;blue&apos;, &apos;green&apos;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cat2.colors); &lt;/span&gt;&lt;span&gt;// [&apos;red&apos;, &apos;blue&apos;] - 未受影响！&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;内置 &lt;code&gt;super&lt;/code&gt; 关键字&lt;/strong&gt;：&lt;code&gt;super&lt;/code&gt; 提供了简洁的方式来调用父类的构造函数和方法，代码更健壮。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;类内部默认使用严格模式&lt;/strong&gt;：&lt;code&gt;class&lt;/code&gt; 和模块的内部，默认就是严格模式 (&lt;code&gt;&apos;use strict&apos;&lt;/code&gt;)，所以不需要再显式声明，有助于编写更规范、更安全的代码。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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;/li&gt;
&lt;li&gt;&lt;strong&gt;兼容性问题&lt;/strong&gt;：&lt;code&gt;class&lt;/code&gt; 语法是 ES6 (2015 年) 的标准，在一些非常老旧的浏览器（如 IE11）上不支持。但在现代前端开发中，通常会使用 Babel 等工具将其转换为 ES5 兼容的代码，所以这已经不是一个主要障碍。&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>category:前端</category><category>category:JavaScript</category><category>tag:JavaScript</category><category>tag:ES6</category><category>tag:继承</category></item><item><title>JavaScript 字符串详解</title><link>https://star-xp.top/post/string</link><guid isPermaLink="false">string</guid><description>JavaScript字符串详解。</description><pubDate>Wed, 05 Nov 2025 10:44:20 GMT</pubDate><content:encoded>&lt;h2&gt;创建字符串&lt;a href=&quot;#创建字符串&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;1.使用字面量(推荐)&lt;/code&gt;：
这是最常用、最直接的方式。你可以用单引号 (&lt;code&gt;&apos;&lt;/code&gt;)、双引号 (&lt;code&gt;&quot;&lt;/code&gt;) 或反引号 (&lt;code&gt;`&lt;/code&gt;) 把文本包起来&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; singleQuote &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;单引号&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; doubleQuote &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;双引号&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; templateLiteral &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; `反引号`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;2.使用String 构造函数（不推荐）&lt;/code&gt;
这种方式会创建一个字符串对象 (Object)，而不是一个基本类型的值，可能会在比较时产生意想不到的结果。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; strObject &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;这是一个字符串对象&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; strObject); &lt;/span&gt;&lt;span&gt;// &quot;object&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; strLiteral &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;这是一个字符串字面量&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; strLiteral); &lt;/span&gt;&lt;span&gt;// &quot;string&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;字符串的不可变性&lt;a href=&quot;#字符串的不可变性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一旦一个字符串被创建，它的内容就永远无法被改变。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; greeting &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Hello&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;greeting &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; greeting &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot;, World!&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 拼接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(greeting); &lt;/span&gt;&lt;span&gt;// &quot;Hello, World!&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表面上看，&lt;code&gt;greeting&lt;/code&gt; 变量的值被改变了。但实际上，内存中发生的事情是：&lt;code&gt;greeting + &quot;, World!&quot;&lt;/code&gt; 创建了一个&lt;strong&gt;全新的字符串&lt;/strong&gt; &lt;code&gt;&quot;Hello, World!&quot;&lt;/code&gt;。然后 &lt;code&gt;greeting&lt;/code&gt; 变量的“指针”从旧的 &lt;code&gt;&quot;Hello&quot;&lt;/code&gt; 指向了这个&lt;strong&gt;新的&lt;/strong&gt;字符串。&lt;/p&gt;
&lt;h3&gt;字符串的属性和方法&lt;a href=&quot;#字符串的属性和方法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4&gt;charAt(index)&lt;a href=&quot;#charatindex&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 返回字符串中指定索引位置的单个字符。&lt;/li&gt;
&lt;li&gt;参数：&lt;code&gt;index&lt;/code&gt; (必需): 一个整数，表示你想要获取的字符的索引 (从 0 开始)。&lt;/li&gt;
&lt;li&gt;返回值： 一个只包含单个字符的新字符串。如果 index 超出范围，返回一个空字符串 &quot;&quot;。&lt;/li&gt;
&lt;li&gt;无法正确处理超出基本多文种平面 (BMP) 的 &lt;code&gt;Unicode&lt;/code&gt; 字符,因为 JavaScript 内部使用 &lt;code&gt;UTF-16&lt;/code&gt; 编码来表示字符串,在需要处理可能包含 &lt;code&gt;emoji&lt;/code&gt; 或其他特殊字符的字符串时，应避免使用 charAt()。推荐使用 ES6 提供的展开运算符 (…)、Array.from() 或 for…of 循环，它们能够正确识别并处理完整的 &lt;code&gt;Unicode&lt;/code&gt; 字符。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; str &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;JavaScript&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 获取第一个字符&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(str.&lt;/span&gt;&lt;span&gt;charAt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;J&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 获取第五个字符&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(str.&lt;/span&gt;&lt;span&gt;charAt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;S&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 如今更常用方括号 str[0] 的写法，但它在索引越界时返回 undefined。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; emojiStr &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Hello&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 这个字符串的 length 并不是 6，而是 7 因为 &quot;&quot; 这个 emoji 占了两个码元&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(emojiStr.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 7&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;slice(startIndex, endIndex)&lt;a href=&quot;#slicestartindex-endindex&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 提取字符串的一个片段，并以新字符串的形式返回它，原始字符串不会被修改。&lt;/li&gt;
&lt;li&gt;参数： &lt;code&gt;startIndex&lt;/code&gt; (必需): 开始提取的索引。如果为负数，则从字符串尾部开始计算。&lt;code&gt;endIndex&lt;/code&gt; (可选): 结束提取的索引 (该索引处的字符不被包含)。如果省略，则提取到字符串末尾。如果为负数，也从尾部计算。&lt;/li&gt;
&lt;li&gt;返回值： 包含提取部分的新字符串。&lt;/li&gt;
&lt;li&gt;开始索引 &lt;code&gt;startIndex&lt;/code&gt; 大于或等于结束索引 &lt;code&gt;endIndex&lt;/code&gt;，slice() 方法将始终返回一个空字符串 &quot;&quot;。它不会像 substring() 那样自动交换参数。如果传递给 slice() 的参数不是数字，JavaScript 会尝试将它们隐式转换为数字。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;The quick brown fox&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 提取 &quot;quick&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(text.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;quick&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 从索引 10 到末尾&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(text.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;brown fox&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 提取最后 3 个字符&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(text.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;fox&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;0123456789&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// null 被转换为 0, true 被转换为 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(data.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;&quot;空的&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(data.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;0&quot; (相当于 slice(0, 1))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;substring(startIndex, endIndex)&lt;a href=&quot;#substringstartindex-endindex&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 与 slice() 类似，提取字符串的片段并返回。&lt;/li&gt;
&lt;li&gt;参数：&lt;code&gt;startIndex&lt;/code&gt; (必需): 开始提取的索引。&lt;code&gt;endIndex&lt;/code&gt; (可选): 结束提取的索引 (不包含)。&lt;/li&gt;
&lt;li&gt;返回值： 包含提取部分的新字符串。&lt;/li&gt;
&lt;li&gt;如果传递给的参数不是数字，JavaScript 会尝试将它们隐式转换为数字。&lt;/li&gt;
&lt;li&gt;与 slice() 的核心区别：&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;不支持负数索引：任何负数参数都会被视为 0。&lt;/li&gt;
&lt;li&gt;参数位置可互换：如果 &lt;code&gt;startIndex&lt;/code&gt; 大于 &lt;code&gt;endIndex&lt;/code&gt;，方法会自动交换这两个参数。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; str &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Mozilla&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(str.&lt;/span&gt;&lt;span&gt;substring&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;ozi&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 自动交换参数 4 和 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(str.&lt;/span&gt;&lt;span&gt;substring&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;ozi&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 负数被视为 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(str.&lt;/span&gt;&lt;span&gt;substring&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;Mozi&quot; (相当于 substring(0, 4))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;includes(searchValue, position)&lt;a href=&quot;#includessearchvalue-position&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 判断一个字符串是否包含另一个指定的子字符串。&lt;/li&gt;
&lt;li&gt;参数: &lt;code&gt;searchValue&lt;/code&gt; (必需): 要查找的子字符串。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;position&lt;/code&gt; (可选): 开始搜索的索引位置，默认为 0, 如果 &lt;code&gt;position&lt;/code&gt; 是负数或 NaN，也会被当作 0。&lt;/li&gt;
&lt;li&gt;返回值： true (如果找到) 或 false (如果未找到)。&lt;/li&gt;
&lt;li&gt;includes() 方法的搜索是严格区分大小写的。如果要进行不区分大小写的搜索，需要先将原字符串和搜索字符串转换为相同的大小写（通常是全小写或全大写），如果是搜索空字符串 (&quot;&quot;)返回 true，还可以处理 Unicode 字符。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; sentence &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Hello, welcome to the universe.&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(sentence.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;welcome&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(sentence.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;world&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 从索引 8 开始搜索 &quot;welcome&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(sentence.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;welcome&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;indexOf(searchValue, fromIndex)&lt;a href=&quot;#indexofsearchvalue-fromindex&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能：查找指定子字符串首次出现的索引。&lt;/li&gt;
&lt;li&gt;参数：searchValue (必需): 要查找的子字符串。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fromIndex&lt;/code&gt; (可选): 开始搜索的起始索引，默认为 0，&lt;code&gt;fromIndex&lt;/code&gt; 是负数，会被当作 0，如果&lt;code&gt;fromIndex&lt;/code&gt; 大于或等于字符串的长度，搜索将不会进行（除非搜索值是空字符串），并直接返回 -1。&lt;/li&gt;
&lt;li&gt;返回值： 第一个匹配项的索引。如果未找到，则返回 -1。&lt;/li&gt;
&lt;li&gt;indexOf() 的搜索是严格区分大小写的。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; paragraph &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;The quick brown fox jumps over the lazy dog. If the dog barked, was it a quick dog?&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 查找第一个 &quot;dog&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(paragraph.&lt;/span&gt;&lt;span&gt;indexOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;dog&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// 40&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 从索引 41 之后开始查找 &quot;dog&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(paragraph.&lt;/span&gt;&lt;span&gt;indexOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;dog&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;41&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// 72&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 查找一个不存在的词&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(paragraph.&lt;/span&gt;&lt;span&gt;indexOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;cat&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// -1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;lastIndexOf(searchValue, fromIndex)&lt;a href=&quot;#lastindexofsearchvalue-fromindex&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 与 indexOf() 类似，但它是从字符串的末尾向前搜索，返回最后一次出现的索引。&lt;/li&gt;
&lt;li&gt;参数：&lt;code&gt;searchValue&lt;/code&gt; (必需): 要查找的子字符串。&lt;code&gt;fromIndex&lt;/code&gt; (可选): 从该索引处开始向前搜索。默认为字符串的长度。&lt;/li&gt;
&lt;li&gt;返回值： 最后一个匹配项的索引，未找到则返回 -1&lt;/li&gt;
&lt;li&gt;与 indexOf() 一样，lastIndexOf() 的搜索也是严格区分大小写的。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; paragraph &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;The quick brown fox jumps over the lazy dog. If the dog barked, was it a quick dog?&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(paragraph.&lt;/span&gt;&lt;span&gt;lastIndexOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;dog&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// 72&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;startsWith(searchString, position)&lt;a href=&quot;#startswithsearchstring-position&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 判断当前字符串是否以另一个给定的子字符串开头。&lt;/li&gt;
&lt;li&gt;参数：&lt;code&gt;searchString&lt;/code&gt; (必需): 要搜索的子字符串。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;position&lt;/code&gt; (可选): 开始搜索的索引位置，默认为 0，如果 &lt;code&gt;position&lt;/code&gt; 是负数，它会被当作 0。&lt;/li&gt;
&lt;li&gt;返回值： true 或 false。&lt;/li&gt;
&lt;li&gt;区分大小写，使用 startsWith(&quot;&quot;) 检查任何字符串，结果始终为 true。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; str &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;To be, or not to be, that is the question.&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(str.&lt;/span&gt;&lt;span&gt;startsWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;To be&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 检查从索引 14 开始的子串是否以 &quot;that&quot; 开头&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(str.&lt;/span&gt;&lt;span&gt;startsWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;that&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;24&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;endsWith(searchString, length)&lt;a href=&quot;#endswithsearchstring-length&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 判断当前字符串是否以另一个给定的子字符串结尾。&lt;/li&gt;
&lt;li&gt;参数：&lt;code&gt;searchString&lt;/code&gt; (必需): 要搜索的子字符串。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;length&lt;/code&gt; (可选): &lt;code&gt;length&lt;/code&gt; 参数是 endsWith() 一个非常独特的特性。它并不指定搜索字符串的长度，而是指定将原字符串的前 &lt;code&gt;length&lt;/code&gt; 个字符视为要检查的子串。&lt;/li&gt;
&lt;li&gt;返回值： true 或 false。&lt;/li&gt;
&lt;li&gt;endsWith() 的搜索是严格区分大小写的，使用 endsWith(&quot;&quot;) 检查任何字符串，结果始终为 true，这包括指定了 &lt;code&gt;length&lt;/code&gt; 参数的情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; str &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Cats are the best!&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(str.&lt;/span&gt;&lt;span&gt;endsWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;best!&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 检查前 8 个字符 &quot;Cats are&quot; 是否以 &quot;are&quot; 结尾&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(str.&lt;/span&gt;&lt;span&gt;endsWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;are&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;replace(searchValue, newValue)&lt;a href=&quot;#replacesearchvalue-newvalue&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 用某些字符替换另一些字符，或替换一个与正则表达式匹配的子串。&lt;/li&gt;
&lt;li&gt;参数： &lt;code&gt;searchValue&lt;/code&gt; (必需): 要被替换的字符串或正则表达式 (RegExp), 当 &lt;code&gt;searchValue&lt;/code&gt; 是一个正则表达式时，你可以在 &lt;code&gt;newValue&lt;/code&gt; 这个字符串中使用一些特殊字符 $ 来引用匹配到的内容 1.$&amp;amp;: 插入匹配到的完整子串, 2.$&lt;code&gt;: 插入匹配子串左边的所有内容。&lt;/code&gt;newValue` (必需): newValue 不仅可以是字符串，还可以是一个函数（回调函数），这极大地增强了替换的灵活性。&lt;/li&gt;
&lt;li&gt;返回值： 一个部分或全部匹配项被替换的新字符串。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; p &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;The quick brown fox jumps over the lazy dog. If the dog barked, was it a quick dog?&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 当 searchValue 是字符串时，只替换第一个匹配项&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(p.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;dog&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;monkey&quot;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// &quot;The quick brown fox ... lazy monkey. If the dog barked...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 使用带 g (全局)标志的正则表达式，替换所有匹配项&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(p.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;dog&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;cat&quot;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// &quot;The quick brown fox ... lazy cat. If the cat barked...&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;replaceAll(searchValue, newValue)&lt;a href=&quot;#replaceallsearchvalue-newvalue&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 替换所有匹配的子串。&lt;/li&gt;
&lt;li&gt;参数： &lt;code&gt;searchValue&lt;/code&gt; (必需): 字符串或必须带 g 标志的正则表达式。&lt;code&gt;newValue&lt;/code&gt; (必需): 用于替换的字符串或函数。&lt;/li&gt;
&lt;li&gt;返回值： 所有匹配项被替换的新字符串。&lt;/li&gt;
&lt;li&gt;如果它的第一个参数 &lt;code&gt;searchValue&lt;/code&gt; 是一个正则表达式，那么这个正则表达式必须包含全局标志 g。否则，会抛出一个 TypeError。这是为了防止开发者误用，因为一个不带 g 的 replaceAll 没有任何意义。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; str &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;I like cats. My favorite cats are Siamese cats.&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 使用字符串直接替换所有 &quot;cats&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(str.&lt;/span&gt;&lt;span&gt;replaceAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;cats&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;dogs&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;I like dogs. My favorite dogs are Siamese dogs.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;toLowerCase() 和 toUpperCase()&lt;a href=&quot;#tolowercase-和-touppercase&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 分别将字符串转换为全小写或全大写。&lt;/li&gt;
&lt;li&gt;参数： 无。&lt;/li&gt;
&lt;li&gt;返回值： 转换后的新字符串, 原始字符串保持不变。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Hello World!&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(text.&lt;/span&gt;&lt;span&gt;toLowerCase&lt;/span&gt;&lt;span&gt;()); &lt;/span&gt;&lt;span&gt;// &quot;hello world!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(text.&lt;/span&gt;&lt;span&gt;toUpperCase&lt;/span&gt;&lt;span&gt;()); &lt;/span&gt;&lt;span&gt;// &quot;HELLO WORLD!&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;trim(), trimStart(), trimEnd()&lt;a href=&quot;#trim-trimstart-trimend&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 移除字符串开头、结尾或两端的空白字符 (空格、制表符、换行符等)。&lt;/li&gt;
&lt;li&gt;参数： 无。&lt;/li&gt;
&lt;li&gt;返回值： 移除空白后的新字符串, 原始字符串保持不变。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; greeting &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;   Hello there!   &quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(greeting.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;()); &lt;/span&gt;&lt;span&gt;// &quot;Hello there!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(greeting.&lt;/span&gt;&lt;span&gt;trimStart&lt;/span&gt;&lt;span&gt;()); &lt;/span&gt;&lt;span&gt;// &quot;Hello there!   &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(greeting.&lt;/span&gt;&lt;span&gt;trimEnd&lt;/span&gt;&lt;span&gt;()); &lt;/span&gt;&lt;span&gt;// &quot;   Hello there!&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;split(separator, limit)&lt;a href=&quot;#splitseparator-limit&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 使用指定的分隔符将一个字符串分割成一个字符串数组。&lt;/li&gt;
&lt;li&gt;参数： &lt;code&gt;separator&lt;/code&gt; (必需): 用作分割依据的字符串或正则表达式。如果为空字符串 &quot;&quot;，则每个字符都会被分割。 &lt;code&gt;limit&lt;/code&gt; (可选): 一个整数，限制返回的数组中的元素个数。&lt;/li&gt;
&lt;li&gt;返回值： 一个包含分割后子串的数组。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; csv &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;2025,August,17,Sunday&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; parts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; csv.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(parts); &lt;/span&gt;&lt;span&gt;// [&quot;2025&quot;, &quot;August&quot;, &quot;17&quot;, &quot;Sunday&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; word &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;character&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(word.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// [&quot;c&quot;, &quot;h&quot;, &quot;a&quot;, &quot;r&quot;, &quot;a&quot;, &quot;c&quot;, &quot;t&quot;, &quot;e&quot;, &quot;r&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;padStart(targetLength, padString) 和 padEnd(targetLength, padString)&lt;a href=&quot;#padstarttargetlength-padstring-和-padendtargetlength-padstring&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 在当前字符串的开头 (padStart) 或结尾 (padEnd) 填充指定的字符串，直到达到目标长度。&lt;/li&gt;
&lt;li&gt;参数：&lt;code&gt;targetLength&lt;/code&gt; (必需): 最终生成的字符串的期望长度。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;padString&lt;/code&gt; (可选): 用来填充的字符串，默认为空格。&lt;/li&gt;
&lt;li&gt;返回值： 填充后的新字符串。&lt;/li&gt;
&lt;li&gt;如果目标长度 &lt;code&gt;targetLength&lt;/code&gt; 小于或等于当前字符串的长度，这两个方法将不会进行任何填充，而是直接返回原始字符串, 需要填充的长度超过了&lt;code&gt;padString&lt;/code&gt; 的长度，&lt;code&gt;padString&lt;/code&gt; 将会被重复使用。如果重复后的 &lt;code&gt;padString&lt;/code&gt; 超出了所需长度，它会被从末尾截断。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; orderId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;123&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 通常用于补全编号，如在开头补 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(orderId.&lt;/span&gt;&lt;span&gt;padStart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;00000123&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; label &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Chapter 1&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 在结尾补点，用于对齐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(label.&lt;/span&gt;&lt;span&gt;padEnd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;.&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;Chapter 1...........&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;concat(…strings)&lt;a href=&quot;#concatstrings&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 将一个或多个字符串与原字符串连接合并。&lt;/li&gt;
&lt;li&gt;参数 &lt;code&gt;...strings&lt;/code&gt; (必需): 一个或多个要连接的字符串。&lt;/li&gt;
&lt;li&gt;返回值： 连接后的新字符串。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; str1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Hello&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; str2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;World&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(str1.&lt;/span&gt;&lt;span&gt;concat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot; &quot;&lt;/span&gt;&lt;span&gt;, str2, &lt;/span&gt;&lt;span&gt;&quot;!&quot;&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;Hello World!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 注意：在现代 JS 中，加号 `+` 或模板字符串 `` `${}` `` 是更常用和更高效的拼接方式。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// let result = str1 + &apos; &apos; + str2 + &apos;!&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// let result = `${str1} ${str2}!`;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;repeat(count)&lt;a href=&quot;#repeatcount&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;功能： 将字符串重复指定的次数。&lt;/li&gt;
&lt;li&gt;参数： count (必需): 一个整数，表示重复的次数。&lt;/li&gt;
&lt;li&gt;返回值： 包含重复内容的新字符串。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;repeat() 对 &lt;code&gt;count&lt;/code&gt; 参数有严格的要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;count 为 0: 返回一个空字符串 &quot;&quot;。&lt;/li&gt;
&lt;li&gt;count 为负数: 抛出一个 RangeError 错误。&lt;/li&gt;
&lt;li&gt;count 为无穷大 (Infinity): 抛出一个 RangeError 错误。&lt;/li&gt;
&lt;li&gt;count 为小数: 会被向下取整。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; cheer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Go! &quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(cheer.&lt;/span&gt;&lt;span&gt;repeat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;// &quot;Go! Go! Go! &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; separatorLine &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;=&quot;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;repeat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(separatorLine); &lt;/span&gt;&lt;span&gt;// &quot;====================&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>category:前端</category><category>category:JavaScript</category><category>tag:JavaScript</category><category>tag:ES6</category></item><item><title>从输入 URL 到页面展示的完整过程</title><link>https://star-xp.top/post/url</link><guid isPermaLink="false">url</guid><description>从输入 URL 到页面展示的完整过程</description><pubDate>Wed, 05 Nov 2025 10:44:20 GMT</pubDate><content:encoded>&lt;p&gt;在现代 Web 开发中，理解浏览器如何处理一个 URL 是掌握性能优化、网络安全及前端架构的基石。这一过程跨越了操作系统的网络栈、复杂的分布式网络设施以及高度优化的浏览器渲染引擎。以下是该过程的详尽拆解。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;一、 进程协作与 URL 解析&lt;a href=&quot;#一-进程协作与-url-解析&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1. 浏览器多进程架构背景&lt;a href=&quot;#1-浏览器多进程架构背景&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;现代浏览器（如 Chrome）采用多进程架构以确保稳定性和安全性。核心进程包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;浏览器进程 (Browser Process)&lt;/strong&gt;：负责地址栏、书签、前进后退按钮及网络请求的调度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络进程 (Network Process)&lt;/strong&gt;：专门处理底层网络协议、缓存管理及 DNS 查询。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渲染进程 (Renderer Process)&lt;/strong&gt;：将 HTML/CSS/JS 转化为用户可交互的界面，每个标签页通常运行在独立的沙箱进程中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPU 进程&lt;/strong&gt;：处理跨进程的图形绘制指令，实现硬件加速。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 地址栏输入判定&lt;a href=&quot;#2-地址栏输入判定&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;当地址栏接收到字符输入时，浏览器进程的 UI 线程首先启动。它会解析输入内容以区分其属性：如果是符合格式的 URL，则直接进入导航流程；如果是搜索词，则将其与预设的搜索引擎组合成搜索 URL。&lt;/p&gt;
&lt;h3&gt;3. URL 标准化与预处理&lt;a href=&quot;#3-url-标准化与预处理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;识别为 URL 后，浏览器会对字符串进行标准化。这包括自动补全协议（如 &lt;code&gt;https://&lt;/code&gt;）、处理非 ASCII 字符的转义编码、解析主机名（Host）、端口号（Port）及路径（Path）。此时，浏览器还会检查该域名是否在 HSTS（HTTP 严格传输安全）预加载列表中，若是，则强制将所有 &lt;code&gt;http://&lt;/code&gt; 请求升级为 &lt;code&gt;https://&lt;/code&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二、 拦截层与多级缓存机制&lt;a href=&quot;#二-拦截层与多级缓存机制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在请求正式发出之前，浏览器会经历多个内部拦截检查，以减少不必要的网络往返。&lt;/p&gt;
&lt;h3&gt;1. 导航类型与 BFCache&lt;a href=&quot;#1-导航类型与-bfcache&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;如果是通过“前进”或“后退”按钮触发的导航，浏览器会优先检查 &lt;strong&gt;往返缓存（Back-Forward Cache）&lt;/strong&gt;。若命中，浏览器将直接恢复页面的完整快照（包括 DOM、JS 状态和滚动位置），此时不会触发完整的网络请求或解析流程。&lt;/p&gt;
&lt;h3&gt;2. Service Worker 劫持&lt;a href=&quot;#2-service-worker-劫持&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;如果目标站点注册了 Service Worker，请求会被其 &lt;code&gt;fetch&lt;/code&gt; 事件拦截。Service Worker 运行在独立线程中，可以根据自定义策略直接从 Cache Storage 返回响应，实现离线访问或极速加载。&lt;/p&gt;
&lt;h3&gt;3. HTTP 缓存策略&lt;a href=&quot;#3-http-缓存策略&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;网络进程会检索磁盘（Disk Cache）和内存（Memory Cache）中的缓存副本：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;强缓存&lt;/strong&gt;：检查 &lt;code&gt;Cache-Control&lt;/code&gt;（如 &lt;code&gt;max-age&lt;/code&gt;）或 &lt;code&gt;Expires&lt;/code&gt;。若资源未过期，直接使用本地副本，状态码显示为 &lt;code&gt;200 (from cache)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协商缓存&lt;/strong&gt;：若强缓存失效，请求会携带 &lt;code&gt;If-None-Match&lt;/code&gt; (ETag) 或 &lt;code&gt;If-Modified-Since&lt;/code&gt; (Last-Modified)。服务器若确认资源未变，返回 &lt;code&gt;304 Not Modified&lt;/code&gt;，浏览器继续使用本地旧资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;三、 网络连接建立：DNS、TCP 与 TLS&lt;a href=&quot;#三-网络连接建立dnstcp-与-tls&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1. DNS 域名解析&lt;a href=&quot;#1-dns-域名解析&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;若缓存未命中，浏览器需要通过 DNS 协议将域名转换为 IP 地址。解析过程具有层级性：从浏览器缓存、操作系统缓存、Hosts 文件，到递归 DNS 服务器，最后到权威 DNS 服务器。现代浏览器支持 &lt;strong&gt;DoH (DNS over HTTPS)&lt;/strong&gt;，通过加密通道获取解析结果，防止域名劫持。&lt;/p&gt;
&lt;h3&gt;2. 传输层连接（TCP/QUIC）&lt;a href=&quot;#2-传输层连接tcpquic&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TCP 连接&lt;/strong&gt;：对于 HTTP/1.1 和 HTTP/2，浏览器与服务器进行“三次握手”建立可靠连接（SYN -&amp;gt; SYN-ACK -&amp;gt; ACK）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QUIC 连接&lt;/strong&gt;：对于 HTTP/3，基于 UDP 的 QUIC 协议将连接建立与加密握手合并，显著减少了往返时延（RTT）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. TLS 安全握手&lt;a href=&quot;#3-tls-安全握手&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在 HTTPS 场景下，TCP 建立后需进行 TLS 握手（目前主流为 TLS 1.2 或 1.3）。客户端与服务端协商加密套件、交换随机数、验证服务器数字证书的合法性，并最终生成对称加密的“会话密钥”，确保后续数据传输的私密性与完整性。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;四、 HTTP 请求发送与服务端响应&lt;a href=&quot;#四-http-请求发送与服务端响应&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1. 请求构造与优先级&lt;a href=&quot;#1-请求构造与优先级&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;连接就绪后，浏览器构造 HTTP 请求报文。报文包含请求行、Header（如 User-Agent, Cookie, Accept）及 Body。浏览器会根据资源类型（主文档、CSS、图片等）分配不同的优先级，高优先级资源会获得更多的连接资源。&lt;/p&gt;
&lt;h3&gt;2. 服务端处理链路&lt;a href=&quot;#2-服务端处理链路&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;请求到达服务器后，通常经过负载均衡器（如 Nginx）转发至应用服务器。服务器根据请求路径执行业务逻辑、查询数据库，并生成响应报文。&lt;/p&gt;
&lt;h3&gt;3. 响应接收与 MIME 检查&lt;a href=&quot;#3-响应接收与-mime-检查&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;浏览器接收响应头后，首先检查 &lt;code&gt;Content-Type&lt;/code&gt;。如果是 &lt;code&gt;text/html&lt;/code&gt;，则准备渲染；如果是 &lt;code&gt;application/octet-stream&lt;/code&gt;，则启动下载流程。若响应头包含重定向状态码（如 301/302），则根据 &lt;code&gt;Location&lt;/code&gt; 字段重新发起导航。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;五、 渲染流水线：从 HTML 到位图&lt;a href=&quot;#五-渲染流水线从-html-到位图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;一旦渲染进程接收到 HTML 字节流，复杂的渲染流水线（Rendering Pipeline）便开始运转。&lt;/p&gt;
&lt;h3&gt;1. 解析 HTML 构建 DOM 树&lt;a href=&quot;#1-解析-html-构建-dom-树&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;解析器将 HTML 字符转换为 Token，并根据嵌套关系构建 &lt;strong&gt;DOM 树&lt;/strong&gt;。解析过程中，&lt;strong&gt;预加载扫描器 (Preload Scanner)&lt;/strong&gt; 会提前识别并下载后续需要的 CSS 和 JS 资源，而不会等待 DOM 构建完成。&lt;/p&gt;
&lt;h3&gt;2. 解析 CSS 构建 CSSOM 树&lt;a href=&quot;#2-解析-css-构建-cssom-树&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;浏览器并行下载并解析 CSS 文件，生成 &lt;strong&gt;CSSOM 树&lt;/strong&gt;。CSSOM 描述了如何将样式应用于 DOM 节点。注意：CSS 的解析会阻塞 JS 的执行，因为脚本可能需要查询准确的计算样式。&lt;/p&gt;
&lt;h3&gt;3. JS 下载与执行：V8 引擎&lt;a href=&quot;#3-js-下载与执行v8-引擎&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;JS 默认是阻塞解析的。当解析器遇到 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;（无 &lt;code&gt;defer&lt;/code&gt;/&lt;code&gt;async&lt;/code&gt;）时，必须停止 HTML 解析，待 JS 下载并执行完毕后方可继续。V8 引擎通过词法分析、语法分析生成 AST（抽象语法树），随后通过解释器生成字节码，并利用即时编译器（JIT）将热点代码转化为机器码执行。&lt;/p&gt;
&lt;h3&gt;4. 生成渲染树 (Render Tree)&lt;a href=&quot;#4-生成渲染树-render-tree&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;浏览器合并 DOM 和 CSSOM，生成渲染树。渲染树只包含可见节点（排除 &lt;code&gt;display: none&lt;/code&gt; 及其子节点）。&lt;/p&gt;
&lt;h3&gt;5. 布局 (Layout)&lt;a href=&quot;#5-布局-layout&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;布局阶段计算渲染树中每个节点在屏幕上的几何信息（位置、大小）。这是通过盒模型算法实现的，涉及父子元素间的空间分配及文本换行计算。&lt;/p&gt;
&lt;h3&gt;6. 分层与绘制 (Layer &amp;amp; Paint)&lt;a href=&quot;#6-分层与绘制-layer--paint&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为了处理复杂的重叠关系（如 &lt;code&gt;z-index&lt;/code&gt;、&lt;code&gt;position: fixed&lt;/code&gt;），渲染进程会生成不同的 &lt;strong&gt;合成层 (Layers)&lt;/strong&gt;。随后，浏览器为每个层生成绘制指令（Paint Records），记录如何画出背景、边框、文字等。&lt;/p&gt;
&lt;h3&gt;7. 合成与显示 (Compositing)&lt;a href=&quot;#7-合成与显示-compositing&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;合成线程&lt;/strong&gt;接收各图层的绘制指令，将图层切分为瓦片（Tiles），并调用 &lt;strong&gt;GPU 进程&lt;/strong&gt;进行光栅化（将指令转为位图）。最后，GPU 将各层合并并输出到屏幕缓冲区，用户正式看到页面。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;六、 页面交互与事件循环&lt;a href=&quot;#六-页面交互与事件循环&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;页面显示后，渲染进程并未停止。它进入了持续的&lt;strong&gt;事件循环 (Event Loop)&lt;/strong&gt; 阶段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;宏任务与微任务&lt;/strong&gt;：JS 执行栈清空后，优先处理微任务队列（如 Promise 回调），然后从宏任务队列（如 定时器、IO 回调）取出一个执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交互响应&lt;/strong&gt;：用户的点击、滚动事件由浏览器进程捕获后分发至渲染进程，通过事件监听器触发 JS 代码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持续渲染&lt;/strong&gt;：任何 DOM 或样式的修改都可能触发重新布局（Reflow）或重绘（Repaint），渲染流水线会循环执行以保持界面更新。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;总结：深度思考&lt;a href=&quot;#总结深度思考&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;导航与渲染并非线性过程，而是高度并发且相互依赖的。性能优化的核心往往在于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;减少网络往返&lt;/strong&gt;（利用 HSTS、HTTP/3、CDN）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化关键渲染路径&lt;/strong&gt;（减少阻塞 CSS、合理使用 &lt;code&gt;defer&lt;/code&gt;/&lt;code&gt;async&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;减轻主线程负担&lt;/strong&gt;（利用合成层、避免强制同步布局、使用 Web Workers）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;掌握这一全流程，便能从底层逻辑上理解 Web 应用的运行本质。&lt;/p&gt;</content:encoded><category>category:前端</category><category>category:JavaScript</category><category>tag:JavaScript</category><category>tag:ES6</category></item><item><title>HTTP入门</title><link>https://star-xp.top/post/http</link><guid isPermaLink="false">http</guid><description>深入理解 HTTP 协议的核心设计哲学，从无状态性到 CORS，从缓存策略到 HTTP/3 的演进。</description><pubDate>Sun, 12 Oct 2025 18:55:38 GMT</pubDate><content:encoded>&lt;h2&gt;HTTP 协议的核心哲学&lt;a href=&quot;#http-协议的核心哲学&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;无状态：一种被误解的”缺陷”&lt;a href=&quot;#无状态一种被误解的缺陷&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;很多人对 HTTP 的无状态性嗤之以鼻，认为这是协议的重大缺陷。但这恰恰是 HTTP 能够支撑整个互联网的关键设计。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;无状态&lt;/code&gt;不是缺陷，而是一种深思熟虑的架构选择。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HTTP的无状态性&lt;/code&gt;让服务端水平扩展变得异常简单。每个请求携带完整上下文，任何服务器实例都能独立处理。这就像优秀的函数式编程思想 - 纯函数不依赖外部状态，易于&lt;code&gt;测试&lt;/code&gt;和&lt;code&gt;并行化&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# `有状态`的设计问题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;用户 -&amp;gt; 服务器A: 登录成功&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;用户 -&amp;gt; 服务器B: 请求数据 (B不知道用户已登录)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;服务器B -&amp;gt; 用户: 请先登录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# `无状态`的优雅&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;用户 -&amp;gt; 任意服务器: 请求 + Token&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;任意服务器 -&amp;gt; 用户: 响应数据&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;文本协议的智慧&lt;a href=&quot;#文本协议的智慧&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;用户报告上传文件时偶尔失败的&lt;code&gt;抓包&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;POST /upload HTTP/1.1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Host: api.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Length: 1024000&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;------WebKitFormBoundary7MA4YWxkTrZu0gW&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Disposition: form-data; name=&quot;file&quot;; filename=&quot;document.pdf&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Type: application/pdf&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[二进制数据]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;------WebKitFormBoundary7MA4YWxkTrZu0gW--&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;边界字符串的错误立即显而易见。如果是二进制协议，可能需要专业的解析工具才能发现问题。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;文本协议&lt;/code&gt;牺牲了少量性能，但获得了无与伦比的&lt;code&gt;调试便利性&lt;/code&gt;。这是一个典型的性能与可维护性的权衡。&lt;/p&gt;
&lt;h3&gt;分层架构的思想&lt;a href=&quot;#分层架构的思想&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;HTTP 延续了 TCP/IP 的分层哲学，每一层都有明确的职责。这种分层不是随意的技术划分，而是对复杂性的优雅管理。&lt;strong&gt;HTTP 站在 TCP 的肩膀上，专注于应用层语义，让专业的层做专业的事&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;深入 CORS：同源策略&lt;a href=&quot;#深入-cors同源策略&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;为什么需要同源策略？&lt;a href=&quot;#为什么需要同源策略&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;同源策略&lt;/code&gt;是 Web 安全的基石，它源于一个深刻的洞察：浏览器中的代码运行在用户环境中，而不是开发者环境中。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设想没有同源策略的世界：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;恶意网站可以读取你的银行余额&lt;/li&gt;
&lt;li&gt;攻击者可以以你的名义发送邮件&lt;/li&gt;
&lt;li&gt;任何网站都能访问你的内部系统&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CORS 不是限制，而是一种保护机制。&lt;/p&gt;
&lt;h3&gt;CORS 预检请求&lt;a href=&quot;#cors-预检请求&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;CORS&lt;/code&gt;的&lt;code&gt;预检请求&lt;/code&gt;设计的智慧：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 简单请求 - 直接发送&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;GET /api/data HTTP/1.1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Origin: https://example.com&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 复杂请求 - 先预检&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;OPTIONS /api/data HTTP/1.1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Access-Control-Request-Method: PUT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Access-Control-Request-Headers: Content-Type, X-Custom-Header&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 服务器响应&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;HTTP/1.1 204 No Content&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Access-Control-Allow-Origin: https://example.com&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Access-Control-Allow-Methods: PUT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Access-Control-Allow-Headers: Content-Type, X-Custom-Header&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;预检请求&lt;/code&gt;体现了一种”先询问，后行动”的安全哲学&lt;/strong&gt;，它避免了对服务器的意外修改，特别是对于可能有副作用的请求。&lt;/p&gt;
&lt;h3&gt;CORS 与 CSRF&lt;a href=&quot;#cors-与-csrf&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在理解了&lt;code&gt;CORS&lt;/code&gt;后，重新审视了&lt;code&gt;CSRF&lt;/code&gt;（跨站请求伪造）防护。这两个概念经常让开发者混淆，但它们解决的是不同层面的安全问题。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# CORS解决的问题：是否允许跨域读取响应&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;浏览器 -&amp;gt; A站点: 从B站点获取数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;B站点 -&amp;gt; 浏览器: 这里是你请求的数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;浏览器 -&amp;gt; A站点: 根据CORS配置决定是否提供数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# CSRF解决的问题：是否执行跨域状态改变操作&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;用户 -&amp;gt; 银行站点: 登录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;攻击者站点 -&amp;gt; 用户浏览器: 发送转账请求到银行&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;用户浏览器 -&amp;gt; 银行站点: 携带用户凭证的转账请求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;银行站点: 根据CSRF Token验证请求的合法性&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;CORS&lt;/code&gt;控制的是信息的读取权限，&lt;code&gt;CSRF&lt;/code&gt;防止的是未授权的状态改变&lt;/strong&gt;，这两种机制相互补充，共同构建了 Web 应用的安全防线。&lt;/p&gt;
&lt;h2&gt;HTTP 性能优化&lt;a href=&quot;#http-性能优化&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;连接复用&lt;a href=&quot;#连接复用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;HTTP/1.0 时代，每个资源都需要新建一个 TCP 连接。这种设计在网页简单时是合理的，但随着网页复杂度增加，性能问题凸显。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;连接复用&lt;/code&gt;的核心思想是”连接即资源”，应该被珍惜和重用。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# HTTP/1.0 - 每个请求新建连接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;域名解析 -&amp;gt; TCP连接 -&amp;gt; HTTP请求 -&amp;gt; HTTP响应 -&amp;gt; TCP断开&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;域名解析 -&amp;gt; TCP连接 -&amp;gt; HTTP请求 -&amp;gt; HTTP响应 -&amp;gt; TCP断开&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;域名解析 -&amp;gt; TCP连接 -&amp;gt; HTTP请求 -&amp;gt; HTTP响应 -&amp;gt; TCP断开&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# HTTP/1.1 Keep-Alive - 连接复用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;域名解析 -&amp;gt; TCP连接 -&amp;gt; HTTP请求 -&amp;gt; HTTP响应&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    -&amp;gt; HTTP请求 -&amp;gt; HTTP响应&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    -&amp;gt; HTTP请求 -&amp;gt; HTTP响应 -&amp;gt; TCP断开&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种优化看似简单，但背后体现了对资源利用效率的深刻思考。&lt;strong&gt;任何优化都是权衡，没有银弹&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;缓存策略&lt;a href=&quot;#缓存策略&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;HTTP缓存&lt;/code&gt;可能是 Web 性能优化中最被低估的特性。深入理解 ETag、Last-Modified、Cache-Control 的交互机制，让你更懂&lt;code&gt;HTTP缓存&lt;/code&gt;策略。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 强缓存 - 客户端直接复用，不发送请求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Cache-Control: max-age=3600&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Expires: Wed, 21 Oct 2025 07:28:00 GMT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 协商缓存 - 客户端发送验证请求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;If-Modified-Since: Wed, 21 Oct 2025 07:00:00 GMT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;If-None-Match: &quot;5d8c72a5edda3&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 服务器响应&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;HTTP/1.1 304 Not Modified&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 或&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;HTTP/1.1 200 OK&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ETag: &quot;5d8c72a5edda3&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;缓存策略的本质是”空间换时间”的经典算法思想在网络协议中的应用。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;压缩算法&lt;a href=&quot;#压缩算法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Gzip 压缩是另一个 HTTP 特性。让我们感受一下压缩前后的数据对比：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 原始响应&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Type: application/json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Length: 10240&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&quot;users&quot;:[{&quot;id&quot;:1,&quot;name&quot;:&quot;Alice&quot;,&quot;email&quot;:&quot;alice@example.com&quot;},...]}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 压缩后响应&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Type: application/json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Encoding: gzip&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Length: 1240&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[二进制压缩数据]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;约 90%的&lt;code&gt;压缩率&lt;/code&gt;！&lt;/strong&gt; 但是它需要 CPU 资源，会增加服务器响应延迟。这是 HTTP 设计的另一个哲学：&lt;strong&gt;协议应该提供机制，而不是策略&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;HTTP/2 和 HTTP/3&lt;a href=&quot;#http2-和-http3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;HTTP/2 的多路复用&lt;a href=&quot;#http2-的多路复用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;HTTP/1.1 的管道化（Pipelining）尝试解决并行请求问题，但因为队头阻塞（Head-of-line Blocking）而失败。HTTP/2 的&lt;code&gt;多路复用&lt;/code&gt;用全新的思路解决了这个问题：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# HTTP/1.1 - 队头阻塞问题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;请求1 -&amp;gt; 响应1 (慢)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;请求2 -&amp;gt; 等待响应1完成&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;请求3 -&amp;gt; 等待响应2完成&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# HTTP/2 - 真正的多路复用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;流1: 请求1 -&amp;gt; 响应1片段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;流2: 请求2 -&amp;gt; 响应2片段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;流3: 请求3 -&amp;gt; 响应3片段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;流1: 响应1片段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;流2: 响应2片段&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;多路复用&lt;/code&gt;的思想突破在于：把请求-响应对抽象为独立的”流”，在单一连接上交错传输。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;HTTP/3 的 QUIC&lt;a href=&quot;#http3-的-quic&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;深入研究 QUIC 协议：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# TCP的队头阻塞问题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;TCP数据包1 -&amp;gt; 丢失&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;TCP数据包2 -&amp;gt; 必须等待数据包1重传&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;TCP数据包3 -&amp;gt; 必须等待数据包1重传&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;应用层: 全部阻塞&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# QUIC的流级隔离&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;QUIC流1数据包 -&amp;gt; 丢失，只影响流1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;QUIC流2数据包 -&amp;gt; 正常传输，不受影响&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;QUIC流3数据包 -&amp;gt; 正常传输，不受影响&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;应用层: 流2和流3正常进行&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;QUIC 的核心思想是：在 UDP 之上重新实现 TCP 的可靠传输特性，但解决 TCP 的固有问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;QUIC 的 0-RTT 连接建立：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# TCP+TLS 1.3 - 至少2-RTT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;客户端 -&amp;gt; 服务器: TCP SYN&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;服务器 -&amp;gt; 客户端: TCP SYN+ACK&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;客户端 -&amp;gt; 服务器: TCP ACK + TLS ClientHello&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;服务器 -&amp;gt; 客户端: TLS ServerHello + 数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# QUIC - 0-RTT恢复连接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;客户端 -&amp;gt; 服务器: 初始包 + 请求数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;服务器 -&amp;gt; 客户端: 响应数据&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这种优化背后的思想是：利用之前连接的”&lt;code&gt;记忆&lt;/code&gt;”跳过不必要的握手步骤。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;协议演进的统一思想&lt;a href=&quot;#协议演进的统一思想&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;回顾 HTTP 的演进历程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;HTTP/1.0 到 HTTP/1.1&lt;/strong&gt;：连接的复用和优化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/1.1 到 HTTP/2&lt;/strong&gt;：应用层并行的突破&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/2 到 HTTP/3&lt;/strong&gt;：传输层问题的根本解决&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;每一代协议的优化都针对上一代的核心痛点，但保持了应用层语义的兼容性。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;HTTP 协议的永恒思想&lt;a href=&quot;#http-协议的永恒思想&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;分层与抽象&lt;a href=&quot;#分层与抽象&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;HTTP 协议的设计体现了计算机科学中的一个核心思想：&lt;strong&gt;通过&lt;code&gt;分层抽象&lt;/code&gt;管理复杂性&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;应用层: HTTP语义 - URL、方法、状态码、头部&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;传输层: TCP/QUIC - 可靠传输、流量控制、拥塞避免&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;网络层: IP - 路由、寻址、分片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;链路层: Ethernet/WiFi - 帧传输、错误检测&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;物理层: 电缆/光纤 - 比特传输&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每一层都有明确的职责和边界，上层不需要了解下层的实现细节。&lt;/p&gt;
&lt;h3&gt;机制与策略分离&lt;a href=&quot;#机制与策略分离&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;HTTP协议&lt;/code&gt;很少规定具体的使用策略，而是提供灵活的机制让应用层决定。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;缓存&lt;/strong&gt;：HTTP 定义了缓存机制，但具体缓存策略由应用决定&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;压缩&lt;/strong&gt;：HTTP 支持多种压缩算法，选择合适的算法是应用的责任&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;认证&lt;/strong&gt;：HTTP 提供认证框架，具体认证方式可以自定义&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全&lt;/strong&gt;：HTTP 可以升级到 HTTPS，但是否升级由应用决定&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;这种分离让 HTTP 协议既强大又灵活，能够适应各种应用场景。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;向后兼容&lt;a href=&quot;#向后兼容&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;HTTP 协议的一个重要特点是&lt;strong&gt;极强的&lt;code&gt;向后兼容性&lt;/code&gt;&lt;/strong&gt;。HTTP/2 和 HTTP/3 都能在必要时&lt;code&gt;优雅降级&lt;/code&gt;到 HTTP/1.1，这保证了互联网的稳定发展。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;HTTP/2服务器 -&amp;gt; 客户端: 我能支持HTTP/2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;HTTP/2客户端 -&amp;gt; 服务器: 好的，我们用HTTP/2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;HTTP/2服务器 -&amp;gt; 老旧客户端: 我能支持HTTP/2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;老旧客户端 -&amp;gt; 服务器: 我不懂HTTP/2，用HTTP/1.1吧&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;服务器 -&amp;gt; 老旧客户端: HTTP/1.1 200 OK&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;系统升级应该渐进式，而不是革命式&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;总结&lt;a href=&quot;#总结&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;HTTP 协议的核心特性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;简单性&lt;/code&gt;&lt;/strong&gt; - 无状态设计&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;抽象分层&lt;/code&gt;&lt;/strong&gt; - TCP/IP 分层&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;向后兼容&lt;/code&gt;&lt;/strong&gt; - 协议演进&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;机制与策略分离&lt;/code&gt;&lt;/strong&gt; - 缓存、压缩等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;安全性&lt;/code&gt;&lt;/strong&gt; - CORS、HTTPS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;性能优化&lt;/code&gt;&lt;/strong&gt; - 连接复用、缓存、压缩&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;HTTP 不仅是一个协议，更是一种思维方式，一套解决复杂问题的哲学体系。&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“完美不是指不能再增加什么，而是指不能再减少什么。” - 安托万·德·圣埃克苏佩里&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><category>category:计算机网络</category><category>tag:网络</category><category>tag:TCP/IP</category><category>tag:HTTP</category></item><item><title>TCP入门</title><link>https://star-xp.top/post/tcp</link><guid isPermaLink="false">tcp</guid><description>深入理解 TCP 协议的核心设计哲学。</description><pubDate>Sun, 12 Oct 2025 18:55:38 GMT</pubDate><content:encoded>&lt;h2&gt;TCP 报文协议&lt;a href=&quot;#tcp-报文协议&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;TCP 头部字段主要有序列号， 确认号， 源端口， 目的端口号， 标记位，头部长度， 窗口大小等， 其中源端口和目的端口都是 16 位，源端口是发送方使用的端口号， 目的端口是接收方使用的端口号，  序列号和确认号都是 32 位， 序列号可以保证数据的有效性，确认号可以保证数据的可靠性， 下面我们来逐个分解。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;源端口 ：16 位字节，发送方进程。&lt;/li&gt;
&lt;li&gt;目的端口 ：16 位字节，接收方进程。&lt;/li&gt;
&lt;li&gt;序列号 ：32 位字节，给每一个字节都设置一个序号，接收方可以根据序号对接收到的数据进行排序。&lt;/li&gt;
&lt;li&gt;确认号 ：32 位字节，告诉发送法我收到了序号 N-1 之前的所有数据，并且通知对方下次从序号 N 开始发送。&lt;/li&gt;
&lt;li&gt;数据偏移／首部长度 ：4 位字（一字是 4 字节），因为 TCP 的首部长度是不固定的，用来表示真正有效的数据，相对于整个 TCP 段的起始位置偏移了多少, 有利于接收方裁剪识别。&lt;/li&gt;
&lt;li&gt;保留 ：6 位字节，是协议设计者为未来预留的“空白地盘”。&lt;/li&gt;
&lt;li&gt;校验和 ：16 位字节，用来检查数据在接收的时候是否损坏。&lt;/li&gt;
&lt;li&gt;窗口 ：16 位字节，告诉发送法我这里还能存储多少字节的数据。&lt;/li&gt;
&lt;li&gt;紧急指针 ：16 位字节，一个特殊的插队机制。&lt;/li&gt;
&lt;li&gt;选项和填充 ：选项 MSS - 告诉对方，我能接收的单个数据包最大是多少，填充 SACK -  如果丢了第 5 个包，但收到了第 6、7 个，SACK 可以告诉发送方“只重传第 5 个”，不用全重传。&lt;/li&gt;
&lt;li&gt;数据部分 ：应用层的数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;TCP 建立连接&lt;a href=&quot;#tcp-建立连接&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;TCP 三次握手， 我们来详细了解一下这个过程&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始状态双方的 TCP 都处于 close， 但是服务器会监听一个端口， listen 来监听。&lt;/li&gt;
&lt;li&gt;第一次握手， 客户端会生成一个随机的序列号 x 放到 TCP 协议的序列号当中，req=x， 然后会将 SYN 标志设置为 1，发送给客户端，之后服务端处于 SYN-SENT 状态。&lt;/li&gt;
&lt;li&gt;服务器接收到了客户端发送来的数据， 然后生成一个随机的序列号 y 放到 TCP 报文中，发送 SYN=1, ACK=1，req=y, ack=x+1 之后服务器也处于 SYN-SENT 状态。&lt;/li&gt;
&lt;li&gt;客户端接收到服务端发送来的数据，客户端会回一个 ACK 确认报文， 该报文的确认号就是 ack=y+1, 序列号为服务端的确认号 seq=x+1, 之后客户端处于 ESTABLISHED 状态。&lt;/li&gt;
&lt;li&gt;服务端接收到 ACK 确认报文后， 服务端也处于 ESTABLISHED 状态。&lt;/li&gt;
&lt;/ul&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;资源浪费，当服务端接收到客户端发来的 SYN 并且确定建立连接时，服务器就会立即为连接分配资源， 如果接收到了一个过期的历史资源包，这个资源包有不需要客户端的确认， 就会在服务端一直挂着，耗费内存。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;TCP 断开连接&lt;a href=&quot;#tcp-断开连接&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;TCP 四次挥手&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始状态都是 WSTABLISHED&lt;/li&gt;
&lt;li&gt;客户端向服务端发送 FIN 报文， 客户端进入 FIN_WAIT_1 状态。&lt;/li&gt;
&lt;li&gt;服务端接收到了客户端发来的报文， 于是向服务端发送 ACK 确认报文，之后服务端进入 CLOSE_WAIT 状态。&lt;/li&gt;
&lt;li&gt;客户端接受到服务端发送来的 ACK 报文， 于是进入了 FIN_WAIT_2 状态。&lt;/li&gt;
&lt;li&gt;服务端处理完数据之后， 向客户端发送 FIN 报文，之后服务端处于 LAST_ACK 状态。&lt;/li&gt;
&lt;li&gt;客户端接受到了服务端的 FIN 报文，向服务端回了一个 ACK 报文， 然后进入 TIME_AWIT 状态。&lt;/li&gt;
&lt;li&gt;服务端接收到 ACK 报文， 进入 CLOSE 状态， 断开链接。&lt;/li&gt;
&lt;li&gt;客户端在 TIME_AWIT 状态经过 2 个 MSL 后，就进入了 CLOSE 状态， 客户端的连接也断开。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;为什么是四次挥手而不是三次挥手？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TCP 是全双工协议， 客户端和服务端都具备发送和接收的能力， 所以我们要保证双方的数据都发送处理完成 ，才能断开。当客户端向服务端发送了一个 FIN 报文， 这个时候就表示客户端的数据已经处理好了， 可以断开连接了， 然后服务端接收到客户端发来的 FIN 报文， 于是发了一个 ACK 确认报文， 说同意断开连接， 但是这个时候服务端还可能没把数据处理完，所以需要等服务端处理完成之后才可以发送 FIN 报文给客户端，所以二次挥手不可与三次挥手合并， 所以需要四次挥手，如果只有三次挥手，可能会导致数据丢失。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:计算机网络</category><category>tag:网络</category><category>tag:TCP/IP</category><category>tag:HTTP</category></item><item><title>WebSocket</title><link>https://star-xp.top/post/websocket</link><guid isPermaLink="false">websocket</guid><description>从零构建WebSocket实时聊天室: 原理剖析与代码实战</description><pubDate>Sun, 12 Oct 2025 18:55:38 GMT</pubDate><content:encoded>&lt;p&gt;作为一名前端工程师，实时通信是我们必须掌握的核心技术之一。今天，我将通过一个完整的聊天室项目，带大家深入理解 WebSocket 的工作原理，并手把手教大家如何构建一个功能完善的实时聊天应用。&lt;/p&gt;
&lt;h2&gt;一、WebSocket 协议深度解析&lt;a href=&quot;#一websocket-协议深度解析&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1.1 HTTP 协议的局限性与 WebSocket 的诞生&lt;a href=&quot;#11-http-协议的局限性与-websocket-的诞生&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在深入代码之前，我们必须理解为什么需要 WebSocket。传统的 HTTP 协议基于”请求-响应”模型，这种模型存在以下关键问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HTTP 轮询的本质缺陷：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 传统轮询实现&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setInterval&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/api/messages&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // 即使没有新消息，也会发送请求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // 造成大量带宽浪费和服务器压力&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;)  &lt;/span&gt;&lt;span&gt;// 每秒请求一次&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种方式会产生大量无效请求，假设有 1000 个用户在线，每秒就会产生 1000 个请求，而其中可能 99%都是无效的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;长轮询的改进与不足：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 长轮询实现&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; longPolling&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/api/messages&apos;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        timeout: &lt;/span&gt;&lt;span&gt;30000&lt;/span&gt;&lt;span&gt;  // 30秒超时&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 收到消息或超时后立即发起下一次请求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        longPolling&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;长轮询虽然减少了请求次数，但服务器需要保持大量连接，且仍然无法实现服务器主动推送。&lt;/p&gt;
&lt;h3&gt;1.2 WebSocket 握手机制详解&lt;a href=&quot;#12-websocket-握手机制详解&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;WebSocket 通过一次 HTTP 握手升级连接，随后建立持久的双向通道。让我们深入理解这个过程：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;客户端发起握手请求：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;span&gt; /chat &lt;/span&gt;&lt;span&gt;HTTP&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Host&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; server.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Upgrade&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; websocket                    # 请求协议升级&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Connection&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Upgrade                   # 连接升级&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Sec-WebSocket-Key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; x3JJHMbDL1EzLkh9GBhXDw==  # 随机生成的Base64编码密钥&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Sec-WebSocket-Version&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 13             # WebSocket协议版本&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Origin&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; http://example.com            # 请求来源&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键字段解析：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Upgrade: websocket&lt;/code&gt;：告诉服务器客户端希望升级到 WebSocket 协议&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Sec-WebSocket-Key&lt;/code&gt;：用于安全校验，防止恶意或无意的连接&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Sec-WebSocket-Version&lt;/code&gt;：确保客户端和服务器使用相同的协议版本&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;服务器响应握手：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;HTTP&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;1.1&lt;/span&gt;&lt;span&gt; 101&lt;/span&gt;&lt;span&gt; Switching Protocols      # 101状态码表示协议切换&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Upgrade&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; websocket&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Connection&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Upgrade&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Sec-WebSocket-Accept&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; HSmrc0sMlYUkAGmm5OPpG2HaGWk=  # 基于客户端Key计算的值&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 WebSocket 数据帧结构&lt;a href=&quot;#13-websocket-数据帧结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;WebSocket 使用帧（Frame）来传输数据，每个帧都有特定的结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt; 0                   1                   2                   3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+-+-+-+-+-------+-+-------------+-------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|F|R|R|R| opcode|M| Payload len |    Extended payload length    |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|N|V|V|V|       |S|             |   (if payload len==126/127)   |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;| |1|2|3|       |K|             |                               |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|     Extended payload length continued, if payload len == 127  |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+ - - - - - - - - - - - - - - - +-------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|                               |Masking-key, if MASK set to 1  |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+-------------------------------+-------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;| Masking-key (continued)       |          Payload Data         &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+-------------------------------- - - - - - - - - - - - - - - - +&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:                     Payload Data continued ...                :&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|                     Payload Data continued ...                |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+---------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;重要字段说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FIN&lt;/code&gt;：表示是否为消息的最后一个分片&lt;/li&gt;
&lt;li&gt;&lt;code&gt;opcode&lt;/code&gt;：操作码，表示帧类型（文本、二进制、关闭等）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MASK&lt;/code&gt;：客户端发送的数据必须掩码，服务器发送的不需要&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Payload&lt;/code&gt;：实际传输的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.4 WebSocket 心跳机制&lt;a href=&quot;#14-websocket-心跳机制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为了保持连接活跃，WebSocket 实现了 Ping/Pong 心跳机制：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 客户端心跳实现&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; heartbeatInterval;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; startHeartbeat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;socket&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    heartbeatInterval &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; setInterval&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (socket.readyState &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; WebSocket.&lt;/span&gt;&lt;span&gt;OPEN&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            socket.&lt;/span&gt;&lt;span&gt;send&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ type: &lt;/span&gt;&lt;span&gt;&apos;ping&apos;&lt;/span&gt;&lt;span&gt; }));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }, &lt;/span&gt;&lt;span&gt;30000&lt;/span&gt;&lt;span&gt;);  &lt;/span&gt;&lt;span&gt;// 每30秒发送一次心跳&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 服务端响应&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;message&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(data);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (message.type &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;ping&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        socket.&lt;/span&gt;&lt;span&gt;send&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ type: &lt;/span&gt;&lt;span&gt;&apos;pong&apos;&lt;/span&gt;&lt;span&gt; }));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、Socket.IO：工程化的 WebSocket 解决方案&lt;a href=&quot;#二socketio工程化的-websocket-解决方案&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;当我们谈论实时 Web 应用时，原生 WebSocket 是基石，但它更像是一个低级别的 API，留下了许多工程化问题需要开发者自己解决，例如连接的可靠性、浏览器兼容性以及扩展性。Socket.IO 正是为解决这些痛点而生，它不是简单地封装 WebSocket，而是提供了一套完整的、经过生产环境严苛考验的实时通信框架。&lt;/p&gt;
&lt;h3&gt;2.1 为什么选择 Socket.IO？—— 超越基础连接&lt;a href=&quot;#21-为什么选择-socketio-超越基础连接&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;选择 Socket.IO 的核心理由在于它为开发者“包办”了那些构建健壮实时应用时最棘手、最繁琐的工作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 自动降级机制 (Automatic Fallback):&lt;/strong&gt; 这是 Socket.IO 最著名的特性。它确保了您的应用能在任何网络环境下工作，即使在不支持 WebSocket 的旧版浏览器或被企业防火墙限制的网络中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Socket.IO 会按以下顺序智能地选择最佳传输方式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; transports&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;websocket&apos;&lt;/span&gt;&lt;span&gt;,        &lt;/span&gt;&lt;span&gt;// 首选：最高效的双向通信&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;webtransport&apos;&lt;/span&gt;&lt;span&gt;,     &lt;/span&gt;&lt;span&gt;// 实验性：基于 HTTP/3 的新一代传输协议&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;polling&apos;&lt;/span&gt;&lt;span&gt;           // 降级方案：可靠的 HTTP 长轮询&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Flash Socket 已被废弃，不再使用&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;深度思考&lt;/strong&gt;：这个机制的价值在于&lt;strong&gt;可靠性&lt;/strong&gt;。当用户的网络环境（例如公司防火墙封锁了 WebSocket 端口）阻止了 WebSocket 连接时，应用不会直接失败，而是无缝降级到 HTTP 长轮询，保证了核心功能的可用性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 自动重连机制 (Automatic Reconnection):&lt;/strong&gt; 网络是不可靠的。移动设备进出隧道、Wi-Fi 切换、短暂的网络波动都可能导致连接中断。Socket.IO 内置了一套强大的自动重连逻辑，采用了**“指数退避 (Exponential Backoff)”**策略。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Socket.IO 默认的智能重连配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; socket&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; io&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    reconnection: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,              &lt;/span&gt;&lt;span&gt;// 启用自动重连（默认）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    reconnectionAttempts: &lt;/span&gt;&lt;span&gt;Infinity&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;span&gt;// 无限次尝试&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    reconnectionDelay: &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;,         &lt;/span&gt;&lt;span&gt;// 初始延迟1秒&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    reconnectionDelayMax: &lt;/span&gt;&lt;span&gt;5000&lt;/span&gt;&lt;span&gt;,      &lt;/span&gt;&lt;span&gt;// 最大延迟5秒&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    randomizationFactor: &lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;         // 随机因子，避免&quot;惊群效应&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;“指数退避”和“随机因子”是工程化的体现。它避免了在服务器宕机恢复后，成千上万的客户端在同一时刻发起重连请求，从而压垮服务器。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 内置心跳机制 (Built-in Heartbeat):&lt;/strong&gt; 原生 WebSocket 需要开发者手动实现心跳来防止连接被中间代理超时断开。Socket.IO 则内置了心跳机制，通过周期性的 &lt;code&gt;ping/pong&lt;/code&gt; 包自动维持连接活性，并检测“僵尸连接”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 房间和命名空间 (Rooms &amp;amp; Namespaces):&lt;/strong&gt; 这是 Socket.IO 在服务器端扩展性的关键。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;命名空间 (Namespaces)&lt;/strong&gt;：像 API 的不同端点（&lt;code&gt;/users&lt;/code&gt;, &lt;code&gt;/orders&lt;/code&gt;），用于在单个连接上隔离不同的业务逻辑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;房间 (Rooms)&lt;/strong&gt;：在命名空间内，用于向特定的客户端子集广播消息（如群聊、订阅特定频道）。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 服务器端 (Node.js)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 命名空间：隔离不同的功能模块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; chatNamespace&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; io.&lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/chat&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; adminNamespace&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; io.&lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/admin&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;chatNamespace.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;connection&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;socket&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 房间：实现群组功能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;room1&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 向 &apos;room1&apos; 房间内的所有客户端（包括发送者）发送消息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    chatNamespace.&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;room1&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;message&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;A new user has joined Room1&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;5. 消息确认 (Acknowledgements):&lt;/strong&gt; 支持 &lt;code&gt;emit&lt;/code&gt; 时附加回调函数，实现类似 RPC (远程过程调用) 的请求-响应模式，方便地确认消息是否被对方收到并处理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现效果 :&lt;/strong&gt;
&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/direct/1529e9afb7e646718f9b9cef94d73273.png&quot; alt=&quot;在这里插入图片描述&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/direct/0347e75373f74423b49ea7fb69b9087a.png&quot; alt=&quot;在这里插入图片描述&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;三、客户端核心实现详解（chat.js）&lt;a href=&quot;#三客户端核心实现详解chatjs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;现在让我们深入分析客户端的核心代码实现。&lt;/p&gt;
&lt;h3&gt;3.1 连接建立与初始化&lt;a href=&quot;#31-连接建立与初始化&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; socket&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; io.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这行代码背后发生了什么？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 协议自动识别：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Socket.IO内部实现&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; connect&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; protocol&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; window.location.protocol &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;https:&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; &apos;wss:&apos;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &apos;ws:&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;protocol&lt;/span&gt;&lt;span&gt;}//${&lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;location&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;host&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WebSocket&lt;/span&gt;&lt;span&gt;(url);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 连接状态管理：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Socket.IO维护的连接状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;connect&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Connected:&apos;&lt;/span&gt;&lt;span&gt;, socket.id);  &lt;/span&gt;&lt;span&gt;// 唯一的socket ID&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;disconnect&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;reason&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Disconnected:&apos;&lt;/span&gt;&lt;span&gt;, reason);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 可能的原因：&apos;io server disconnect&apos;, &apos;ping timeout&apos;, &apos;transport close&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 登录流程的完整实现&lt;a href=&quot;#32-登录流程的完整实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;page.&lt;/span&gt;&lt;span&gt;onLogin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(username)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;login&apos;&lt;/span&gt;&lt;span&gt;, username)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;login&quot;&lt;/span&gt;&lt;span&gt;,(&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt;(result){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        page.&lt;/span&gt;&lt;span&gt;intoChatRoom&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;users&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;)  &lt;/span&gt;&lt;span&gt;// 请求用户列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;用户名已被占用&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;登录流程时序图：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;客户端                     服务器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |                          |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |----emit(&apos;login&apos;)--------&amp;gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |                          | 验证用户名&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&amp;lt;---emit(&apos;login&apos;,true)----|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |                          |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |----emit(&apos;users&apos;)--------&amp;gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |                          | 发送用户列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&amp;lt;---emit(&apos;users&apos;,[...])---|&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;/li&gt;
&lt;li&gt;&lt;strong&gt;异步处理：&lt;/strong&gt; 使用事件机制处理异步响应，不阻塞 UI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态同步：&lt;/strong&gt; 登录成功后立即获取用户列表，保持状态一致&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3.3 消息发送机制深度分析&lt;a href=&quot;#33-消息发送机制深度分析&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;page.&lt;/span&gt;&lt;span&gt;onSendMsg&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 输入验证层&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;msg &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;msg.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 构造消息体&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; payload&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        from,           &lt;/span&gt;&lt;span&gt;// 发送者&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        msg,            &lt;/span&gt;&lt;span&gt;// 消息内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        to: to &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;  // 接收者，null表示群发&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 发送消息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;msg&apos;&lt;/span&gt;&lt;span&gt;, payload)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 乐观更新UI&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    page.&lt;/span&gt;&lt;span&gt;addMsg&lt;/span&gt;&lt;span&gt;(from, msg, to)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    page.&lt;/span&gt;&lt;span&gt;clearInput&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 消息接收与去重机制&lt;a href=&quot;#34-消息接收与去重机制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;message&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 数据验证&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;from &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;msg) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 去重处理：避免显示自己发送的消息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt;(from &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; page.me) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 渲染消息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    page.&lt;/span&gt;&lt;span&gt;addMsg&lt;/span&gt;&lt;span&gt;(from, msg, to)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.5 用户状态实时同步&lt;a href=&quot;#35-用户状态实时同步&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 监听新用户加入&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;userin&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    page.&lt;/span&gt;&lt;span&gt;addUser&lt;/span&gt;&lt;span&gt;(user)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 监听用户离开&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;userout&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    page.&lt;/span&gt;&lt;span&gt;removeUser&lt;/span&gt;&lt;span&gt;(user)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 获取完整用户列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;users&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;users&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; u&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; users) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        page.&lt;/span&gt;&lt;span&gt;addUser&lt;/span&gt;&lt;span&gt;(u)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.6 总代码&lt;a href=&quot;#36-总代码&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 连接到服务器，需要指定完整的服务器地址&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; socket&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; io.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;http://localhost:3000&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//客户端发送消息给服务器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;page.&lt;/span&gt;&lt;span&gt;onLogin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(username)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;login&apos;&lt;/span&gt;&lt;span&gt;, username)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;page.&lt;/span&gt;&lt;span&gt;onSendMsg&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;me&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(msg)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;msg&apos;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    to,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    content: msg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  page.&lt;/span&gt;&lt;span&gt;addMsg&lt;/span&gt;&lt;span&gt;(me,msg,to)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 客户端监听服务器消息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;login&quot;&lt;/span&gt;&lt;span&gt;,(&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt;(result){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    page.&lt;/span&gt;&lt;span&gt;intoChatRoom&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;users&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;用户名已被占用&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 客户端监听服务器发送的用户列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;users&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;users&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; u&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; users) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    page.&lt;/span&gt;&lt;span&gt;addUser&lt;/span&gt;&lt;span&gt;(u)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 发送消息：由 UI 回调触发&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;page.&lt;/span&gt;&lt;span&gt;onSendMsg&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;msg &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;msg.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; payload&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { from, msg, to: to &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;msg&apos;&lt;/span&gt;&lt;span&gt;, payload)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 本地先渲染自己的消息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  page.&lt;/span&gt;&lt;span&gt;addMsg&lt;/span&gt;&lt;span&gt;(from, msg, to)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  page.&lt;/span&gt;&lt;span&gt;clearInput&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 接收消息：来自其他用户或服务端转发&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;message&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 忽略自己发送且已本地渲染的消息（服务端未回发给自己则无需判断）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;from &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;msg) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt;(from &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; page.me) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  page.&lt;/span&gt;&lt;span&gt;addMsg&lt;/span&gt;&lt;span&gt;(from, msg, to)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;userin&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  page.&lt;/span&gt;&lt;span&gt;addUser&lt;/span&gt;&lt;span&gt;(user)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;userout&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  page.&lt;/span&gt;&lt;span&gt;removeUser&lt;/span&gt;&lt;span&gt;(user)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、服务端架构详解（chatSer.js）&lt;a href=&quot;#四服务端架构详解chatserjs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;4.1 模块化设计与数据结构&lt;a href=&quot;#41-模块化设计与数据结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; users&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exports&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; io&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;socket.io&apos;&lt;/span&gt;&lt;span&gt;)(server);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    io.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;connection&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;socket&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 连接处理逻辑&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 连接生命周期管理&lt;a href=&quot;#42-连接生命周期管理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;io.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;connection&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;socket&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;New connection:&apos;&lt;/span&gt;&lt;span&gt;, socket.id)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 连接建立时的初始化&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        authenticated: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        username: &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 设置超时断开&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; timeout&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; setTimeout&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;socket.data.authenticated) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            socket.&lt;/span&gt;&lt;span&gt;disconnect&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }, &lt;/span&gt;&lt;span&gt;10000&lt;/span&gt;&lt;span&gt;)  &lt;/span&gt;&lt;span&gt;// 10秒内必须完成登录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;disconnect&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        clearTimeout&lt;/span&gt;&lt;span&gt;(timeout)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 清理用户数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; idx&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; users.&lt;/span&gt;&lt;span&gt;findIndex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; u.socket.id &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; socket.id)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (idx &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;removed&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; users.&lt;/span&gt;&lt;span&gt;splice&lt;/span&gt;&lt;span&gt;(idx, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            socket.broadcast.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;userout&apos;&lt;/span&gt;&lt;span&gt;, removed.username)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;/li&gt;
&lt;li&gt;&lt;strong&gt;资源清理：&lt;/strong&gt; 确保断开时释放所有资源&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优雅断开：&lt;/strong&gt; 通知其他用户状态变化&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.3 登录验证与安全性&lt;a href=&quot;#43-登录验证与安全性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;login&quot;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 基础验证&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (data &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;所有人&apos;&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; users.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; u.username &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; data).&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;login&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 注册用户&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    users.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        username: data,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        socket&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;login&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.broadcast.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;userin&apos;&lt;/span&gt;&lt;span&gt;, data)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.4 消息路由系统的实现&lt;a href=&quot;#44-消息路由系统的实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;msg&apos;&lt;/span&gt;&lt;span&gt;,(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;from &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;msg) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;to){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 群发消息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        socket.broadcast.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;message&apos;&lt;/span&gt;&lt;span&gt;, { from, msg })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 私聊消息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; target&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; users.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; u.username &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; to)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt;(target){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        target.socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;message&apos;&lt;/span&gt;&lt;span&gt;, { from, msg, to })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;消息路由的核心概念：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;broadcast：&lt;/strong&gt; 向除发送者外的所有连接发送&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;to：&lt;/strong&gt; 向特定房间发送&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;emit：&lt;/strong&gt; 向特定 socket 发送&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.5 用户列表同步机制&lt;a href=&quot;#45-用户列表同步机制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;users&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; arr&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; users.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; u.username)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;users&apos;&lt;/span&gt;&lt;span&gt;, arr)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.6 总代码&lt;a href=&quot;#46-总代码&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; users&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exports&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; io&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;socket.io&apos;&lt;/span&gt;&lt;span&gt;)(server, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    cors: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      origin: &lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;span&gt;// 允许所有来源，包括file://&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      methods: [&lt;/span&gt;&lt;span&gt;&quot;GET&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;POST&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  io.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;connection&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;socket&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;login&quot;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      let&lt;/span&gt;&lt;span&gt; curusername &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (data &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;所有人&apos;&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; users.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; u.username &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; data).&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;login&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        users.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          username: data,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          socket&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        curusername &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;login&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        //新用户进入&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        socket.broadcast.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;userin&apos;&lt;/span&gt;&lt;span&gt;, data)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 用户&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;users&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; arr&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; users.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; u.username)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;users&apos;&lt;/span&gt;&lt;span&gt;, arr)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 消息路由&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;msg&apos;&lt;/span&gt;&lt;span&gt;,(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;from &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;msg) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;to){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        socket.broadcast.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;message&apos;&lt;/span&gt;&lt;span&gt;, { from, msg })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; target&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; users.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; u.username &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; to)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt;(target){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        target.socket.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;message&apos;&lt;/span&gt;&lt;span&gt;, { from, msg, to })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    socket.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;disconnect&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; idx&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; users.&lt;/span&gt;&lt;span&gt;findIndex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; u.socket.id &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; socket.id)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (idx &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;removed&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; users.&lt;/span&gt;&lt;span&gt;splice&lt;/span&gt;&lt;span&gt;(idx, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 通知其他客户端有人离开（如果前端监听的话）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        socket.broadcast.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;userout&apos;&lt;/span&gt;&lt;span&gt;, removed.username)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、UI 层架构设计（ui.js）&lt;a href=&quot;#五ui-层架构设计uijs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;5.1 模块化封装模式&lt;a href=&quot;#51-模块化封装模式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; page&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 私有变量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userList&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.users&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; chatList&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.chat-list&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 私有方法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    function&lt;/span&gt;&lt;span&gt; formatTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; date&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Date&lt;/span&gt;&lt;span&gt;(timestamp)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getHours&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}:${&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getMinutes&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 公开接口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        me: &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        intoChatRoom,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        addUser,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        addMsg,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;IIFE 模式的优势：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;避免全局污染：&lt;/strong&gt; 所有变量都在函数作用域内&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;信息隐藏：&lt;/strong&gt; 只暴露必要的接口&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单例模式：&lt;/strong&gt; 确保只有一个实例&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;5.2 DOM 操作优化&lt;a href=&quot;#52-dom-操作优化&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; addMsg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 创建消息元素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;li&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&amp;lt;span class=&quot;user&quot;&amp;gt;${&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;}&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;gray&quot;&amp;gt;对&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;span class=&quot;user&quot;&amp;gt;${&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; to&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &quot;所有人&quot;}&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;gray&quot;&amp;gt;说：&amp;lt;/span&amp;gt; `&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 防XSS：使用text()而非html()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; msgSpan&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;span&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(msg);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    li.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(msgSpan).&lt;/span&gt;&lt;span&gt;appendTo&lt;/span&gt;&lt;span&gt;(chatList);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 自动滚动到底部&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    chatList.&lt;/span&gt;&lt;span&gt;scrollTop&lt;/span&gt;&lt;span&gt;(chatList.&lt;/span&gt;&lt;span&gt;prop&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;scrollHeight&quot;&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 事件处理与用户交互&lt;a href=&quot;#53-事件处理与用户交互&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 事件委托处理用户列表点击&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;userList.&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (e.target.tagName &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;LI&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.sendmsg .user&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(e.target.innerText);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 回车发送消息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.sendmsg input&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;keydown&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (e.key &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;Enter&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        page.onSendMsg &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            page.&lt;/span&gt;&lt;span&gt;onSendMsg&lt;/span&gt;&lt;span&gt;(page.me, &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;(e.target).&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;(), page.&lt;/span&gt;&lt;span&gt;getTargetUser&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.4 总代码&lt;a href=&quot;#54-总代码&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; page&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; userList&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.users&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; chatList&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.chat-list&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; intoChatRoom&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.login&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;hide&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.chat&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;show&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; addUser&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;userName&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;li&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(userName).&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;user&quot;&lt;/span&gt;&lt;span&gt;, userName).&lt;/span&gt;&lt;span&gt;appendTo&lt;/span&gt;&lt;span&gt;(userList);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.user-list .title span&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.user-list .title span&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(number &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    addLog&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&amp;lt;span class=&quot;user&quot;&amp;gt;${&lt;/span&gt;&lt;span&gt;userName&lt;/span&gt;&lt;span&gt;}&amp;lt;/span&amp;gt; 进入聊天室`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    chatList.&lt;/span&gt;&lt;span&gt;scrollTop&lt;/span&gt;&lt;span&gt;(chatList.&lt;/span&gt;&lt;span&gt;prop&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;scrollHeight&quot;&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; addLog&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;li&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;addClass&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;log&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;(log).&lt;/span&gt;&lt;span&gt;appendTo&lt;/span&gt;&lt;span&gt;(chatList);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; removeUser&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;userName&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; userList.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`li[user=&quot;${&lt;/span&gt;&lt;span&gt;userName&lt;/span&gt;&lt;span&gt;}&quot;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;li.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    li.&lt;/span&gt;&lt;span&gt;remove&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.user-list .title span&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.user-list .title span&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(number &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    addLog&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&amp;lt;span class=&quot;user&quot;&amp;gt;${&lt;/span&gt;&lt;span&gt;userName&lt;/span&gt;&lt;span&gt;}&amp;lt;/span&amp;gt; 退出了聊天室`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; clearInput&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.sendmsg input&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; addMsg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;li&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      .&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&amp;lt;span class=&quot;user&quot;&amp;gt;${&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;}&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;gray&quot;&amp;gt;对&amp;lt;/span&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;span class=&quot;user&quot;&amp;gt;${&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      to&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; to&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &quot;所有人&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;gray&quot;&amp;gt;说：&amp;lt;/span&amp;gt; `&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; msgSpan&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;span&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(msg);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    li.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(msgSpan).&lt;/span&gt;&lt;span&gt;appendTo&lt;/span&gt;&lt;span&gt;(chatList);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    chatList.&lt;/span&gt;&lt;span&gt;scrollTop&lt;/span&gt;&lt;span&gt;(chatList.&lt;/span&gt;&lt;span&gt;prop&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;scrollHeight&quot;&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; getTargetUser&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; user&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.sendmsg .user&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; user &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;所有人&quot;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; user;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; initChatRoom&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    userList.&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&amp;lt;li class=&quot;all&quot;&amp;gt;所有人&amp;lt;/li&amp;gt;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.user-list .title span&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    chatList.&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&amp;lt;li class=&quot;log&quot;&amp;gt;欢迎来到渡一聊天室&amp;lt;/li&amp;gt;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  userList.&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (e.target.tagName &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;LI&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.sendmsg .user&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(e.target.innerText);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    me: &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    intoChatRoom,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    initChatRoom,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    addUser,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    addLog,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    removeUser,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    addMsg,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    clearInput,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    getTargetUser,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    onLogin: &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    onSendMsg: &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.login input&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;keydown&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (e.key &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;Enter&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      page.me &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;(e.target).&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      page.onLogin &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; page.&lt;/span&gt;&lt;span&gt;onLogin&lt;/span&gt;&lt;span&gt;(page.me);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  $&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.sendmsg input&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;keydown&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (e.key &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;Enter&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      page.onSendMsg &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        page.&lt;/span&gt;&lt;span&gt;onSendMsg&lt;/span&gt;&lt;span&gt;(page.me, &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;(e.target).&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;(), page.&lt;/span&gt;&lt;span&gt;getTargetUser&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;HTML 实现&lt;a href=&quot;#html-实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!&lt;/span&gt;&lt;span&gt;DOCTYPE&lt;/span&gt;&lt;span&gt; html&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt; lang&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;en&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;head&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt; charset&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;viewport&quot;&lt;/span&gt;&lt;span&gt; content&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;width=device-width, initial-scale=1.0&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&amp;gt;在线聊天室&amp;lt;/&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt; rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;stylesheet&quot;&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;./css/global.css&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt; rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;stylesheet&quot;&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;./css/index.css&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;head&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;login&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;form&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;请输入你的昵称，回车后进入聊天室&amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text&quot;&lt;/span&gt;&lt;span&gt; placeholder&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;请输入你的昵称&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;chat&quot;&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;display: none;&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;user-list&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;在线人数：&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;0&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;users&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;all&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;所有人&amp;lt;/&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;main&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;chat-list&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;log&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;欢迎来到聊天室&amp;lt;/&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;sendmsg&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;gray&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;对&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;user&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;所有人&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;gray&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;说：&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://cdn.bootcdn.net/ajax/libs/jquery/3.5.0/jquery.min.js&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;js/ui.js&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;!-- Socket.IO客户端库 --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://cdn.socket.io/4.5.4/socket.io.min.js&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;./js/chat.js&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>category:计算机网络</category><category>tag:网络</category><category>tag:TCP/IP</category><category>tag:HTTP</category></item><item><title>计算机网络入门</title><link>https://star-xp.top/post/network-basics</link><guid isPermaLink="false">network-basics</guid><description>深入理解计算机网络。</description><pubDate>Sun, 11 May 2025 21:44:20 GMT</pubDate><content:encoded>&lt;h2&gt;计算机的发展史&lt;a href=&quot;#计算机的发展史&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;什么是计算机&lt;a href=&quot;#什么是计算机&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;计算机俗称&lt;strong&gt;电脑&lt;/strong&gt;，是一种用于高速计算的电子计算机器，可以进行数值计算，又可以进行逻辑计算，具备存储记忆的功能，是可以按照程序进行，自动，高速进行海量数据的现代化智能电子设备。&lt;/p&gt;
&lt;h3&gt;计算机的分类&lt;a href=&quot;#计算机的分类&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;​编辑&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;超级计算机&lt;/strong&gt;：气象预测、核能模拟、基因分析、人工智能训练等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大型计算机&lt;/strong&gt;：银行交易处理、政府数据中心、航空订票系统等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务器&lt;/strong&gt;：为其他设备（客户端）提供网络服务（如数据存储、网站托管）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;小型机&lt;/strong&gt;：企业资源管理（ERP）、科学计算等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工作站&lt;/strong&gt;：高性能个人计算机，专为专业领域设计（如图形渲染、工程设计）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;个人计算机&lt;/strong&gt;：面向个人用户，包括台式机、笔记本电脑、平板电脑等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;嵌入式计算机&lt;/strong&gt;：集成在设备内部，执行特定功能（如智能家电、汽车控制系统、医疗设备）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;计算机的发展历史&lt;a href=&quot;#计算机的发展历史&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;早期计算设备（19 世纪 - 20 世纪初）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;机械计算机&lt;/strong&gt;：“在 19 世纪，像巴贝奇的差分机这样的机械计算机，是那个时代对复杂计算需求的回应，虽然它们很笨重，但奠定了自动计算的基础。”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;个人观点&lt;/strong&gt;：机械计算机虽然受限于物理磨损和制造工艺，但其“程序控制”的思想是现代计算机的灵魂。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;电子计算机的诞生（20 世纪中期）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;电子管计算机（如 ENIAC）&lt;/strong&gt;：“二战期间和战后，电子管计算机的出现，如 ENIAC，标志着计算速度的巨大飞跃，从机械时代的缓慢计算转变为电子速度。”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;晶体管计算机&lt;/strong&gt;：“晶体管的发明极大地提高了计算机的可靠性和效率，缩小了体积，为商业化铺平了道路。”&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;集成电路与个人计算机革命（20 世纪后期）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;集成电路的发展&lt;/strong&gt;：摩尔定律驱动了芯片性能的指数级增长。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;个人计算机的兴起&lt;/strong&gt;：Apple II 和 IBM PC 让计算力走进家庭。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;互联网与现代计算（21 世纪初至今）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;互联网的普及&lt;/strong&gt;：全球化数字经济的诞生。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;移动计算和人工智能&lt;/strong&gt;：智能手机与深度学习改变了各行各业。
​编辑&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;计算机的大脑：CPU&lt;a href=&quot;#计算机的大脑cpu&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;什么是 CPU&lt;a href=&quot;#什么是-cpu&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;CPU（Central Processing Unit），即&lt;strong&gt;中央处理器&lt;/strong&gt;，是计算机的大脑，负责执行指令和处理数据。&lt;/p&gt;
&lt;h3&gt;指令获取与执行&lt;a href=&quot;#指令获取与执行&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;工作过程&lt;/strong&gt;：读取指令 -&amp;gt; 解码指令 -&amp;gt; 执行操作 -&amp;gt; 结果输出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;个人理解&lt;/strong&gt;：CPU 的流程就像解决问题：先理解问题（解码），再思考方案（执行），最后得出答案（输出）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;主要功能单元&lt;a href=&quot;#主要功能单元&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;算术逻辑单元（ALU）&lt;/strong&gt;：CPU 的心脏，负责所有的算术（加减乘除）和逻辑（判断）运算。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;控制单元（CU）&lt;/strong&gt;：CPU 的指挥部，协调从内存取指令、解码及控制其他部件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;寄存器&lt;/strong&gt;：CPU 内部的高速存储单元，相当于“临时记忆”，速度远超内存。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;性能指标&lt;a href=&quot;#性能指标&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主频&lt;/strong&gt;：工作频率（GHz），频率越高，单位时间执行指令越多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;核心数量&lt;/strong&gt;：多核设计就像多条生产线，提高多任务处理能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存 (Cache)&lt;/strong&gt;：位于 CPU 与内存之间，减少 CPU 访问慢速内存的次数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;架构和指令集&lt;/strong&gt;：决定了 CPU 的“语言”和执行效率（如 x86, ARM）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;CPU 的分类&lt;a href=&quot;#cpu-的分类&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Intel&lt;/strong&gt;：以稳定著称，Core（酷睿）系列面向大众，Xeon（至强）系列面向服务器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AMD&lt;/strong&gt;：以高性价比和多核性能著称，Ryzen（锐龙）系列深受玩家喜爱。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ARM&lt;/strong&gt;：低功耗架构，广泛用于手机（苹果 A 系列、骁龙）和嵌入式设备。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;小贴士&lt;/strong&gt;：一般办公选 Intel，游戏/生产力追求极致可选 AMD，但这没有绝对界限，看个人需求。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;选购与故障排除&lt;a href=&quot;#选购与故障排除&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;指标&lt;/strong&gt;：看主频、缓存、核数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;常见故障&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;散热不良&lt;/strong&gt;：死机、蓝屏。需清洁风扇或重涂硅脂。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;超频问题&lt;/strong&gt;：自检不通过。需恢复 BIOS 默认频率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;针脚接触不良&lt;/strong&gt;：无法启动。检查插槽是否有异物或针脚弯曲。
​编辑&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;计算机的工作台：内存 (Memory)&lt;a href=&quot;#计算机的工作台内存-memory&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;内存简介与原理&lt;a href=&quot;#内存简介与原理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;内存是用于暂时存储 CPU 正在使用的数据和程序，以供快速访问。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理&lt;/strong&gt;：像是有许多“小抽屉”的柜子，每个抽屉有唯一编号（内存地址）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;易失性&lt;/strong&gt;：断电后数据会消失，所以需要硬盘进行长期存储。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;内存的分类&lt;a href=&quot;#内存的分类&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;按功能&lt;/strong&gt;：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;RAM（随机存取存储器）&lt;/strong&gt;：可读写，如 DDR4、DDR5。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ROM（只读存储器）&lt;/strong&gt;：固件存储，通常不可随意修改。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;按技术&lt;/strong&gt;：目前主流是 &lt;strong&gt;DDR5&lt;/strong&gt;，传输速率远超前代。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;选购与维护&lt;a href=&quot;#选购与维护&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;指南&lt;/strong&gt;：关注主板兼容性（插槽类型）、容量（8G 办公，16G+ 游戏）、频率和时序（CL 值）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;故障解决&lt;/strong&gt;：&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无法开机&lt;/strong&gt;：尝试重新插拔，用橡皮擦擦拭“金手指”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;蓝屏&lt;/strong&gt;：使用系统自带的内存诊断工具检查。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;计算机的仓库：硬盘 (Disk)&lt;a href=&quot;#计算机的仓库硬盘-disk&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;硬盘种类对比&lt;a href=&quot;#硬盘种类对比&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;机械硬盘 (HDD)&lt;/strong&gt;：盘片旋转读写。容量大、便宜，适合存电影/文档。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;固态硬盘 (SSD)&lt;/strong&gt;：闪存芯片存储。速度极快（秒开机）、抗震，适合装系统和软件。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;常见故障原因&lt;a href=&quot;#常见故障原因&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;系统不认硬盘&lt;/strong&gt;：数据线松动或 BIOS 跳线设置错误。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;系统无法启动&lt;/strong&gt;：引导文件丢失（可用 PE 系统修复）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬盘坏道&lt;/strong&gt;：物理坏道建议送修，逻辑坏道可用软件修复。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;计算机的骨架：主板 (Motherboard)&lt;a href=&quot;#计算机的骨架主板-motherboard&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;主板的作用&lt;a href=&quot;#主板的作用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;主板连接并支撑所有硬件，负责数据流动的协调。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心组件&lt;/strong&gt;：芯片组、BIOS、CPU 插槽、内存插槽、PCIe 插槽。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;选购要点&lt;a href=&quot;#选购要点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;板型&lt;/strong&gt;：ATX（大板）、Micro-ATX（中板）、Mini-ITX（小板）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;品牌&lt;/strong&gt;：华硕（ASUS）、技嘉（Gigabyte）、微星（MSI）是御三家。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扩展性&lt;/strong&gt;：USB 接口数量、是否自带 Wi-Fi/蓝牙。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;计算机的视觉神经：显卡 (GPU)&lt;a href=&quot;#计算机的视觉神经显卡-gpu&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;显卡简介&lt;a href=&quot;#显卡简介&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;显卡负责将数据转换为显示器能识别的图像信号。在现代前端开发中，GPU 也常用于网页的硬件加速渲染。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心任务&lt;/strong&gt;：图像生成、AI 计算、视频解码。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;显卡接口演进&lt;a href=&quot;#显卡接口演进&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;ISA -&amp;gt; 2. PCI -&amp;gt; 3. AGP -&amp;gt; &lt;strong&gt;4. PCI-E&lt;/strong&gt; (当前主流)&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>category:计算机网络</category><category>tag:网络</category><category>tag:TCP/IP</category></item><item><title>Vue 详细讲解</title><link>https://star-xp.top/post/vue-basics</link><guid isPermaLink="false">vue-basics</guid><description>全量合并 JavaScript 与 Vue2 进阶笔记，严格保留所有原始代码、注释、心得及编辑占位符。</description><pubDate>Sat, 25 Jan 2025 15:25:22 GMT</pubDate><content:encoded>&lt;h2&gt;第一部分：JavaScript 基础&lt;a href=&quot;#第一部分javascript-基础&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;js 的三种书写方式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;行内：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; onclick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;alert&lt;/span&gt;&lt;span&gt;(&apos;行内&apos;)&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;内部：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;内部&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;外部：
外部 js 标签中间不能书写东西&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;./01.js.html&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;js 的输入输出：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入语法：
页面打印：&lt;code&gt;document.write(&apos;页面打印&apos;)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;​编辑​&lt;/p&gt;
&lt;p&gt;控制台打印：&lt;code&gt;console.log(&apos;控制台打印&apos;)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;​编辑​&lt;/p&gt;
&lt;p&gt;弹出界面：&lt;code&gt;console.log(&apos;控制台打印&apos;)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;​编辑​&lt;/p&gt;
&lt;p&gt;输入语法：&lt;code&gt;prompt(&apos;请输入信息&apos;)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;​编辑​&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注释：&lt;/strong&gt;
单行注释：&lt;code&gt;// 我是单行注释&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;多行注释：&lt;code&gt;/* 我是多行注释 */&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;变量：&lt;/strong&gt;
变量是用来储存数据的容器，简单理解就是一个盒子。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 声明并赋值一个年龄变量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; age &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 18&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 输出&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;alert&lt;/span&gt;&lt;span&gt;(age);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 声明一个年龄变量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; age;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 赋值&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;age &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 18&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 输出&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;alert&lt;/span&gt;&lt;span&gt;(age);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;数组：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;定义数组：
&lt;code&gt;let arr = [1, 2, 3, 4, 5]; console.log(arr); // 输出 [1, 2, 3, 4, 5]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;数组的增删改查：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;增加元素：&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;push()&lt;/code&gt; 向数组的末尾添加一个或多个元素。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;unshift()&lt;/code&gt; 向数组的开头添加一个或多个元素。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;splice()&lt;/code&gt; 可以在任意位置插入元素。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;删除元素：&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pop()&lt;/code&gt; 从数组末尾移除并返回。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;shift()&lt;/code&gt; 从数组开头移除并返回。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;splice()&lt;/code&gt; 从指定位置删除元素。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改元素：&lt;/strong&gt;
直接通过索引修改：&lt;code&gt;arr[1] = 2.5;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;查询元素：&lt;/strong&gt;
&lt;code&gt;indexOf(元素)&lt;/code&gt; 返回索引，不存在返回 -1。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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;number&lt;/strong&gt;: 数值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;string&lt;/strong&gt;: 字符串。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;boolean&lt;/strong&gt;: 布尔值（true/false）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;undefined&lt;/strong&gt;: 未定义。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;null&lt;/strong&gt;: 空值。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;引用数据类型（堆内存）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;对象 (Object)&lt;/strong&gt;: 键值对集合。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数组 (Array)&lt;/strong&gt;: 有序元素列表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;函数 (Function)&lt;/strong&gt;: 可执行的代码块。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;数据转换：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;隐式转换：&lt;/strong&gt; &lt;code&gt;11 + &apos;11&apos;&lt;/code&gt; 变字符串，&lt;code&gt;+&apos;123&apos;&lt;/code&gt; 变数字。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;显式转换：&lt;/strong&gt; &lt;code&gt;Number()&lt;/code&gt;、&lt;code&gt;String()&lt;/code&gt;、&lt;code&gt;Boolean()&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;运算符：&lt;/strong&gt;
包括算数运算符（&lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt;, &lt;code&gt;**&lt;/code&gt;）、比较运算符（&lt;code&gt;==&lt;/code&gt;, &lt;code&gt;===&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt;, &lt;code&gt;!==&lt;/code&gt;）和逻辑运算符（&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;||&lt;/code&gt;, &lt;code&gt;!&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;函数：&lt;/strong&gt;
包含函数声明、函数表达式、立即执行函数（IIFE）以及箭头函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对象：&lt;/strong&gt;
创建对象可用字面量 &lt;code&gt;{}&lt;/code&gt; 或 &lt;code&gt;new Object()&lt;/code&gt;。支持属性的增（&lt;code&gt;obj.prop = val&lt;/code&gt;）、删（&lt;code&gt;delete obj.prop&lt;/code&gt;）、改、查（&lt;code&gt;Object.keys/values&lt;/code&gt;）及 &lt;code&gt;for...in&lt;/code&gt; 遍历。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;第二部分：JavaScript 进阶 (DOM/BOM)&lt;a href=&quot;#第二部分javascript-进阶-dombom&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;DOM (Document Object Model)：&lt;/strong&gt;
DOM 将文档视为树形结构。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;节点类型：&lt;/strong&gt; 元素节点、属性节点、文本节点、注释节点、文档节点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;获取节点：&lt;/strong&gt; &lt;code&gt;getElementById&lt;/code&gt;、&lt;code&gt;getElementsByClassName&lt;/code&gt;、&lt;code&gt;querySelector&lt;/code&gt;、&lt;code&gt;querySelectorAll&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自定义属性：&lt;/strong&gt; &lt;code&gt;data-*&lt;/code&gt; 属性，通过 &lt;code&gt;element.dataset&lt;/code&gt; 访问。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;操作 Class：&lt;/strong&gt;
使用 &lt;code&gt;classList&lt;/code&gt; 方法：&lt;code&gt;add()&lt;/code&gt;、&lt;code&gt;remove()&lt;/code&gt;、&lt;code&gt;toggle()&lt;/code&gt;、&lt;code&gt;contains()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定时器：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;setTimeout&lt;/code&gt; / &lt;code&gt;clearTimeout&lt;/code&gt;: 延时执行一次。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setInterval&lt;/code&gt; / &lt;code&gt;clearInterval&lt;/code&gt;: 每隔一段时间执行。&lt;/li&gt;
&lt;/ul&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;click&lt;/code&gt;, &lt;code&gt;mouseenter&lt;/code&gt;, &lt;code&gt;mouseleave&lt;/code&gt; (推荐，无冒泡)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;键盘：&lt;/strong&gt; &lt;code&gt;keydown&lt;/code&gt;, &lt;code&gt;keyup&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;表单：&lt;/strong&gt; &lt;code&gt;submit&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;change&lt;/code&gt;, &lt;code&gt;focus&lt;/code&gt;, &lt;code&gt;blur&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;事件流：&lt;/strong&gt;
经历捕获阶段、目标阶段、冒泡阶段。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阻止冒泡：&lt;code&gt;e.stopPropagation()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;阻止默认行为：&lt;code&gt;e.preventDefault()&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;页面尺寸与位置：&lt;/strong&gt;
&lt;code&gt;scroll&lt;/code&gt; (滚动), &lt;code&gt;offset&lt;/code&gt; (偏移), &lt;code&gt;client&lt;/code&gt; (视口)。
​编辑&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;日期与时间：&lt;/strong&gt;
&lt;code&gt;new Date()&lt;/code&gt; 获取当前时间，支持获取年、月、日等。时间戳获取：&lt;code&gt;Date.now()&lt;/code&gt; 或 &lt;code&gt;+new Date()&lt;/code&gt;。
​编辑&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BOM (Browser Object Model)：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;location&lt;/code&gt;: 地址栏操作（&lt;code&gt;href&lt;/code&gt;, &lt;code&gt;reload&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;history&lt;/code&gt;: 历史记录（&lt;code&gt;back&lt;/code&gt;, &lt;code&gt;forward&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;localStorage&lt;/code&gt;: 本地存储，持久化。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;第三部分：Vue2 进阶篇&lt;a href=&quot;#第三部分vue2-进阶篇&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;组件基础：&lt;/strong&gt;
​编辑
组件由 &lt;code&gt;template&lt;/code&gt; (结构)、&lt;code&gt;style&lt;/code&gt; (样式)、&lt;code&gt;script&lt;/code&gt; (逻辑) 组成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scoped:&lt;/strong&gt; &lt;code&gt;&amp;lt;style scoped&amp;gt;&lt;/code&gt; 确保样式只作用于当前组件，防止污染。Vue 会通过 &lt;code&gt;data-v-xxxx&lt;/code&gt; 属性来实现样式隔离。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data:&lt;/strong&gt; 在组件中 &lt;code&gt;data&lt;/code&gt; 必须是一个函数并 &lt;code&gt;return&lt;/code&gt; 一个对象，确保每个实例维护独立的数据拷贝。
​编辑&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;组件通信：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Props (父传子):&lt;/strong&gt; 父组件绑定属性，子组件通过 &lt;code&gt;props&lt;/code&gt; 接收（支持数组、对象、校验）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$emit (子传父):&lt;/strong&gt; 子组件通过 &lt;code&gt;this.$emit(&apos;event&apos;, data)\&lt;/code&gt; 触发，父组件监听该事件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EventBus:&lt;/strong&gt; 建立一个中央事件总线，实现非父子组件通信。
​编辑&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;v-model 原理：&lt;/strong&gt;
&lt;code&gt;v-model&lt;/code&gt; 是 &lt;code&gt;v-bind:value&lt;/code&gt; 和 &lt;code&gt;v-on:input&lt;/code&gt; 的语法糖。
​编辑&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;.sync 修饰符：&lt;/strong&gt;
实现父子组件数据的“双向绑定”，本质是 &lt;code&gt;update:prop&lt;/code&gt; 事件的简写。
​编辑&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Computed (计算属性) vs Watch (侦听器)：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Computed:&lt;/strong&gt; 具有缓存性，依赖项不变不重新计算。支持 &lt;code&gt;getter&lt;/code&gt; 和 &lt;code&gt;setter&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Watch:&lt;/strong&gt; 监听数据变化执行回调，适合异步或开销较大的操作。支持 &lt;code&gt;deep: true&lt;/code&gt; 深度监听。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Vuex (Store) 核心：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;State:&lt;/strong&gt; 存放共享数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mutations:&lt;/strong&gt; 同步修改 State 的唯一方式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actions:&lt;/strong&gt; 处理异步逻辑，提交 Mutation。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Getters:&lt;/strong&gt; 类似计算属性，对 State 进行加工。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;插槽 (Slot)：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基本插槽:&lt;/strong&gt; &lt;code&gt;&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;具名插槽:&lt;/strong&gt; &lt;code&gt;&amp;lt;slot name=&quot;header&quot;&amp;gt;&amp;lt;/slot&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用域插槽:&lt;/strong&gt; 子组件传数据给父组件展示。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;路由 (Vue Router)：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;router-link&lt;/code&gt;: 导航链接。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;router-view&lt;/code&gt;: 路由组件显示出口。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;params / query&lt;/code&gt;: 路由传参。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重定向与守卫:&lt;/strong&gt; &lt;code&gt;redirect&lt;/code&gt; 配置和 &lt;code&gt;beforeEach&lt;/code&gt; 全局守卫。
​编辑&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;自定义指令：&lt;/strong&gt;
使用 &lt;code&gt;Vue.directive&lt;/code&gt; 或组件内 &lt;code&gt;directives&lt;/code&gt; 定义。常用于操作 DOM（如 &lt;code&gt;v-focus&lt;/code&gt;）。&lt;/p&gt;</content:encoded><category>category:前端</category><category>category:Vue</category><category>tag:JavaScript</category><category>tag:Vue2</category><category>tag:前端开发</category></item><item><title>Python 基础</title><link>https://star-xp.top/post/python-basics</link><guid isPermaLink="false">python-basics</guid><description>Python 语言的基础语法和常用特性介绍。</description><pubDate>Sun, 05 Jan 2025 16:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;环境与基础概念&lt;a href=&quot;#环境与基础概念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Python 是一种&lt;strong&gt;解释型&lt;/strong&gt;、&lt;strong&gt;面向对象&lt;/strong&gt;、&lt;strong&gt;动态数据类型&lt;/strong&gt;的高级编程语言。&lt;/p&gt;
&lt;h3&gt;变量与命名&lt;a href=&quot;#变量与命名&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python 中的变量不需要声明类型，直接赋值即可使用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;命名规则&lt;/strong&gt;：由字母、数字和下划线组成，不能以数字开头。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;风格习惯&lt;/strong&gt;：推荐使用蛇形命名法（snake_case），如 &lt;code&gt;user_name&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;标准输入输出&lt;a href=&quot;#标准输入输出&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Hello Python&quot;&lt;/span&gt;&lt;span&gt;)           &lt;/span&gt;&lt;span&gt;# 输出&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; input&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;请输入你的名字: &quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;# 输入（结果始终为字符串）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;数据类型&lt;a href=&quot;#数据类型&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Python 拥有丰富且灵活的数据类型：&lt;/p&gt;
&lt;h3&gt;基础类型&lt;a href=&quot;#基础类型&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数字&lt;/strong&gt;：整数 (&lt;code&gt;int&lt;/code&gt;)、浮点数 (&lt;code&gt;float&lt;/code&gt;)、复数 (&lt;code&gt;complex&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;布尔&lt;/strong&gt;：&lt;code&gt;True&lt;/code&gt; 和 &lt;code&gt;False&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;容器类型&lt;a href=&quot;#容器类型&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&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;示例&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;列表 (List)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;有序、可变、允许重复&lt;/td&gt;&lt;td&gt;&lt;code&gt;fruits = [&quot;apple&quot;, &quot;banana&quot;]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;元组 (Tuple)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;有序、&lt;strong&gt;不可变&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;coords = (10, 20)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;字典 (Dict)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;键值对、无序(3.7 后有序)&lt;/td&gt;&lt;td&gt;&lt;code&gt;user = {&quot;name&quot;: &quot;Gemini&quot;, &quot;age&quot;: 1}&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;集合 (Set)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;无序、&lt;strong&gt;元素唯一&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;unique_ids = {1, 2, 3}&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;控制流程&lt;a href=&quot;#控制流程&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;条件判断&lt;a href=&quot;#条件判断&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python 使用&lt;strong&gt;缩进&lt;/strong&gt;来表示代码块，这在其他语言中通常使用花括号 &lt;code&gt;{}&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;score &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 85&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; score &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; 90&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;优秀&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;elif&lt;/span&gt;&lt;span&gt; score &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; 60&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;及格&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;不及格&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;循环结构&lt;a href=&quot;#循环结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;for 循环&lt;/strong&gt;：遍历序列。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;while 循环&lt;/strong&gt;：条件满足时重复执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 遍历 0 到 4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(i)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 遍历列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; item &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;A&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;B&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;C&quot;&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(item)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;函数与模块&lt;a href=&quot;#函数与模块&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;定义函数&lt;a href=&quot;#定义函数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;def&lt;/code&gt; 关键字定义函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; add_numbers&lt;/span&gt;&lt;span&gt;(a, b&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;):  &lt;/span&gt;&lt;span&gt;# b 为默认参数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;&quot;&quot;计算两数之和&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; a &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; add_numbers(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;)    &lt;/span&gt;&lt;span&gt;# 返回 15&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;模块导入&lt;a href=&quot;#模块导入&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python 的强大之处在于其丰富的库。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; math&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(math.sqrt(&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;))      &lt;/span&gt;&lt;span&gt;# 输出 4.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; datetime &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; datetime&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(datetime.now())     &lt;/span&gt;&lt;span&gt;# 输出当前时间&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;异常处理与文件操作&lt;a href=&quot;#异常处理与文件操作&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;异常处理&lt;a href=&quot;#异常处理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;防止程序因为小错误直接崩溃。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    num &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; ZeroDivisionError&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;错误：除数不能为零&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;操作完成&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;文件读写&lt;a href=&quot;#文件读写&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;with&lt;/code&gt; 语句可以自动关闭文件，防止内存泄露。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 写入文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt; open&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;test.txt&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;w&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; f:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    f.write(&lt;/span&gt;&lt;span&gt;&quot;Python 基础学习&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 读取文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt; open&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;test.txt&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;r&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; f:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    content &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; f.read()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(content)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;学习路线建议&lt;a href=&quot;#学习路线建议&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基础阶段&lt;/strong&gt;：掌握上述语法、列表推导式、装饰器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进阶阶段&lt;/strong&gt;：面向对象编程 (OOP)、正则表达、多线程/多进程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用阶段&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Web 开发&lt;/strong&gt;：Django / Flask。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据分析&lt;/strong&gt;：Pandas / Numpy / Matplotlib。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;爬虫&lt;/strong&gt;：Requests / Scrapy。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;人工智能&lt;/strong&gt;：PyTorch / TensorFlow。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;</content:encoded><category>category:后端</category><category>category:Python</category><category>tag:Python</category></item></channel></rss>