<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Qiang</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://github.com/wz71014q/</id>
  <link href="https://github.com/wz71014q/" rel="alternate"/>
  <link href="https://github.com/wz71014q/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Qiang</rights>
  <title>βloS</title>
  <updated>2026-05-22T05:52:31.558Z</updated>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="AI时代前端工程师实战指南" scheme="https://github.com/wz71014q/categories/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="AI" scheme="https://github.com/wz71014q/tags/AI/"/>
    <category term="前端工程师" scheme="https://github.com/wz71014q/tags/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88/"/>
    <category term="实战指南" scheme="https://github.com/wz71014q/tags/%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="提效" scheme="https://github.com/wz71014q/tags/%E6%8F%90%E6%95%88/"/>
    <category term="SDD" scheme="https://github.com/wz71014q/tags/SDD/"/>
    <category term="TDD" scheme="https://github.com/wz71014q/tags/TDD/"/>
    <content>
      <![CDATA[<blockquote><p><strong>本章目的</strong>：从需求分析到上线运维，覆盖整个软件开发生命周期（SDLC）。但本文不是给你一堆 Prompt 模板——而是教你<strong>把每个研发环节封装成一个 Skill（SKILL.md），然后用一个 Workflow（AGENTS.md）让 AI Agent 按流水线自动执行</strong>。Agent 自行决策每一步用什么工具和 MCP，你只需要在关键 Gate 做确认。</p></blockquote><hr><h2 id="2-1-开发生命周期全景"><a href="#2-1-开发生命周期全景" class="headerlink" title="2.1 开发生命周期全景"></a>2.1 开发生命周期全景</h2><p>传统开发流程中，AI 往往只被用在”写代码”这个环节。但事实上，<strong>每个环节都可以 Skill 化</strong>。一旦被 Skill 化，Agent 就能自动识别当前处于什么阶段、该做什么事、用什么工具。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐</span><br><span class="line">│ 需求分析      │ → │ 技术设计      │ → │ 编码实现      │ → │  测试        │ → │ Code         │ → │ 部署运维      │</span><br><span class="line">│ Skill        │    │ Skill        │    │ Skill        │    │ Skill        │    │ Review Skill │    │ Skill        │</span><br><span class="line">│              │    │              │    │              │    │              │    │              │    │              │</span><br><span class="line">│ requirement- │    │ tech-design  │    │ coding       │    │ test-        │    │ code-review  │    │ deploy-ops   │</span><br><span class="line">│ analysis     │    │              │    │              │    │ generation   │    │              │    │              │</span><br><span class="line">└──────┬───────┘    └──────┬───────┘    └──────┬───────┘    └──────┬───────┘    └──────┬───────┘    └──────┬───────┘</span><br><span class="line">       │                   │                   │                   │                   │                   │</span><br><span class="line">       ▼                   ▼                   ▼                   ▼                   ▼                   ▼</span><br><span class="line">  产出：功能点清单      产出：方案文档       产出：实现代码       产出：测试文件      产出：Review 结论   产出：部署完成</span><br><span class="line">  验收标准             API 设计            + 测试通过          + 覆盖率报告       + MR 合并          + 监控告警</span><br><span class="line">  模糊点清单           组件树</span><br></pre></td></tr></table></figure><p>不再是你跑过去问 Agent”帮我分析这个需求”——Agent 看到 JIRA Ticket 后，自己就会加载 <code>requirement-analysis</code> Skill，按 Skill 里的步骤执行。</p><hr><h2 id="2-2-环节一：需求分析-Skill"><a href="#2-2-环节一：需求分析-Skill" class="headerlink" title="2.2 环节一：需求分析 Skill"></a>2.2 环节一：需求分析 Skill</h2><h3 id="传统痛点"><a href="#传统痛点" class="headerlink" title="传统痛点"></a>传统痛点</h3><ul><li>需求文档含糊不清，开发过程中才发现遗漏</li><li>沟通成本高：产品和开发的认知偏差</li><li>验收标准不明确，上线后才发现理解错误</li></ul><h3 id="Skill-化方案"><a href="#Skill-化方案" class="headerlink" title="Skill 化方案"></a>Skill 化方案</h3><p>不再给 Prompt 模板，而是把”需求分析”这个能力固化成一个 Skill。Agent 加载后，自己决定怎么读文件、查 JIRA、还是搜索知识库。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"># 文件名：.opencode/skills/requirement-analysis/SKILL.md</span><br><span class="line"></span><br><span class="line">## 适用场景</span><br><span class="line">收到产品需求（文字/文档/JIRA Ticket），需要拆解为可执行的功能点</span><br><span class="line"></span><br><span class="line">## 输入</span><br><span class="line">- 需求原文（从 JIRA / 文档 / 用户输入获取）</span><br><span class="line">- 项目背景信息（从 CLAUDE.md / AGENTS.md 获取）</span><br><span class="line"></span><br><span class="line">## 输出</span><br><span class="line">- 功能点清单（按 P0/P1/P2 优先级排序）</span><br><span class="line">- 每个功能点的验收标准（Given/When/Then 格式）</span><br><span class="line">- 待确认问题列表（需求中的模糊点）</span><br><span class="line">- 技术可行性评估（风险 / 依赖 / 复杂度）</span><br><span class="line"></span><br><span class="line">## 执行步骤</span><br><span class="line">1. 读取需求输入（从文件、JIRA API、或用户消息中提取）</span><br><span class="line">2. 按 MVP 优先级将需求拆解为独立功能点</span><br><span class="line">3. 为每个功能点编写可测试的验收标准</span><br><span class="line">4. 识别需求中的模糊点和不一致之处，形成待确认问题列表</span><br><span class="line">5. 评估每个功能点的技术可行性和实现复杂度</span><br><span class="line">6. 输出结构化文档到 `.opencode/artifacts/requirement-analysis.md`</span><br><span class="line">7. 等待人工确认后进入下一环节</span><br><span class="line"></span><br><span class="line">## 约束</span><br><span class="line">- MUST: 每个功能点必须有可测试的验收标准</span><br><span class="line">- MUST: 必须区分 P0（MVP必需）/ P1（重要）/ P2（锦上添花）</span><br><span class="line">- MUST: 必须列出至少 3 个待确认问题</span><br><span class="line">- MUST NOT: 不要假设需求中没有提到的技术方案</span><br><span class="line">- MUST NOT: 跳过模糊点标注</span><br><span class="line">- MUST NOT: 直接开始编码（需求分析阶段不产生代码）</span><br></pre></td></tr></table></figure><h3 id="设计思路"><a href="#设计思路" class="headerlink" title="设计思路"></a>设计思路</h3><p>这个 Skill 的巧妙之处在于——它<strong>不指定具体用什么工具</strong>。Agent 可以根据环境自主选择：</p><ul><li>如果项目绑定了 JIRA MCP，Agent 会直接调用 <code>jira_getIssue</code> 拉取 Ticket</li><li>如果需求写在本地文档里，Agent 会自动 <code>Read</code> 对应文件</li><li>如果没有外部源，Agent 会提示你粘贴需求文本</li></ul><p><strong>你只需要写一次 Skill，以后每个需求分析 Agent 都会按这个 SOP 执行。</strong></p><h3 id="与直接给-Prompt-的区别"><a href="#与直接给-Prompt-的区别" class="headerlink" title="与直接给 Prompt 的区别"></a>与直接给 Prompt 的区别</h3><table><thead><tr><th>对比维度</th><th>传统 Prompt 方式</th><th>Skill 化方式</th></tr></thead><tbody><tr><td>复用性</td><td>每次要 CP 粘贴</td><td>写在 SKILL.md 里自动加载</td></tr><tr><td>一致性</td><td>不同人&#x2F;次 Prompt 不同</td><td>所有 Agent 按相同步骤执行</td></tr><tr><td>工具决策</td><td>你告诉 AI 用什么工具</td><td>Agent 自己选（JIRA MCP &#x2F; 读文件 &#x2F; …）</td></tr><tr><td>输出规約</td><td>每次格式不统一</td><td>固定输出格式，下一环节可直接消费</td></tr><tr><td>人工 Gate</td><td>靠自觉</td><td>Skill 执行步骤明确要求等待确认</td></tr></tbody></table><hr><h2 id="2-3-环节二：技术设计-Skill"><a href="#2-3-环节二：技术设计-Skill" class="headerlink" title="2.3 环节二：技术设计 Skill"></a>2.3 环节二：技术设计 Skill</h2><h3 id="传统痛点-1"><a href="#传统痛点-1" class="headerlink" title="传统痛点"></a>传统痛点</h3><ul><li>方案选型凭经验，容易遗漏替代方案</li><li>设计文档写起来痛苦，经常滞后甚至缺失</li><li>架构决策的 trade-off 分析不充分</li></ul><h3 id="Skill-化方案-1"><a href="#Skill-化方案-1" class="headerlink" title="Skill 化方案"></a>Skill 化方案</h3><p>需求分析确认后，Agent 自动加载 <code>tech-design</code> Skill。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"># 文件名：.opencode/skills/tech-design/SKILL.md</span><br><span class="line"></span><br><span class="line">## 适用场景</span><br><span class="line">功能点已确定，需要进行技术方案设计、组件架构设计、API 接口定义</span><br><span class="line"></span><br><span class="line">## 输入</span><br><span class="line">- `.opencode/artifacts/requirement-analysis.md`（需求分析产出）</span><br><span class="line">- 项目技术栈信息（从 CLAUDE.md 读取）</span><br><span class="line"></span><br><span class="line">## 输出</span><br><span class="line">- 技术方案对比文档（2-3 个候选方案）</span><br><span class="line">- 推荐方案及理由</span><br><span class="line">- 组件树 / 数据结构 / API 接口设计</span><br><span class="line">- 风险点和应对策略</span><br><span class="line"></span><br><span class="line">## 执行步骤</span><br><span class="line">1. 读取需求分析阶段的产出物</span><br><span class="line">2. 分析技术约束（现有技术栈、团队能力、时间限制）</span><br><span class="line">3. 生成 2-3 个可行的技术方案，列出每个方案的优缺点</span><br><span class="line">4. 输出推荐方案及关键技术决策的 trade-off 分析</span><br><span class="line">5. 设计组件树结构、Props/Emits 接口、数据流</span><br><span class="line">6. 定义 API 接口格式（请求/响应类型）</span><br><span class="line">7. 如果涉及架构变更，生成 Mermaid 架构图</span><br><span class="line">8. 输出设计文档到 `.opencode/artifacts/tech-design.md`</span><br><span class="line">9. 等待人工确认后进入编码阶段</span><br><span class="line"></span><br><span class="line">## 约束</span><br><span class="line">- MUST: 至少对比 2 个方案</span><br><span class="line">- MUST: 明确标注每个方案的 trade-off（不是只说优点）</span><br><span class="line">- MUST: 定义完整的 TypeScript 类型接口</span><br><span class="line">- MUST NOT: 跳过已有系统的兼容性分析</span><br><span class="line">- MUST NOT: 直接输出实现代码（设计阶段不编码）</span><br></pre></td></tr></table></figure><h3 id="关键要点"><a href="#关键要点" class="headerlink" title="关键要点"></a>关键要点</h3><ul><li>技术设计 Skill 的输入明确依赖 <code>requirement-analysis</code> 的产出——<strong>这是 Skill 串联的关键</strong>。每个 Skill 的输入&#x2F;输出定义清晰，下一个 Skill 才知道从哪里拿数据。</li><li>Agent 在加载这个 Skill 后，<strong>不需要你手动粘贴需求文档</strong>。它会自动读取 <code>requirement-analysis</code> 阶段写入的文件。</li></ul><hr><h2 id="2-4-环节三：编码实现-Skill"><a href="#2-4-环节三：编码实现-Skill" class="headerlink" title="2.4 环节三：编码实现 Skill"></a>2.4 环节三：编码实现 Skill</h2><p>这是 AI 最强也最常用的环节。但问题在于，大多数人直接把编码 Skill 当”万能打字机”用——输入模糊的需求，输出不可靠的代码。</p><p><strong>Skill 化的核心思路</strong>：编码 Skill 不直接对接原始需求，而是消费需求分析和设计阶段的结构化产出。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"># 文件名：.opencode/skills/coding/SKILL.md</span><br><span class="line"></span><br><span class="line">## 适用场景</span><br><span class="line">技术设计已确认，需要根据 Spec 和设计文档进行编码实现</span><br><span class="line"></span><br><span class="line">## 输入</span><br><span class="line">- `.opencode/artifacts/requirement-analysis.md`（需求分析产出：功能点+验收标准）</span><br><span class="line">- `.opencode/artifacts/tech-design.md`（技术设计产出：组件树+类型定义+API 接口）</span><br><span class="line">- 项目代码规范和目录结构（从 CLAUDE.md 获取）</span><br><span class="line"></span><br><span class="line">## 输出</span><br><span class="line">- 实现代码（通过对应测试）</span><br><span class="line">- 代码变更摘要</span><br><span class="line"></span><br><span class="line">## 执行步骤</span><br><span class="line">1. 读取需求分析和设计文档</span><br><span class="line">2. 检查是否已有测试文件（如果有，以测试通过为目标编写实现）</span><br><span class="line">3. 优先增量生成：一次只实现一个功能点</span><br><span class="line">4. 每完成一个功能点后立即运行 `npm run build` 和 `npm run test`</span><br><span class="line">5. 如果测试失败，自主定位并修复（红 → 绿 循环）</span><br><span class="line">6. 一个功能点通过后再进入下一个</span><br><span class="line">7. 所有功能点完成后输出代码变更摘要</span><br><span class="line">8. 等待人工确认后进入测试阶段</span><br><span class="line"></span><br><span class="line">## 约束</span><br><span class="line">- MUST: 读取需求分析和设计文档作为输入（不直接对接原始需求）</span><br><span class="line">- MUST: 优先检查是否有测试文件，有则 TDD 模式</span><br><span class="line">- MUST: 增量生成，每次不超过一个功能点</span><br><span class="line">- MUST: 每步完成后立即运行构建和测试验证</span><br><span class="line">- MUST: AI 生成的代码必须人工 Review</span><br><span class="line">- MUST NOT: 一口气生成超过 200 行的代码块</span><br><span class="line">- MUST NOT: 修改已有的测试文件</span><br><span class="line">- MUST NOT: 跳过验证步骤直接进入下一个功能点</span><br></pre></td></tr></table></figure><h3 id="设计思路-1"><a href="#设计思路-1" class="headerlink" title="设计思路"></a>设计思路</h3><p>这个 Skill 有几个设计细节值得注意：</p><p><strong>第一，输入上游化。</strong> 编码 Skill 的输入不是”你粘贴一段需求”，而是自动读取两个上游文档。这意味着需求分析和设计阶段做得越扎实，编码质量越高。</p><p><strong>第二，增量原则。</strong> Skill 明确要求”一次只实现一个功能点”——这不是保守，而是为了让 Agent 在可控范围内工作。如果一口气生成 500 行代码，出问题你根本不知道从哪开始 Debug。</p><p><strong>第三，TDD 自动触发。</strong> 如果检测到已有测试文件，Agent 会自动进入”让测试通过”模式。这就是为什么下一环节（测试生成）要在编码 Skill 之前或并行执行。</p><hr><h2 id="2-5-环节四：测试-Skill"><a href="#2-5-环节四：测试-Skill" class="headerlink" title="2.5 环节四：测试 Skill"></a>2.5 环节四：测试 Skill</h2><p>写测试是大多数开发者的痛点，但 AI 非常擅长。测试 Skill 可以独立运行，也可以在编码之前提前生成测试文件（TDD 模式）。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"># 文件名：.opencode/skills/test-generation/SKILL.md</span><br><span class="line"></span><br><span class="line">## 适用场景</span><br><span class="line">需求分析和技术设计已完成，需要为功能点生成测试文件</span><br><span class="line"></span><br><span class="line">## 输入</span><br><span class="line">- `.opencode/artifacts/requirement-analysis.md`（功能点清单 + 验收标准）</span><br><span class="line">- `.opencode/artifacts/tech-design.md`（组件结构 + 类型定义 + API 接口）</span><br><span class="line">- 项目测试框架配置（从 CLAUDE.md 获取）</span><br><span class="line"></span><br><span class="line">## 输出</span><br><span class="line">- 完整的测试文件（`.test.ts` / `.spec.ts`）</span><br><span class="line">- 覆盖率简报</span><br><span class="line"></span><br><span class="line">## 执行步骤</span><br><span class="line">1. 读取需求分析的验收标准和设计文档的类型定义</span><br><span class="line">2. 分析每个功能点的正常路径、边界值、异常路径</span><br><span class="line">3. 为每个功能点生成对应的测试用例（一个 describe 对应一个功能点）</span><br><span class="line">4. 确保测试文件可以独立运行（mock 外部依赖但不 over-mock）</span><br><span class="line">5. 运行测试验证语法正确（预期全红，因为还没有实现代码）</span><br><span class="line">6. 输出测试覆盖率目标（最低 80% 行覆盖率）</span><br><span class="line">7. 将测试文件写入对应源文件目录</span><br><span class="line"></span><br><span class="line">## 约束</span><br><span class="line">- MUST: 一个 describe 对应一个功能点，一个 it 只测一个行为</span><br><span class="line">- MUST: 覆盖正常路径 + 边界值 + 异常路径</span><br><span class="line">- MUST: 测试可独立运行，不依赖其他测试</span><br><span class="line">- MUST: 只生成测试，不生成实现代码</span><br><span class="line">- MUST NOT: mock 不需要的外部依赖</span><br><span class="line">- MUST NOT: 测试实现细节（如内部状态、私有方法）</span><br><span class="line">- MUST NOT: 修改源文件</span><br></pre></td></tr></table></figure><h3 id="关键要点-1"><a href="#关键要点-1" class="headerlink" title="关键要点"></a>关键要点</h3><p>测试 Skill 可以独立运行，但在 TDD 流程中它通常在编码之前执行。<strong>Agent 的自主决策</strong>体现得非常明显：</p><ul><li>如果项目用 Vitest，Agent 会生成 <code>vitest</code> 格式的测试</li><li>如果项目用 Jest，Agent 会适配 <code>jest</code> 格式</li><li>如果是组件测试，Agent 自己决定用 Testing Library 还是 Playwright Component Test</li><li>Agent 从项目配置文件中检测这些信息，不需要你手动告诉它</li></ul><p>这就是 Skill 化与传统模板的区别——<strong>Skill 只规定”做什么”和”约束”，不规定”用什么工具”</strong>。</p><hr><h2 id="2-6-环节五：Code-Review-Skill"><a href="#2-6-环节五：Code-Review-Skill" class="headerlink" title="2.6 环节五：Code Review Skill"></a>2.6 环节五：Code Review Skill</h2><p>Code Review 通常被认为是”人的工作”。但 AI 做 Pre-review 的效率极高，它擅长发现一致性问题和边界遗漏。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"># 文件名：.opencode/skills/code-review/SKILL.md</span><br><span class="line"></span><br><span class="line">## 适用场景</span><br><span class="line">代码变更已完成并经过自测，需要进行 Code Review</span><br><span class="line"></span><br><span class="line">## 输入</span><br><span class="line">- 代码变更（通过 git diff 获取，或通过 LSP 读取文件）</span><br><span class="line">- 项目代码规范（从 CLAUDE.md 获取）</span><br><span class="line">- `.opencode/artifacts/requirement-analysis.md`（用于对照需求判断逻辑正确性）</span><br><span class="line"></span><br><span class="line">## 输出</span><br><span class="line">- P0（必须修复）：逻辑错误、安全问题、类型问题、性能隐患</span><br><span class="line">- P1（建议修复）：代码质量、错误处理、测试覆盖</span><br><span class="line">- P2（风格建议）：命名一致性、注释准确性</span><br><span class="line">- 最终结论（是否可以合并）</span><br><span class="line"></span><br><span class="line">## 执行步骤</span><br><span class="line">1. 通过 git diff（或 LSP 文件对比）获取代码变更</span><br><span class="line">2. 对照需求分析文档判断变更是否满足验收标准</span><br><span class="line">3. 按 P0 → P1 → P2 分级 Review：</span><br><span class="line">   - P0：逻辑错误、XSS/CSRF/注入、类型不安全、性能泄漏</span><br><span class="line">   - P1：可读性、错误处理、测试覆盖、重复代码</span><br><span class="line">   - P2：命名一致性、代码风格、注释质量</span><br><span class="line">4. 生成 Review Report 到 `.opencode/artifacts/code-review.md`</span><br><span class="line">5. 如果存在 P0 问题，要求 Agent 自主修复并重新 Review</span><br><span class="line">6. 如果全部通过（或仅 P2 问题），进入合并或下一步</span><br><span class="line"></span><br><span class="line">## 约束</span><br><span class="line">- MUST: 使用 git diff 获取变更（不依赖人工粘贴代码）</span><br><span class="line">- MUST: 按 P0/P1/P2 三级分类问题</span><br><span class="line">- MUST: 每个问题必须给出具体的代码位置和建议修复方案</span><br><span class="line">- MUST: P0 问题必须修复后再合并</span><br><span class="line">- MUST NOT: 仅做风格检查（需要涉及业务逻辑判断）</span><br><span class="line">- MUST NOT: 完全依赖 AI 判断（最终决策由人工完成）</span><br></pre></td></tr></table></figure><h3 id="设计思路-2"><a href="#设计思路-2" class="headerlink" title="设计思路"></a>设计思路</h3><p>这个 Skill <strong>不依赖粘贴代码</strong>——Agent 会自动执行 <code>git diff</code> 获取变更内容。如果需要上下文，Agent 可以用 LSP 读取相关文件。Review 时还会对照需求分析的产出，判断代码是否偏离了原始需求。</p><p>&emsp;&emsp;需要注意的一点：AI 擅长发现<strong>遗漏的边界处理</strong>和<strong>一致性问题</strong>，但不太擅长判断<strong>业务逻辑对错</strong>。所以 Skill 输出规定了”人做最终决策”，但 P0 问题 Agent 可以自主修复。</p><hr><h2 id="2-7-环节六：部署运维-Skill"><a href="#2-7-环节六：部署运维-Skill" class="headerlink" title="2.7 环节六：部署运维 Skill"></a>2.7 环节六：部署运维 Skill</h2><p>部署运维与其他 Skill 最大的区别在于：<strong>它更依赖 MCP 工具</strong>。测试和 Review 主要操作文件系统，但部署需要调用 CI&#x2F;CD 平台、云服务商 API、监控系统。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"># 文件名：.opencode/skills/deploy-ops/SKILL.md</span><br><span class="line"></span><br><span class="line">## 适用场景</span><br><span class="line">代码已合并到主分支，需要进行构建、部署、配置变更、或排查线上问题</span><br><span class="line"></span><br><span class="line">## 输入</span><br><span class="line">- 代码变更摘要（合并到主分支的 commit）</span><br><span class="line">- 项目部署配置（从 CLAUDE.md 或 CI 配置文件读取）</span><br><span class="line"></span><br><span class="line">## 输出</span><br><span class="line">- 部署完成确认（或部署失败报告）</span><br><span class="line">- 部署后验证结果</span><br><span class="line">- 监控告警配置确认</span><br><span class="line"></span><br><span class="line">## 执行步骤</span><br><span class="line">1. 读取项目 CI/CD 配置文件（`.github/workflows/` 或 `.gitlab-ci.yml`）</span><br><span class="line">2. 触发构建流水线（通过 GitHub MCP / GitLab MCP）</span><br><span class="line">3. 监控构建状态，如果失败则分析日志并修复</span><br><span class="line">4. 构建通过后，触发部署到目标环境</span><br><span class="line">5. 部署完成后执行健康检查（HTTP 状态码 / 页面加载 / API 响应）</span><br><span class="line">6. 检查监控告警是否正常配置</span><br><span class="line">7. 输出部署报告到 `.opencode/artifacts/deploy-report.md`</span><br><span class="line"></span><br><span class="line">## 约束</span><br><span class="line">- MUST: 通过 MCP 工具触发 CI/CD，不手动执行命令</span><br><span class="line">- MUST: 部署后执行健康检查验证</span><br><span class="line">- MUST: 如果部署失败，自动回滚到上一个稳定版本</span><br><span class="line">- MUST NOT: 直接修改生产环境配置（通过 IaC 工具或 CI 配置变更）</span><br><span class="line">- MUST NOT: 跳过部署验证步骤</span><br></pre></td></tr></table></figure><h3 id="与其他-Skill-的区别"><a href="#与其他-Skill-的区别" class="headerlink" title="与其他 Skill 的区别"></a>与其他 Skill 的区别</h3><table><thead><tr><th>Skill</th><th>主要工具</th><th>MCP 依赖度</th></tr></thead><tbody><tr><td>requirement-analysis</td><td>Read &#x2F; JIRA MCP</td><td>可选</td></tr><tr><td>tech-design</td><td>Read &#x2F; Write &#x2F; LSP</td><td>低</td></tr><tr><td>coding</td><td>Read &#x2F; Write &#x2F; LSP &#x2F; Bash</td><td>低</td></tr><tr><td>test-generation</td><td>Read &#x2F; Write &#x2F; Bash (npm test)</td><td>低</td></tr><tr><td>code-review</td><td>git diff &#x2F; LSP &#x2F; Read</td><td>低</td></tr><tr><td>deploy-ops</td><td>GitHub MCP &#x2F; Cloud MCP &#x2F; HTTP</td><td><strong>高</strong></td></tr></tbody></table><p>&emsp;&emsp;部署 Skill 是<strong>唯一一个默认就重度依赖 MCP</strong> 的环节。如果 Agent 环境没有配置 GitHub MCP 或云服务 MCP，这个 Skill 的效果会大打折扣。所以部署 Skill 的 SKILL.md 中应该额外注明<strong>前置依赖</strong>的 MCP 工具列表。</p><hr><h2 id="2-8-⭐-核心：Skill-Workflow-编排"><a href="#2-8-⭐-核心：Skill-Workflow-编排" class="headerlink" title="2.8 ⭐ 核心：Skill Workflow 编排"></a>2.8 ⭐ 核心：Skill Workflow 编排</h2><p>单个 Skill 解决了”这个环节怎么做”，但最强大的部分是把它们<strong>串成自动流水线</strong>。这正是 AGENTS.md 的作用。</p><h3 id="完整-AGENTS-md-配置"><a href="#完整-AGENTS-md-配置" class="headerlink" title="完整 AGENTS.md 配置"></a>完整 AGENTS.md 配置</h3><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 文件名：AGENTS.md</span></span><br><span class="line"></span><br><span class="line"><span class="section"># 研发效能 Workflow</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 流程说明</span></span><br><span class="line">本项目将软件开发生命周期封装为一系列可编排的 Skill，Agent 按顺序自动执行。</span><br><span class="line">每个 Skill 的输入/输出通过 <span class="code">`.opencode/artifacts/`</span> 目录传递。</span><br><span class="line"></span><br><span class="line"><span class="section">## Workflow 顺序</span></span><br><span class="line"></span><br><span class="line"><span class="section">### Phase 1: 需求分析</span></span><br><span class="line"><span class="bullet">-</span> Skill: <span class="code">`.opencode/skills/requirement-analysis`</span></span><br><span class="line"><span class="bullet">-</span> 输入: JIRA Ticket / 需求文档</span><br><span class="line"><span class="bullet">-</span> 输出: <span class="code">`artifacts/requirement-analysis.md`</span></span><br><span class="line"><span class="bullet">-</span> Gate: ✅ 人工确认</span><br><span class="line"></span><br><span class="line"><span class="section">### Phase 2: 技术设计</span></span><br><span class="line"><span class="bullet">-</span> Skill: <span class="code">`.opencode/skills/tech-design`</span></span><br><span class="line"><span class="bullet">-</span> 输入: <span class="code">`artifacts/requirement-analysis.md`</span></span><br><span class="line"><span class="bullet">-</span> 输出: <span class="code">`artifacts/tech-design.md`</span></span><br><span class="line"><span class="bullet">-</span> Gate: ✅ 人工确认</span><br><span class="line"></span><br><span class="line"><span class="section">### Phase 3: 测试生成（TDD 前置）</span></span><br><span class="line"><span class="bullet">-</span> Skill: <span class="code">`.opencode/skills/test-generation`</span></span><br><span class="line"><span class="bullet">-</span> 输入: <span class="code">`artifacts/requirement-analysis.md`</span> + <span class="code">`artifacts/tech-design.md`</span></span><br><span class="line"><span class="bullet">-</span> 输出: 测试文件（写入 src 目录）</span><br><span class="line"><span class="bullet">-</span> Gate: ⏭️ 无（自动进入编码）</span><br><span class="line"></span><br><span class="line"><span class="section">### Phase 4: 编码实现</span></span><br><span class="line"><span class="bullet">-</span> Skill: <span class="code">`.opencode/skills/coding`</span></span><br><span class="line"><span class="bullet">-</span> 输入: <span class="code">`artifacts/requirement-analysis.md`</span> + <span class="code">`artifacts/tech-design.md`</span></span><br><span class="line"><span class="bullet">-</span> 输出: 实现代码 + 测试通过</span><br><span class="line"><span class="bullet">-</span> Gate: ✅ 人工确认</span><br><span class="line"></span><br><span class="line"><span class="section">### Phase 5: Code Review</span></span><br><span class="line"><span class="bullet">-</span> Skill: <span class="code">`.opencode/skills/code-review`</span></span><br><span class="line"><span class="bullet">-</span> 输入: git diff + <span class="code">`artifacts/requirement-analysis.md`</span></span><br><span class="line"><span class="bullet">-</span> 输出: <span class="code">`artifacts/code-review.md`</span></span><br><span class="line"><span class="bullet">-</span> Gate: ✅ 人工确认 + P0 问题修复</span><br><span class="line"></span><br><span class="line"><span class="section">### Phase 6: 部署运维</span></span><br><span class="line"><span class="bullet">-</span> Skill: <span class="code">`.opencode/skills/deploy-ops`</span></span><br><span class="line"><span class="bullet">-</span> 输入: 合并后的主分支代码</span><br><span class="line"><span class="bullet">-</span> 输出: 部署确认 + 健康检查结果</span><br><span class="line"><span class="bullet">-</span> Gate: ✅ 最终确认</span><br></pre></td></tr></table></figure><h3 id="Skill-串联关系图"><a href="#Skill-串联关系图" class="headerlink" title="Skill 串联关系图"></a>Skill 串联关系图</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line">┌──────────────────────────────────────────────┐</span><br><span class="line">│              Agent 自动调度                     │</span><br><span class="line">│  按 AGENTS.md 声明的 Workflow 顺序加载 Skill   │</span><br><span class="line">└──────────────────────────────────────────────┘</span><br><span class="line">                   │</span><br><span class="line">                   ▼</span><br><span class="line">┌──────────────────────────────────────┐</span><br><span class="line">│  requirement-analysis                 │</span><br><span class="line">│  读取 JIRA / 文档 → 输出功能点清单     │</span><br><span class="line">└──────────────┬───────────────────────┘</span><br><span class="line">               │</span><br><span class="line">               ▼</span><br><span class="line">        ╔════════════╗</span><br><span class="line">        ║ 人工确认 Gate ║ ← 需求模糊？返回修改</span><br><span class="line">        ╚════════════╝</span><br><span class="line">               │</span><br><span class="line">               ▼</span><br><span class="line">┌──────────────────────────────────────┐</span><br><span class="line">│  tech-design                          │</span><br><span class="line">│  消费需求分析产出 → 输出方案对比+设计   │</span><br><span class="line">└──────────────┬───────────────────────┘</span><br><span class="line">               │</span><br><span class="line">               ▼</span><br><span class="line">        ╔════════════╗</span><br><span class="line">        ║ 人工确认 Gate ║ ← 方案不满意？调整设计</span><br><span class="line">        ╚════════════╝</span><br><span class="line">               │</span><br><span class="line">               ├─────────────────────────┐</span><br><span class="line">               │                         │</span><br><span class="line">               ▼                         ▼</span><br><span class="line">┌──────────────────────┐    ┌──────────────────────┐</span><br><span class="line">│  test-generation      │    │  coding              │</span><br><span class="line">│  先生成测试(可选前置)   │    │  消费需求+设计 → 实现  │</span><br><span class="line">└──────────┬───────────┘    └──────────┬───────────┘</span><br><span class="line">           │                          │</span><br><span class="line">           └──────────┬───────────────┘</span><br><span class="line">                      ▼</span><br><span class="line">               ╔════════════╗</span><br><span class="line">               ║ 人工确认 Gate ║ ← 代码或测试有问题的</span><br><span class="line">               ╚════════════╝</span><br><span class="line">                      │</span><br><span class="line">                      ▼</span><br><span class="line">┌──────────────────────────────────────┐</span><br><span class="line">│  code-review                          │</span><br><span class="line">│  git diff + LSP → 分级 Review Report  │</span><br><span class="line">└──────────────┬───────────────────────┘</span><br><span class="line">               │</span><br><span class="line">               ▼</span><br><span class="line">        ╔════════════╗</span><br><span class="line">        ║ 人工确认 Gate ║ ← P0 问题修复后合并</span><br><span class="line">        ╚════════════╝</span><br><span class="line">               │</span><br><span class="line">               ▼</span><br><span class="line">┌──────────────────────────────────────┐</span><br><span class="line">│  deploy-ops                           │</span><br><span class="line">│  MCP 触发 CI/CD + 健康检查             │</span><br><span class="line">└──────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="Agent-的自主决策"><a href="#Agent-的自主决策" class="headerlink" title="Agent 的自主决策"></a>Agent 的自主决策</h3><p>Skill Workflow 中最关键的概念是：<strong>Agent 自主决定每一步用什么工具</strong>。AGENTS.md 只规定”做什么”和”顺序”，不规定”用什么工具来做”。</p><p>举个例子，在 coding 阶段，Agent 会自问：</p><blockquote><p>“我需要实现这个功能点。项目是 React 18 + TypeScript + Tailwind CSS。测试文件已经生成好了。我应该用什么方式写代码？”</p></blockquote><p>然后 Agent 根据环境自主选择：</p><ul><li>如果当前环境是 Cursor，它会用 Composer 来生成代码（因为 Cursor Composer 支持多文件编辑）</li><li>如果当前环境是 OpenCode，它可能会直接使用 Write 工具写文件</li><li>如果有 LSP，它会在写完后用 LSP 检查语法</li></ul><p><strong>同样</strong>，在 review 阶段，Agent 会自己决定：</p><ul><li>用 <code>git diff</code> 获取变更 → 如果变更太多就分批 Review</li><li>用 LSP 检查类型错误 → 看 tsconfig 是否严格模式</li><li>用 Bash 运行 lint → 检查代码风格</li><li>用 GitHub MCP 创建 Review Comment → 如果配置了 GitHub 集成</li></ul><p><strong>在部署阶段</strong>，Agent 的决策更加多样化：</p><ul><li>如果有 GitHub MCP → 触发 GitHub Actions Workflow Dispatch</li><li>如果有 Vercel MCP → 调用 Vercel Deploy</li><li>如果有阿里云 MCP → 同步到 OSS 并刷新 CDN</li></ul><p><strong>这就是 Skill 化与传统 Prompt 模板最根本的区别：Skill 告诉 Agent “做什么”和”不能做什么”，但”怎么做”由 Agent 根据环境自主决策。</strong></p><hr><h2 id="2-9-Skill-Workflow-的设计原则"><a href="#2-9-Skill-Workflow-的设计原则" class="headerlink" title="2.9 Skill Workflow 的设计原则"></a>2.9 Skill Workflow 的设计原则</h2><p>把研发流程转化为 Skill Workflow 不是随手写几个 Skill 就行。以下是我在实践中总结的五条原则：</p><h3 id="原则一：松耦合"><a href="#原则一：松耦合" class="headerlink" title="原则一：松耦合"></a>原则一：松耦合</h3><p>每个 Skill 应该独立可运行，不依赖其他 Skill 的内部状态。Skill 之间的通信通过<strong>结构化文件</strong>（<code>.opencode/artifacts/</code>）完成，而不是通过内存变量或环境变量。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">✅ 正确：coding → 读取 artifacts/requirement-analysis.md</span><br><span class="line">❌ 错误：coding → 调用 requirement-analysis Skill 的内部函数</span><br></pre></td></tr></table></figure><p>这样设计的好处是：你可以单独替换某个 Skill 而不影响其他环节。比如想换一种 Code Review 策略，只改 <code>code-review</code> Skill 就行。</p><h3 id="原则二：明确-Gate"><a href="#原则二：明确-Gate" class="headerlink" title="原则二：明确 Gate"></a>原则二：明确 Gate</h3><p>Gate（人工确认点）是 Skill Workflow 的刹车。每个 Gate 都是一次<strong>质量检查</strong>，也是<strong>人类的决策权</strong>所在。</p><p>推荐在以下位置设置 Gate：</p><table><thead><tr><th>位置</th><th>目的</th><th>跳过条件</th></tr></thead><tbody><tr><td>需求分析后</td><td>确认功能点理解正确</td><td>需求极其明确</td></tr><tr><td>技术设计后</td><td>确认方案方向正确</td><td>改动极小，方案明显</td></tr><tr><td>编码完成后</td><td>确认代码质量和功能完整</td><td>—</td></tr><tr><td>Code Review 后</td><td>最终合并前检查</td><td>—</td></tr><tr><td>部署前</td><td>确认部署窗口和环境</td><td>紧急修复（Hotfix）</td></tr></tbody></table><h3 id="原则三：渐进式执行"><a href="#原则三：渐进式执行" class="headerlink" title="原则三：渐进式执行"></a>原则三：渐进式执行</h3><p>Agent 不需要一次加载所有 Skill。AGENTS.md 定义了完整的 Workflow，但 Agent <strong>只加载当前步骤对应的 Skill</strong>。这带来了三个好处：</p><ol><li><strong>上下文窗口不浪费</strong> — 一个 Skill 的 SKILL.md 通常只有 50-80 行，远低于一整套 Prompt 模板</li><li><strong>执行速度快</strong> — Agent 不需要在需求分析阶段就加载部署 Skill 的规则</li><li><strong>按需追加</strong> — 如果某个环节需要补充规则，只修改对应的 SKILL.md</li></ol><h3 id="原则四：可跳过"><a href="#原则四：可跳过" class="headerlink" title="原则四：可跳过"></a>原则四：可跳过</h3><p>不是所有开发任务都需要走完整流程。紧急 Bug 修复就应该绕过需求分析和设计阶段。</p><p>实现方式：在 AGENTS.md 中标注每个 Phase 的 <code>skipable</code> 属性，或通过指令参数控制：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&gt; 修复用户登录页的 500 错误</span><br><span class="line">→ Agent 检测到这是紧急 Bug 修复</span><br><span class="line">→ 自动跳过 requirement-analysis 和 tech-design</span><br><span class="line">→ 直接加载 coding + test-generation + code-review</span><br></pre></td></tr></table></figure><h3 id="原则五：输入输出契约"><a href="#原则五：输入输出契约" class="headerlink" title="原则五：输入输出契约"></a>原则五：输入输出契约</h3><p>这是最容易被忽视但最重要的原则。每个 Skill 的输入&#x2F;输出必须<strong>显式声明</strong>并且<strong>格式一致</strong>。如果 <code>requirement-analysis</code> 输出的功能点格式是：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">## FP-001: 用户登录</span></span><br><span class="line"><span class="bullet">-</span> 优先级: P0</span><br><span class="line"><span class="bullet">-</span> 验收标准: Given... When... Then...</span><br></pre></td></tr></table></figure><p>那么 <code>coding</code> Skill 和 <code>test-generation</code> Skill 都必须能消费这个格式。建议在项目级别定义一份<strong>Artifact 规范</strong>（放在 <code>.opencode/artifacts/README.md</code>），约定所有 Skill 产出的文件结构和 Markdown 格式。</p><hr><h2 id="2-10-真实场景：从-JIRA-到-MR-的全自动-Skill-流水线"><a href="#2-10-真实场景：从-JIRA-到-MR-的全自动-Skill-流水线" class="headerlink" title="2.10 真实场景：从 JIRA 到 MR 的全自动 Skill 流水线"></a>2.10 真实场景：从 JIRA 到 MR 的全自动 Skill 流水线</h2><p>理论讲了这么多，来看看在实际项目中<strong>Agent 如何自动跑完整条流水线</strong>。</p><h3 id="场景设定"><a href="#场景设定" class="headerlink" title="场景设定"></a>场景设定</h3><p>你是一名前端工程师，收到一个 JIRA Ticket：</p><blockquote><p><strong>Ticket</strong>: FRONT-2345<br><strong>标题</strong>: 在用户个人中心增加实名认证功能<br><strong>描述</strong>: 用户在个人中心可以看到实名认证入口，提交姓名和身份证号进行认证。认证通过后展示已认证状态和脱敏后的身份信息。需要对接第三方实名认证服务。</p></blockquote><h3 id="第一步：Agent-收到-Ticket"><a href="#第一步：Agent-收到-Ticket" class="headerlink" title="第一步：Agent 收到 Ticket"></a>第一步：Agent 收到 Ticket</h3><p>你在聊天窗口说：</p><blockquote><p>“处理 FRONT-2345”</p></blockquote><p>Agent 解析这个消息，判断这是<strong>新功能开发</strong>，需要走完整 Workflow。它读取 AGENTS.md 找到第一个 Phase，然后加载 <code>requirement-analysis</code> Skill。</p><h3 id="第二步：加载需求分析-Skill"><a href="#第二步：加载需求分析-Skill" class="headerlink" title="第二步：加载需求分析 Skill"></a>第二步：加载需求分析 Skill</h3><p>Agent 读取 <code>requirement-analysis/SKILL.md</code>，看到执行步骤：</p><ol><li><strong>读取需求输入</strong> → Agent 调用 JIRA MCP 的 <code>jira_getIssue(&quot;FRONT-2345&quot;)</code> 拉取 Ticket 详情</li><li><strong>拆解功能点</strong> → Agent 分析需求文本，识别出 5 个 P0 功能点</li><li><strong>编写验收标准</strong> → 为每个功能点写 Given&#x2F;When&#x2F;Then</li><li><strong>识别模糊点</strong> → 发现 4 个待确认问题：</li></ol><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">## 待确认问题</span></span><br><span class="line"><span class="bullet">1.</span> 身份证号脱敏展示几位？通常前6+后4，需确认</span><br><span class="line"><span class="bullet">2.</span> 认证失败的错误信息是否展示给用户？</span><br><span class="line"><span class="bullet">3.</span> 第三方认证服务是否有沙箱环境？</span><br><span class="line"><span class="bullet">4.</span> 实名认证是否需要与已有用户信息打通？</span><br></pre></td></tr></table></figure><ol start="5"><li><strong>技术可行性评估</strong> → 判断身份证校验、第三方 API 对接的复杂度</li><li><strong>输出文档</strong> → 写入 <code>.opencode/artifacts/requirement-analysis.md</code></li><li><strong>等待确认</strong> → Agent 输出总结并问你：”以上功能点拆解和模糊点确认，是否有需要调整的地方？”</li></ol><p>你确认后，Agent 进入下一环节。</p><h3 id="第三步：加载技术设计-Skill"><a href="#第三步：加载技术设计-Skill" class="headerlink" title="第三步：加载技术设计 Skill"></a>第三步：加载技术设计 Skill</h3><p>Agent 读取 <code>tech-design/SKILL.md</code>，开始设计：</p><ol><li><strong>读取需求分析产出</strong> → 从 <code>artifacts/requirement-analysis.md</code> 获取功能点</li><li><strong>分析技术约束</strong> → 从 CLAUDE.md 读取技术栈（React 18 + Ant Design + TypeScript）</li><li><strong>生成方案对比</strong> → Agent 输出 2-3 个方案并推荐最合适的</li></ol><p>这时 Agent 可能会问自己：”这个项目用了 Ant Design，我应该用什么方案来做表单校验？” 然后它决定用 Ant Design Form + 自定义校验规则，并在设计文档中说明理由。</p><ol start="4"><li><strong>输出设计文档</strong> → 写入 <code>.opencode/artifacts/tech-design.md</code></li><li><strong>等待确认</strong> → “方案已输出，请确认设计方向”</li></ol><h3 id="第四步：加载测试生成-Skill"><a href="#第四步：加载测试生成-Skill" class="headerlink" title="第四步：加载测试生成 Skill"></a>第四步：加载测试生成 Skill</h3><p>你确认设计后，Agent 自动加载 <code>test-generation</code> Skill：</p><ol><li><strong>读取验收标准</strong> → 从需求分析产出中获取每个功能点的验收标准</li><li><strong>分析测试场景</strong> → 识别正常路径、边界值、异常路径</li><li><strong>生成测试文件</strong> → 写入 <code>src/components/CertifyForm.test.tsx</code></li></ol><p>Agent 自己决定用 Vitest + Testing Library（因为它检测到项目中配置了这些工具）。它还会检查已有的测试风格，保持一致。</p><h3 id="第五步：加载编码-Skill"><a href="#第五步：加载编码-Skill" class="headerlink" title="第五步：加载编码 Skill"></a>第五步：加载编码 Skill</h3><p>测试文件已就位。Agent 加载 <code>coding</code> Skill：</p><ol><li><strong>读取输入</strong> → 自动读取需求分析和技术设计文档</li><li><strong>检查测试是否存在</strong> → 发现 <code>CertifyForm.test.tsx</code> 存在，进入 TDD 模式</li><li><strong>实现第一个功能点</strong> → 编写认证入口按钮组件</li><li><strong>运行测试验证</strong> → <code>npm run test</code>，通过</li><li><strong>下一个功能点</strong> → 认证表单组件</li><li><strong>重复</strong> → 直到所有功能点实现完成</li></ol><p>Agent 的决策过程在这里非常明显：</p><ul><li><strong>是否需要新文件？</strong> → Agent 检查是否应该新建组件还是修改现有文件</li><li><strong>代码风格统一？</strong> → Agent 读取已有组件的写法，保持一致性</li><li><strong>依赖管理？</strong> → 如果需要新依赖，Agent 会用 <code>npm install</code> 添加</li></ul><p>所有功能点通过测试后，Agent 输出变更摘要并等待确认。</p><h3 id="第六步：加载-Code-Review-Skill"><a href="#第六步：加载-Code-Review-Skill" class="headerlink" title="第六步：加载 Code Review Skill"></a>第六步：加载 Code Review Skill</h3><p>你确认代码后（或 Agent 自动触发下一环节），Agent 加载 <code>code-review</code> Skill：</p><ol><li><strong>获取变更</strong> → <code>git diff</code> 查看所有改动</li><li><strong>对照需求</strong> → 检查实现是否覆盖了需求分析中的全部验收标准</li><li><strong>分级 Review</strong> →</li></ol><p>Agent 自己决定 Review 策略：</p><ul><li>用 LSP 检查类型错误 → 扫描所有新增 TypeScript 文件</li><li>用 Bash 运行 <code>npm run lint</code> → 检查代码风格</li><li>用 <code>git diff</code> 分析逻辑变更 → 判断是否有潜在问题</li></ul><ol start="4"><li><strong>输出 Review Report</strong> → P0&#x2F;P1&#x2F;P2 分级问题</li><li><strong>如果存在 P0 问题</strong> → Agent 自主修复并重新 Review</li><li><strong>等待最终确认</strong></li></ol><h3 id="第七步：加载部署运维-Skill"><a href="#第七步：加载部署运维-Skill" class="headerlink" title="第七步：加载部署运维 Skill"></a>第七步：加载部署运维 Skill</h3><p>Review 通过后，Agent 加载 <code>deploy-ops</code> Skill：</p><ol><li><strong>触发 CI&#x2F;CD</strong> → 通过 GitHub MCP 调用 GitHub Actions</li><li><strong>监控构建状态</strong> → 轮询 Action 运行状态</li><li><strong>健康检查</strong> → 部署后请求目标 URL 确认服务正常</li><li><strong>输出部署报告</strong></li></ol><p>这个阶段 Agent 的自主决策能力最突出：</p><ul><li>如果用 GitHub MCP → <code>github_actions_triggerWorkflow</code></li><li>如果用 GitLab CI → 调用对应的 Pipeline API</li><li>如果用自建 Jenkins → 通过 Jenkins API 触发</li></ul><p>Agent 根据项目 CI 配置自动选择调用方式。</p><h3 id="完整流程时间线"><a href="#完整流程时间线" class="headerlink" title="完整流程时间线"></a>完整流程时间线</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">09:00  →  你: &quot;处理 FRONT-2345&quot;</span><br><span class="line">09:01  →  Agent 加载 requirement-analysis Skill</span><br><span class="line">09:03  →  Agent 输出功能点清单 + 模糊点</span><br><span class="line">          你确认 ✅</span><br><span class="line">09:05  →  Agent 加载 tech-design Skill</span><br><span class="line">09:08  →  Agent 输出方案对比 + 设计文档</span><br><span class="line">          你确认 ✅</span><br><span class="line">09:10  →  Agent 加载 test-generation Skill</span><br><span class="line">09:12  →  测试文件写入完成</span><br><span class="line">09:12  →  Agent 加载 coding Skill（自动开始编码）</span><br><span class="line">09:25  →  全部功能点实现完成，测试通过 ✅</span><br><span class="line">09:25  →  Agent 加载 code-review Skill</span><br><span class="line">09:28  →  Review 完成，无 P0 问题</span><br><span class="line">          你确认 ✅</span><br><span class="line">09:30  →  Agent 合并 MR + 加载 deploy-ops Skill</span><br><span class="line">09:32  →  CI/CD 触发，部署完成</span><br><span class="line">09:33  →  健康检查通过 ✅</span><br><span class="line"></span><br><span class="line">总耗时：33 分钟</span><br><span class="line">你实际参与：3 次确认，约 2 分钟</span><br></pre></td></tr></table></figure><h3 id="对比传统流程"><a href="#对比传统流程" class="headerlink" title="对比传统流程"></a>对比传统流程</h3><table><thead><tr><th>维度</th><th>传统 Prompt 方式</th><th>Skill 化 Workflow</th></tr></thead><tbody><tr><td>启动方式</td><td>你在每个环节写 Prompt</td><td>你说一句”处理这个 Ticket”</td></tr><tr><td>流程切换</td><td>你手动复制粘贴上下文</td><td>Agent 自动读取 artifacts</td></tr><tr><td>工具选择</td><td>你告诉 AI 用什么</td><td>Agent 自主选择</td></tr><tr><td>一致性</td><td>每次 Prompt 质量看状态</td><td>固化在 SKILL.md 中，稳定输出</td></tr><tr><td>人工参与</td><td>每个环节你都在操作</td><td>只在 Gate 做决策确认</td></tr><tr><td>上下文</td><td>每个环节重新描述上下文</td><td>Agent 按需加载 Skill</td></tr></tbody></table><hr><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>这篇文章的核心思想很简单：<strong>不要教 AI 怎么做事——把你做事的方法写成 Skill，让 AI 自己去执行。</strong></p><p>把研发流程 Skill 化后，你得到的是一个<strong>可编排、可复用、可跳过、渐进式执行</strong>的自动化流水线。每个 Skill 封装了一个环节的最佳实践，AGENTS.md 将它们串联成端到端的工作流，Agent 自主选择工具完成每个步骤。</p><p>这种模式不仅适用于本文章覆盖的六个环节（需求分析 → 技术设计 → 编码 → 测试 → Code Review → 部署），你也可以为<strong>项目规范检查、性能优化、Changelog 生成、依赖升级</strong>等任何重复性流程编写 Skill。</p><p><strong>下一节</strong>将深入讲解 Cursor 的具体使用技巧，包括三种模式（Chat &#x2F; Composer &#x2F; Agent）的实际应用场景和最佳实践。不过，在掌握 Cursor 之前，先把项目的 Skill 体系搭建好——这才是 AI 时代前端工程师真正需要掌握的”提效思维”。</p>]]>
    </content>
    <id>https://github.com/wz71014q/2026/04/30/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-%E5%85%A8%E9%93%BE%E8%B7%AF%E6%8F%90%E6%95%88%E7%AF%87/</id>
    <link href="https://github.com/wz71014q/2026/04/30/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-%E5%85%A8%E9%93%BE%E8%B7%AF%E6%8F%90%E6%95%88%E7%AF%87/"/>
    <published>2026-04-30T02:00:00.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p><strong>本章目的</strong>：从需求分析到上线运维，覆盖整个软件开发生命周期（SDLC）。但本文不是给你一堆 Prompt 模板——而是教你<strong>把每个研发环节封装成一个 Skill（SKILL.md），然后用一个 Workflow（AGENTS.md）让 AI Agent 按流水线自动执行</strong>。Agent 自行决策每一步用什么工具和 MCP，你只需要在关键 Gate 做确认。</p>
</blockquote>
<hr>
<h2 id="2-1-开发生命周期全景"><a href="#2-1-开发生命周期全景" class="headerlink" title="2.1 开发生命周期全景"></a>2.1 开发生命周期全景</h2><p>传统开发流程中，AI 往往只被用在”写代码”这个环节。但事实上，<strong>每个环节都可以 Skill 化</strong>。一旦被 Skill 化，Agent 就能自动识别当前处于什么阶段、该做什么事、用什么工具。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐</span><br><span class="line">│ 需求分析      │ → │ 技术设计      │ → │ 编码实现      │ → │  测试        │ → │ Code         │ → │ 部署运维      │</span><br><span class="line">│ Skill        │    │ Skill        │    │ Skill        │    │ Skill        │    │ Review Skill │    │ Skill        │</span><br><span class="line">│              │    │              │    │              │    │              │    │              │    │              │</span><br><span class="line">│ requirement- │    │ tech-design  │    │ coding       │    │ test-        │    │ code-review  │    │ deploy-ops   │</span><br><span class="line">│ analysis     │    │              │    │              │    │ generation   │    │              │    │              │</span><br><span class="line">└──────┬───────┘    └──────┬───────┘    └──────┬───────┘    └──────┬───────┘    └──────┬───────┘    └──────┬───────┘</span><br><span class="line">       │                   │                   │                   │                   │                   │</span><br><span class="line">       ▼                   ▼                   ▼                   ▼                   ▼                   ▼</span><br><span class="line">  产出：功能点清单      产出：方案文档       产出：实现代码       产出：测试文件      产出：Review 结论   产出：部署完成</span><br><span class="line">  验收标准             API 设计            + 测试通过          + 覆盖率报告       + MR 合并          + 监控告警</span><br><span class="line">  模糊点清单           组件树</span><br></pre></td></tr></table></figure>

<p>不再是你跑过去问 Agent”帮我分析这个需求”——Agent 看到 JIRA Ticket 后，自己就会加载 <code>requirement-analysis</code> Skill，按 Skill 里的步骤执行。</p>
<hr>
<h2 id="2-2-环节一：需求分析-Skill"><a href="#2-2-环节一：需求分析-Skill" class="headerlink" title="2.2 环节一：需求分析 Skill"></a>2.2 环节一：需求分析 Skill</h2>]]>
    </summary>
    <title>AI时代前端工程师实战指南：全链路提效篇</title>
    <updated>2026-05-22T05:52:31.558Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="AI时代前端工程师实战指南" scheme="https://github.com/wz71014q/categories/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="AI" scheme="https://github.com/wz71014q/tags/AI/"/>
    <category term="前端工程师" scheme="https://github.com/wz71014q/tags/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88/"/>
    <category term="实战指南" scheme="https://github.com/wz71014q/tags/%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="Agent" scheme="https://github.com/wz71014q/tags/Agent/"/>
    <category term="工作流" scheme="https://github.com/wz71014q/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81/"/>
    <content>
      <![CDATA[<h1 id="AI时代前端工程师实战指南：Agent工作流"><a href="#AI时代前端工程师实战指南：Agent工作流" class="headerlink" title="AI时代前端工程师实战指南：Agent工作流"></a>AI时代前端工程师实战指南：Agent工作流</h1><blockquote><p>本文定位不一样——不是教你怎么给 Agent 写 Prompt，而是教你怎么<strong>设计一套能被 Agent 自动执行的 Skill 体系</strong>。<br>把开发流程拆成可复用的指令模块，让 Agent 按工作流自动运转，你从”操作者”变成”设计者”。</p></blockquote><hr><h2 id="5-1-从”写-Prompt”到”设计-Skill”"><a href="#5-1-从”写-Prompt”到”设计-Skill”" class="headerlink" title="5.1 从”写 Prompt”到”设计 Skill”"></a>5.1 从”写 Prompt”到”设计 Skill”</h2><p>&emsp;&emsp;上一章我们讲了 Prompt 工程——如何写出结构清晰、约束明确的 Prompt，让 AI 输出更可靠。这是一个巨大的进步，从”随便问两句”到”系统化地写 Prompt”，已经是质的飞跃。</p><p>&emsp;&emsp;但用过一段时间后，你会发现一个新问题：<strong>每次都要重复写几乎一样的 Prompt</strong>。</p><p>&emsp;&emsp;比如你有一个开发习惯——写任何代码之前先做需求分析，然后出技术方案，再动手编码，最后加测试。这个流程你每次都要在 Prompt 里重申一遍：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">❌ 每次重复：</span><br><span class="line">&quot;先分析需求，列出所有功能点和边界条件&quot;</span><br><span class="line">&quot;然后出技术方案，考虑组件拆分和数据流&quot;</span><br><span class="line">&quot;再开始编码，遵循项目规范&quot;</span><br><span class="line">&quot;最后加测试，覆盖正常路径和异常路径&quot;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;这种做法的弊端很明显：</p><ol><li><strong>不一致</strong> — 今天记得说”先分析需求”，明天可能忘了，Agent 的行为就变了</li><li><strong>遗漏步骤</strong> — 流程有 5 步，你只说了 3 步，Agent 就按 3 步执行</li><li><strong>依赖个人记忆</strong> — 换一个人用，他不知道你的流程是什么，重新摸索</li><li><strong>不可复用</strong> — 换个项目，同样的流程要再写一遍</li></ol><p>&emsp;&emsp;这就引出了本文的核心概念：<strong>Skill</strong>。</p><p>&emsp;&emsp;Skill 的本质是什么？是把”怎么做”固化为一个可加载的指令模块。你把开发流程封装成 Skill 文件放在项目里，Agent 按需加载，不需要你每次口头交代。</p><table><thead><tr><th>对比维度</th><th>Prompt</th><th>Skill</th></tr></thead><tbody><tr><td><strong>生命周期</strong></td><td>一次性，用完即弃</td><td>持久化，可反复使用</td></tr><tr><td><strong>一致性</strong></td><td>每次重新写，可能遗漏</td><td>固化在文件中，始终一致</td></tr><tr><td><strong>可复用性</strong></td><td>仅限当前对话</td><td>跨项目、跨团队共享</td></tr><tr><td><strong>组合性</strong></td><td>单次输入，难以串联</td><td>多个 Skill 可编排为 Workflow</td></tr><tr><td><strong>维护性</strong></td><td>改一次 Prompt 只有你知道</td><td>改一个文件，所有人都受益</td></tr></tbody></table><p>&emsp;&emsp;打个比方：Prompt 就像你每次下厨都要翻菜谱，Skill 就像你把菜谱写成一本食谱书放在厨房——不仅你自己不用再翻，其他人也能按这本书做出同样的菜。</p><p>&emsp;&emsp;在 OpenCode 这类 Agent 编程工具中，Skill 以 <code>.opencode/skills/</code> 目录下的 SKILL.md 文件形式存在。Agent 启动时自动扫描这些文件，根据当前任务匹配合适的 Skill 加载。这就是”设计 Skill”的本质：<strong>把你的开发方法论写成 Agent 可读、可执行的标准文档</strong>。</p><hr><h2 id="5-2-Skill-的设计原则"><a href="#5-2-Skill-的设计原则" class="headerlink" title="5.2 Skill 的设计原则"></a>5.2 Skill 的设计原则</h2><p>&emsp;&emsp;既然 Skill 是一份给 Agent 读的操作指南，那它应该怎么设计？下面是我在实践中总结的五个核心原则。</p><h3 id="原则一：单一职责"><a href="#原则一：单一职责" class="headerlink" title="原则一：单一职责"></a>原则一：单一职责</h3><p>&emsp;&emsp;<strong>每个 Skill 只做一件事，并且把它做好。</strong></p><p>&emsp;&emsp;一个 Skill 如果既要做需求分析、又要写代码、还要做测试，它就会像一个人同时扮演三个角色——每个角色都演不到位。Agent 加载这样的 Skill 后，会在多个目标之间摇摆，哪一步都做不深入。</p><p>&emsp;&emsp;正确做法是把一个大流程拆成多个 Skill，每个 Skill 专注一个环节：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">❌ 不推荐：full-development-skill（包含分析、设计、编码、测试）</span><br><span class="line">✅ 推荐：requirement-analysis-skill → tech-design-skill → coding-skill → test-skill</span><br></pre></td></tr></table></figure><h3 id="原则二：声明边界"><a href="#原则二：声明边界" class="headerlink" title="原则二：声明边界"></a>原则二：声明边界</h3><p>&emsp;&emsp;<strong>明确告诉 Agent 什么能做、什么不能做。</strong></p><p>&emsp;&emsp;Agent 没有人类常识，你说”分析需求”，它可能真的去改代码了。你需要用 MUST DO &#x2F; MUST NOT DO 来定义行为边界。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">MUST DO:</span><br><span class="line">- 仅读取 src/ 和 docs/ 目录下的文件</span><br><span class="line">- 输出格式必须为 Markdown</span><br><span class="line"></span><br><span class="line">MUST NOT DO:</span><br><span class="line">- 不要修改任何源代码文件</span><br><span class="line">- 不要执行 npm install 或修改 package.json</span><br></pre></td></tr></table></figure><h3 id="原则三：可验证"><a href="#原则三：可验证" class="headerlink" title="原则三：可验证"></a>原则三：可验证</h3><p>&emsp;&emsp;<strong>每个 Skill 的产出物必须有明确的检查标准。</strong></p><p>&emsp;&emsp;Agent 执行完 Skill 后，怎么知道它做对了？靠人的直觉判断是不够的。你需要在 Skill 中嵌入验证步骤，让 Agent 自己检查自己的输出。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">验证标准：</span><br><span class="line">- 所有功能点都有对应的测试用例</span><br><span class="line">- LSP diagnostics 零错误</span><br><span class="line">- 测试覆盖率 ≥ 80%</span><br></pre></td></tr></table></figure><h3 id="原则四：可组合"><a href="#原则四：可组合" class="headerlink" title="原则四：可组合"></a>原则四：可组合</h3><p>&emsp;&emsp;<strong>多个 Skill 可以串联成工作流，前一个的输出是后一个的输入。</strong></p><p>&emsp;&emsp;Skill 之间应该通过”文件”或”标准输出格式”传递信息。比如需求分析 Skill 输出一份 spec.md，技术设计 Skill 读取这份 spec.md 作为输入。这样 Skill 之间松耦合，可以灵活编排。</p><h3 id="原则五：自包容"><a href="#原则五：自包容" class="headerlink" title="原则五：自包容"></a>原则五：自包容</h3><p>&emsp;&emsp;<strong>Skill 自带完整上下文，不依赖外部假设。</strong></p><p>&emsp;&emsp;一个 Skill 文件应该让一个不了解项目的 Agent 也能执行。不要在 Skill 里写”参考我们之前讨论过的 X 方案”——Agent 不知道”之前”是什么。所有必需的背景信息、参考文件路径、技术栈说明，都要写在 Skill 文件本身。</p><hr><h2 id="5-3-Skill-文件结构"><a href="#5-3-Skill-文件结构" class="headerlink" title="5.3 Skill 文件结构"></a>5.3 Skill 文件结构</h2><p>&emsp;&emsp;一个标准的 SKILL.md 文件包含以下几个部分：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">SKILL.md</span><br><span class="line">├── 1. Metadata（元数据）</span><br><span class="line">│   ├── 名称 — Skill 的唯一标识</span><br><span class="line">│   ├── 适用场景 — 什么情况下触发此 Skill</span><br><span class="line">│   └── 输入/输出 — 前置条件和后置产出</span><br><span class="line">├── 2. 执行步骤（Step by step）</span><br><span class="line">│   └── 按顺序排列的操作指令</span><br><span class="line">├── 3. 约束规则</span><br><span class="line">│   ├── MUST DO — Agent 必须做的事情</span><br><span class="line">│   └── MUST NOT DO — Agent 绝不能做的事情</span><br><span class="line">├── 4. 验证标准</span><br><span class="line">│   └── 如何判断产出物是否合格</span><br><span class="line">└── 5. 参考和依赖</span><br><span class="line">    └── 相关文件、文档、其他 Skill</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;下面给出三个完整的 SKILL.md 示例，覆盖了前端开发中最常见的三个环节。</p><h3 id="示例一：需求分析-Skill"><a href="#示例一：需求分析-Skill" class="headerlink" title="示例一：需求分析 Skill"></a>示例一：需求分析 Skill</h3><p>&emsp;&emsp;这个 Skill 负责将模糊的需求描述转化为结构化的功能规格说明。它不做编码，只做分析和文档输出。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"># Requirement Analysis Skill</span><br><span class="line"></span><br><span class="line">## Metadata</span><br><span class="line"></span><br><span class="line">- **名称**: requirement-analysis</span><br><span class="line">- **适用场景**: 收到一个新的功能需求，需要先理清需求边界和功能点</span><br><span class="line">- **前置条件**: 用户提供了需求描述（可以是几句话、一段文字或一个 Issue 链接）</span><br><span class="line">- **输出产物**: `docs/ specs/&lt;feature-name&gt;/requirement.md`</span><br><span class="line"></span><br><span class="line">## 执行步骤</span><br><span class="line"></span><br><span class="line">1. **读取上下文**</span><br><span class="line">   - 如果用户提到了相关文件，先读取这些文件了解背景</span><br><span class="line">   - 如果用户没有提供，询问并提供模板：需求背景、目标用户、核心功能</span><br><span class="line"></span><br><span class="line">2. **需求拆解**</span><br><span class="line">   - 列出所有功能点，每个功能点用一句话描述</span><br><span class="line">   - 标注每个功能点的优先级：P0（必须有）/ P1（应该有）/ P2（可以有）</span><br><span class="line"></span><br><span class="line">3. **边界条件识别**</span><br><span class="line">   - 列出正常路径（Happy Path）</span><br><span class="line">   - 列出异常路径（Error Path）</span><br><span class="line">   - 列出边界情况（空数据、大数据量、并发操作）</span><br><span class="line"></span><br><span class="line">4. **输出规范文档**</span><br><span class="line">   - 使用以下格式写入 `docs/ specs/&lt;feature-name&gt;/requirement.md`</span><br><span class="line"></span><br><span class="line">```markdown</span><br><span class="line"># &lt;功能名称&gt; 需求规格说明</span><br><span class="line"></span><br><span class="line">## 概述</span><br><span class="line">[一句话描述这个功能]</span><br><span class="line"></span><br><span class="line">## 功能点列表</span><br><span class="line">| 功能点 | 优先级 | 描述 |</span><br><span class="line">|--------|--------|------|</span><br><span class="line">| ...    | P0     | ...  |</span><br><span class="line"></span><br><span class="line">## 验收标准</span><br><span class="line">### Happy Path</span><br><span class="line">- [ ] ...</span><br><span class="line"></span><br><span class="line">### Error Path</span><br><span class="line">- [ ] ...</span><br><span class="line"></span><br><span class="line">### 边界条件</span><br><span class="line">- [ ] ...</span><br><span class="line"></span><br><span class="line">## 技术约束</span><br><span class="line">- 技术栈：[项目技术栈]</span><br><span class="line">- 兼容性要求：[浏览器/设备]</span><br><span class="line">- 性能要求：[加载时间/响应时间]</span><br></pre></td></tr></table></figure><h2 id="约束规则"><a href="#约束规则" class="headerlink" title="约束规则"></a>约束规则</h2><h3 id="MUST-DO"><a href="#MUST-DO" class="headerlink" title="MUST DO"></a>MUST DO</h3><ul><li>必须输出到 <code>docs/ specs/&lt;feature-name&gt;/requirement.md</code></li><li>必须区分 P0&#x2F;P1&#x2F;P2 优先级</li><li>必须包含验收标准</li></ul><h3 id="MUST-NOT-DO"><a href="#MUST-NOT-DO" class="headerlink" title="MUST NOT DO"></a>MUST NOT DO</h3><ul><li>不要修改任何源代码文件</li><li>不要输出代码实现</li><li>不要执行终端命令</li></ul><h2 id="验证标准"><a href="#验证标准" class="headerlink" title="验证标准"></a>验证标准</h2><ul><li><input disabled="" type="checkbox"> 功能点列表完整，无遗漏</li><li><input disabled="" type="checkbox"> 每个功能点都有明确的验收标准</li><li><input disabled="" type="checkbox"> 边界条件已覆盖空数据、错误、异常情况</li><li><input disabled="" type="checkbox"> 输出文件符合格式要求</li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li>项目 README: <code>README.md</code></li><li>现有功能规格示例: <code>docs/specs/example.md</code></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">### 示例二：测试生成 Skill</span><br><span class="line"></span><br><span class="line">&amp;emsp;&amp;emsp;这个 Skill 负责为已有组件或功能生成测试文件。它读源码，写测试，不修改业务代码。</span><br><span class="line"></span><br></pre></td></tr></table></figure><h1 id="Test-Generation-Skill"><a href="#Test-Generation-Skill" class="headerlink" title="Test Generation Skill"></a>Test Generation Skill</h1><h2 id="Metadata"><a href="#Metadata" class="headerlink" title="Metadata"></a>Metadata</h2><ul><li><strong>名称</strong>: test-generation</li><li><strong>适用场景</strong>: 为一个已有的组件、Hook、工具函数生成单元测试</li><li><strong>前置条件</strong>: 目标文件的路径已确定</li><li><strong>输出产物</strong>: <code>src/**/__tests__/&lt;name&gt;.spec.ts</code> 或 <code>.test.ts</code></li></ul><h2 id="执行步骤"><a href="#执行步骤" class="headerlink" title="执行步骤"></a>执行步骤</h2><ol><li><p><strong>读取目标文件</strong></p><ul><li>读取完整的目标源码文件</li><li>识别导出的函数、组件、类型</li></ul></li><li><p><strong>分析测试需求</strong></p><ul><li>对于函数：参数类型、返回值类型、边界条件</li><li>对于组件：Props、Slots、Emits、状态变化</li><li>对于 Hook：输入参数、返回状态、副作用清理</li></ul></li><li><p><strong>生成测试文件</strong></p><ul><li>在 <code>__tests__/</code> 目录下创建对应的测试文件</li><li>测试文件命名规则：<code>&lt;原名&gt;.spec.ts</code></li></ul></li><li><p><strong>编写测试用例</strong></p><ul><li>至少覆盖以下场景：<ul><li>正常渲染&#x2F;调用（Happy Path）</li><li>空状态（无数据&#x2F;null&#x2F;undefined）</li><li>错误处理（异常输入、失败回调）</li><li>边界条件（极限值、特殊字符）</li></ul></li></ul></li></ol><h2 id="约束规则-1"><a href="#约束规则-1" class="headerlink" title="约束规则"></a>约束规则</h2><h3 id="MUST-DO-1"><a href="#MUST-DO-1" class="headerlink" title="MUST DO"></a>MUST DO</h3><ul><li>测试文件必须放在 <code>__tests__/</code> 目录下</li><li>使用项目已有的测试框架和工具（从 <code>package.json</code> 中识别）</li><li>每个测试用例必须有明确的 <code>describe</code> 和 <code>it</code> 描述</li><li>测试描述使用中文</li></ul><h3 id="MUST-NOT-DO-1"><a href="#MUST-NOT-DO-1" class="headerlink" title="MUST NOT DO"></a>MUST NOT DO</h3><ul><li>不要修改被测源文件</li><li>不要安装新的测试依赖</li><li>不要生成集成测试或 E2E 测试（那是别的 Skill 的工作）</li></ul><h2 id="验证标准-1"><a href="#验证标准-1" class="headerlink" title="验证标准"></a>验证标准</h2><ul><li><input disabled="" type="checkbox"> 测试文件编译无类型错误</li><li><input disabled="" type="checkbox"> 测试运行全部通过（<code>npm run test -- --run</code>）</li><li><input disabled="" type="checkbox"> 覆盖了正常路径、空状态、错误处理、边界条件</li><li><input disabled="" type="checkbox"> 没有修改任何业务代码</li></ul><h2 id="参考-1"><a href="#参考-1" class="headerlink" title="参考"></a>参考</h2><ul><li>项目测试配置: <code>vitest.config.ts</code></li><li>现有测试示例: <code>src/components/__tests__/example.spec.ts</code></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">### 示例三：代码审查 Skill</span><br><span class="line"></span><br><span class="line">&amp;emsp;&amp;emsp;这个 Skill 负责审查代码变更，从多个维度评估代码质量并输出审查报告。</span><br><span class="line"></span><br></pre></td></tr></table></figure><h1 id="Code-Review-Skill"><a href="#Code-Review-Skill" class="headerlink" title="Code Review Skill"></a>Code Review Skill</h1><h2 id="Metadata-1"><a href="#Metadata-1" class="headerlink" title="Metadata"></a>Metadata</h2><ul><li><strong>名称</strong>: code-review</li><li><strong>适用场景</strong>: 一个功能开发完成，需要审查代码质量后再提交</li><li><strong>前置条件</strong>: 代码变更已在工作区中，未提交</li><li><strong>输出产物</strong>: <code>docs/reviews/&lt;feature-name&gt;-review.md</code></li></ul><h2 id="执行步骤-1"><a href="#执行步骤-1" class="headerlink" title="执行步骤"></a>执行步骤</h2><ol><li><p><strong>获取变更范围</strong></p><ul><li>运行 <code>git diff --name-only</code> 获取变更文件列表</li><li>运行 <code>git diff --stat</code> 了解变更规模</li></ul></li><li><p><strong>逐文件审查</strong></p><ul><li>对于每个变更文件，依次检查：</li></ul></li><li><p><strong>检查维度</strong></p><ul><li><strong>类型安全</strong>：是否有 <code>any</code> 类型？是否有未使用的变量？</li><li><strong>错误处理</strong>：异步操作是否有 try&#x2F;catch？边界情况是否有防御？</li><li><strong>性能</strong>：是否有不必要的重渲染？是否有内存泄漏风险？</li><li><strong>可维护性</strong>：函数是否过长？是否有重复代码？命名是否清晰？</li><li><strong>安全</strong>：是否有 XSS 风险？是否有敏感信息泄露？</li></ul></li><li><p><strong>输出审查报告</strong></p><ul><li>使用以下格式写入 <code>docs/reviews/&lt;feature-name&gt;-review.md</code></li></ul></li></ol><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># &lt;功能名称&gt; 代码审查报告</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 概述</span></span><br><span class="line"><span class="bullet">-</span> 审查者：[Skill 名称]</span><br><span class="line"><span class="bullet">-</span> 变更范围：[文件数量] files changed</span><br><span class="line"><span class="bullet">-</span> 审查结果：✅ 通过 / ⚠️ 有条件通过 / ❌ 不通过</span><br><span class="line"></span><br><span class="line"><span class="section">## 文件审查详情</span></span><br><span class="line"><span class="section">### `&lt;文件路径&gt;`</span></span><br><span class="line">| 维度 | 评级 | 问题描述 |</span><br><span class="line">|------|------|----------|</span><br><span class="line">| 类型安全 | ✅/⚠️/❌ | ... |</span><br><span class="line">| 错误处理 | ✅/⚠️/❌ | ... |</span><br><span class="line">| 性能 | ✅/⚠️/❌ | ... |</span><br><span class="line">| 可维护性 | ✅/⚠️/❌ | ... |</span><br><span class="line">| 安全 | ✅/⚠️/❌ | ... |</span><br><span class="line"></span><br><span class="line"><span class="section">## 需要修复的问题</span></span><br><span class="line"><span class="bullet">1.</span> <span class="strong">**[严重/中等/轻微]**</span> 问题描述</span><br><span class="line"><span class="bullet">2.</span> ...</span><br><span class="line"></span><br><span class="line"><span class="section">## 改进建议</span></span><br><span class="line"><span class="bullet">-</span> ...</span><br></pre></td></tr></table></figure><h2 id="约束规则-2"><a href="#约束规则-2" class="headerlink" title="约束规则"></a>约束规则</h2><h3 id="MUST-DO-2"><a href="#MUST-DO-2" class="headerlink" title="MUST DO"></a>MUST DO</h3><ul><li>每个变更文件都必须审查</li><li>问题必须标注严重等级（严重&#x2F;中等&#x2F;轻微）</li><li>必须提供具体的改进建议，而不是笼统的批评</li></ul><h3 id="MUST-NOT-DO-2"><a href="#MUST-NOT-DO-2" class="headerlink" title="MUST NOT DO"></a>MUST NOT DO</h3><ul><li>不要修改代码（只审查，不修改）</li><li>不要运行破坏性命令</li><li>不要审查非本次变更的文件</li></ul><h2 id="验证标准-2"><a href="#验证标准-2" class="headerlink" title="验证标准"></a>验证标准</h2><ul><li><input disabled="" type="checkbox"> 所有变更文件已审查</li><li><input disabled="" type="checkbox"> 每个问题都有严重等级和修改建议</li><li><input disabled="" type="checkbox"> 报告格式完整</li></ul><h2 id="参考-2"><a href="#参考-2" class="headerlink" title="参考"></a>参考</h2><ul><li>项目代码规范: <code>docs/coding-standards.md</code></li><li>之前的审查报告: <code>docs/reviews/</code></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">---</span><br><span class="line"></span><br><span class="line">## 5.4 从 Skill 到 Workflow：AGENTS.md 的总控配置</span><br><span class="line"></span><br><span class="line">&amp;emsp;&amp;emsp;单个 Skill 解决的是&quot;怎么做一件事&quot;的问题。但实际开发中，一个功能要从需求走到上线，需要多个 Skill 串起来。这时候就需要 AGENTS.md 来当&quot;总控&quot;。</span><br><span class="line"></span><br><span class="line">&amp;emsp;&amp;emsp;AGENTS.md 的核心作用有三点：</span><br><span class="line"></span><br><span class="line">1. **定义你是谁** — Agent 的角色和核心能力</span><br><span class="line">2. **声明你会什么** — 可用的 Skill 清单</span><br><span class="line">3. **规定按什么流程工作** — Skill 的执行顺序和触发条件</span><br><span class="line"></span><br><span class="line">&amp;emsp;&amp;emsp;下面是一个完整的 AGENTS.md 示例，它定义了一个前端开发的完整工作流：</span><br><span class="line"></span><br><span class="line">```markdown</span><br><span class="line"># AGENTS.md — Agent 层级知识库</span><br><span class="line"></span><br><span class="line">你是 Sisyphus — 一个资深的 AI 赋能前端架构师。</span><br><span class="line"></span><br><span class="line">## 你的核心能力</span><br><span class="line"></span><br><span class="line">1. **需求分析** — 将模糊需求转化为结构化规格文档</span><br><span class="line">2. **技术设计** — 组件拆分、状态管理、接口设计</span><br><span class="line">3. **代码生成** — Vue 3 / React / TypeScript / Tailwind CSS</span><br><span class="line">4. **测试编写** — 单元测试、组件测试</span><br><span class="line">5. **代码审查** — 类型安全、性能、可维护性检查</span><br><span class="line"></span><br><span class="line">## 技能清单</span><br><span class="line"></span><br><span class="line">以下技能按执行顺序排列，通常按 ①→②→③→④→⑤ 的顺序执行。</span><br><span class="line"></span><br><span class="line">### ① requirement-analysis</span><br><span class="line">- **位置**: `.opencode/skills/requirement-analysis/SKILL.md`</span><br><span class="line">- **触发条件**: 收到新功能需求，或用户说&quot;帮我分析一下 X&quot;</span><br><span class="line">- **输出**: `docs/specs/&lt;feature-name&gt;/requirement.md`</span><br><span class="line">- **完成后触发**: tech-design</span><br><span class="line"></span><br><span class="line">### ② tech-design</span><br><span class="line">- **位置**: `.opencode/skills/tech-design/SKILL.md`</span><br><span class="line">- **触发条件**: requirement-analysis 完成，或用户说&quot;出一下设计方案&quot;</span><br><span class="line">- **前置条件**: 存在 `docs/specs/&lt;feature-name&gt;/requirement.md`</span><br><span class="line">- **输入**: 读取 requirements.md</span><br><span class="line">- **输出**: `docs/specs/&lt;feature-name&gt;/design.md`</span><br><span class="line">- **完成后触发**: coding</span><br><span class="line"></span><br><span class="line">### ③ coding</span><br><span class="line">- **位置**: `.opencode/skills/coding/SKILL.md`</span><br><span class="line">- **触发条件**: tech-design 完成，或用户说&quot;开始编码&quot;</span><br><span class="line">- **前置条件**: 存在 `docs/specs/&lt;feature-name&gt;/design.md`</span><br><span class="line">- **输入**: 读取 design.md 和 requirements.md</span><br><span class="line">- **输出**: 源代码文件变更</span><br><span class="line">- **完成后触发**: test-generation</span><br><span class="line"></span><br><span class="line">### ④ test-generation</span><br><span class="line">- **位置**: `.opencode/skills/test-generation/SKILL.md`</span><br><span class="line">- **触发条件**: coding 完成，或用户说&quot;加测试&quot;</span><br><span class="line">- **前置条件**: 源代码已变更</span><br><span class="line">- **输出**: 测试文件</span><br><span class="line">- **完成后触发**: code-review</span><br><span class="line"></span><br><span class="line">### ⑤ code-review</span><br><span class="line">- **位置**: `.opencode/skills/code-review/SKILL.md`</span><br><span class="line">- **触发条件**: test-generation 完成，或用户说&quot;审查代码&quot;</span><br><span class="line">- **前置条件**: 测试通过</span><br><span class="line">- **输出**: `docs/reviews/&lt;feature-name&gt;-review.md`</span><br><span class="line"></span><br><span class="line">## 行为规则</span><br><span class="line"></span><br><span class="line">1. **流程优先** — 任何功能开发，默认按 ①→②→③→④→⑤ 顺序执行</span><br><span class="line">2. **可跳过** — 如果某个 Skill 不适用（如纯 Bug 修复不需要需求分析），用户说&quot;跳过 X&quot;即跳过</span><br><span class="line">3. **可循环** — 如果 code-review 未通过，回到 coding 步骤修复问题</span><br><span class="line">4. **验证驱动** — 每个 Skill 完成后必须运行自身的验证标准</span><br><span class="line">5. **人在环路** — 关键决策点（架构选型、技术方案）输出后等待人工确认</span><br><span class="line"></span><br><span class="line">## 工作流示意</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>用户需求<br>    │<br>    ▼<br>① requirement-analysis ──→ docs&#x2F;specs&#x2F;requirement.md<br>    │<br>    ▼<br>② tech-design ──→ docs&#x2F;specs&#x2F;design.md<br>    │<br>    ▼<br>③ coding ──→ 源代码变更<br>    │<br>    ▼<br>④ test-generation ──→ 测试文件<br>    │<br>    ▼<br>⑤ code-review ──→ docs&#x2F;reviews&#x2F;review.md<br>    │<br>    ├── ✅ 通过 → 完成，可供提交<br>    └── ❌ 不通过 → 返回 ③ 修复</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">## 参考文档</span><br><span class="line"></span><br><span class="line">- 项目文档: `docs/`</span><br><span class="line">- 组件设计规范: `docs/component-guidelines.md`</span><br><span class="line">- 代码风格: `.eslintrc.cjs`</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;这份 AGENTS.md 就是 Agent 的”总设定”。Agent 启动后读取它，就知道：</p><ul><li>“我是一个前端架构师”</li><li>“我手上有 5 个 Skill”</li><li>“处理功能的流程是分析→设计→编码→测试→审查”</li><li>“每个步骤结束后自动触发下一个”</li></ul><p>&emsp;&emsp;你不需要再说”先分析需求”——AGENTS.md 已经替你说好了。你要做的只是告诉 Agent：”实现搜索功能”。然后它就会自动走完整个工作流。</p><hr><h2 id="5-5-Agent-如何”自动决策”工具和-MCP"><a href="#5-5-Agent-如何”自动决策”工具和-MCP" class="headerlink" title="5.5 Agent 如何”自动决策”工具和 MCP"></a>5.5 Agent 如何”自动决策”工具和 MCP</h2><p>&emsp;&emsp;到这里你可能有一个疑问：Skill 只规定了”做什么步骤”和”不能做什么”，但它没有规定具体用什么工具来执行。Agent 怎么知道该用什么？</p><p>&emsp;&emsp;<strong>这就是 Skill 设计的精妙之处——它只规定”什么”，不规定”怎么”。</strong></p><p>&emsp;&emsp;举个例子，看前面 test-generation Skill 中的一条指令：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">编写测试用例：</span><br><span class="line">- 正常渲染/调用（Happy Path）</span><br><span class="line">- 空状态（无数据/null/undefined）</span><br><span class="line">- 错误处理（异常输入、失败回调）</span><br><span class="line">- 边界条件（极限值、特殊字符）</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;这条指令没有说”用 Vitest 的 <code>describe</code> 和 <code>it</code>“，也没有说”运行 <code>npm run test</code>“。Agent 加载 SKILL.md 后，会<strong>自主决策</strong>：</p><ol><li><strong>读项目配置</strong> — <code>package.json</code> 中发现测试框架是 Vitest</li><li><strong>读现有测试</strong> — 发现项目中使用 <code>describe</code> &#x2F; <code>it</code> &#x2F; <code>expect</code> 模式</li><li><strong>选择工具</strong> — 决定用 Vitest 的 API 编写测试</li><li><strong>执行验证</strong> — 运行 <code>npm run test -- --run</code> 来确认测试通过</li></ol><p>&emsp;&emsp;再举一个更直观的例子。假设你有一个 Skill 要求”验证 UI 表现”：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">验证步骤：</span><br><span class="line">3. 打开浏览器检查页面渲染效果</span><br><span class="line">   - 确认组件在不同视图尺寸下的布局</span><br><span class="line">   - 确认交互动效正常</span><br><span class="line">   - 截图保存到 docs/screenshots/</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;Agent 加载这条指令后，会做以下决策：</p><ol><li><strong>分析需求</strong> — “打开浏览器” → 需要浏览器自动化工具</li><li><strong>扫描可用 MCP</strong> — 发现 <code>opencode.json</code> 中配置了 Playwright MCP</li><li><strong>调用 MCP</strong> — 通过 Playwright MCP 启动浏览器、导航到页面、截图</li><li><strong>保存结果</strong> — 将截图写入 <code>docs/screenshots/</code></li></ol><p>&emsp;&emsp;这个过程完全不需要你告诉 Agent “用 Playwright MCP”。它从 Skill 的步骤描述中推断出需要浏览器操作，然后自主选择可用的 MCP Server 来完成。</p><p>&emsp;&emsp;这种设计带来了极大的灵活性。同样的 Skill，在不同环境中可能使用不同的工具：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Skill 说：&quot;读取数据库中的数据&quot;</span><br><span class="line">├── 开发环境 → Agent 选择 SQLite MCP</span><br><span class="line">├── 测试环境 → Agent 选择测试数据库 MCP</span><br><span class="line">└── 生产环境 → Agent 选择只读副本 MCP（因为 MUST NOT 中禁止了写操作）</span><br><span class="line"></span><br><span class="line">Skill 说：&quot;发起 PR&quot;</span><br><span class="line">├── GitHub 项目 → Agent 选择 GitHub MCP</span><br><span class="line">└── GitLab 项目 → Agent 选择 GitLab MCP</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;把”工具选择”的决策权交给 Agent，Skill 就不需要绑定具体的工具实现。这使得 Skill 在不同的项目、不同的技术栈中都能复用。</p><p>&emsp;&emsp;MCP 在其中扮演的角色类似于”设备驱动”。Agent 是操作系统，SKILL.md 是应用程序，MCP Server 就是驱动程序：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Agent（操作系统）</span><br><span class="line">  ├── SKILL.md（应用程序）→ 规定步骤和约束</span><br><span class="line">  ├── 读文件工具（内置驱动）</span><br><span class="line">  ├── 写文件工具（内置驱动）</span><br><span class="line">  ├── 执行命令工具（内置驱动）</span><br><span class="line">  ├── Playwright MCP（浏览器驱动）</span><br><span class="line">  ├── GitHub MCP（代码仓库驱动）</span><br><span class="line">  └── 其他 MCP（自定义驱动）</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;<strong>这就是你想要的——给 Agent 总设定和工作流，它自行做决策、选工具、调 MCP，自动完成整个开发流程。</strong></p><hr><h2 id="5-6-实战：构建一个完整的-Skill-工作流"><a href="#5-6-实战：构建一个完整的-Skill-工作流" class="headerlink" title="5.6 实战：构建一个完整的 Skill 工作流"></a>5.6 实战：构建一个完整的 Skill 工作流</h2><p>&emsp;&emsp;理论讲完了，来看一个真实场景。假设我们要给一个电商项目添加”商品搜索功能”。我们一步步看 Agent 如何利用前面设计的 Skill 工作流自动完成任务。</p><h3 id="Step-1：触发需求分析"><a href="#Step-1：触发需求分析" class="headerlink" title="Step 1：触发需求分析"></a>Step 1：触发需求分析</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">用户输入： &quot;给商城加一个商品搜索功能，支持关键词搜索和分类筛选&quot;</span><br><span class="line"></span><br><span class="line">Agent → 读取 AGENTS.md</span><br><span class="line">Agent → 识别这是一个&quot;新功能需求&quot;</span><br><span class="line">Agent → 加载 requirement-analysis Skill</span><br><span class="line">Agent → 按 Skill 步骤执行：</span><br><span class="line"></span><br><span class="line">  1. 读取上下文 → 读取项目 README 和现有页面结构</span><br><span class="line">  2. 需求拆解 → 列出功能点：</span><br><span class="line">     - P0: 搜索输入框（支持关键词输入）</span><br><span class="line">     - P0: 搜索结果列表展示</span><br><span class="line">     - P0: 按分类筛选（下拉选择）</span><br><span class="line">     - P1: 搜索历史记录</span><br><span class="line">     - P2: 搜索联想/自动补全</span><br><span class="line">  3. 边界条件 → 空搜索、无结果、搜索词过长</span><br><span class="line">  4. 输出 → docs/specs/search/requirement.md ✅</span><br><span class="line"></span><br><span class="line">Agent → requirement-analysis 验证通过</span><br><span class="line">Agent → 自动触发下一个 Skill：tech-design</span><br></pre></td></tr></table></figure><h3 id="Step-2：技术设计"><a href="#Step-2：技术设计" class="headerlink" title="Step 2：技术设计"></a>Step 2：技术设计</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Agent → 加载 tech-design Skill</span><br><span class="line">Agent → 读取 requirement.md 作为输入</span><br><span class="line">Agent → 按 Skill 步骤执行：</span><br><span class="line"></span><br><span class="line">  1. 组件拆分：</span><br><span class="line">     - SearchBar（搜索输入框 + 分类筛选）</span><br><span class="line">     - SearchResults（结果列表）</span><br><span class="line">     - SearchFilter（筛选面板）</span><br><span class="line">     - SearchHistory（搜索历史）</span><br><span class="line">  2. 状态管理设计：</span><br><span class="line">     - useSearch(params) → 搜索请求</span><br><span class="line">     - useSearchHistory() → 本地存储</span><br><span class="line">  3. 接口设计：</span><br><span class="line">     - GET /api/products/search?q=&amp;category=&amp;page=</span><br><span class="line">  4. 输出 → docs/specs/search/design.md ✅</span><br><span class="line"></span><br><span class="line">Agent → tech-design 验证通过</span><br><span class="line">Agent → 输出设计文档后等待人工确认</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;这里注意：AGENTS.md 中设置了”人在环路”，所以 Agent 不会直接进入编码，而是先把设计方案给你看。你确认后告诉它”可以开始编码”。</p><h3 id="Step-3：编码实现"><a href="#Step-3：编码实现" class="headerlink" title="Step 3：编码实现"></a>Step 3：编码实现</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">用户确认： &quot;可以了，开始编码&quot;</span><br><span class="line"></span><br><span class="line">Agent → 加载 coding Skill</span><br><span class="line">Agent → 读取 design.md</span><br><span class="line">Agent → 按 Skill 步骤执行：</span><br><span class="line"></span><br><span class="line">  1. 创建 SearchBar 组件 → src/components/search/SearchBar.tsx</span><br><span class="line">  2. 创建 SearchResults 组件 → src/components/search/SearchResults.tsx</span><br><span class="line">  3. 创建 useSearch Hook → src/hooks/useSearch.ts</span><br><span class="line">  4. 创建 API 层 → src/api/search.ts</span><br><span class="line">  5. 创建类型定义 → src/types/search.ts</span><br><span class="line">  6. 注册路由 → src/router/index.ts</span><br><span class="line"></span><br><span class="line">Agent → coding 验证通过（LSP 零错误）</span><br><span class="line">Agent → 自动触发 test-generation</span><br></pre></td></tr></table></figure><h3 id="Step-4：生成测试"><a href="#Step-4：生成测试" class="headerlink" title="Step 4：生成测试"></a>Step 4：生成测试</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Agent → 加载 test-generation Skill</span><br><span class="line">Agent → 读取 coding 产出的源文件</span><br><span class="line">Agent → 按 Skill 步骤执行：</span><br><span class="line"></span><br><span class="line">  1. 分析 useSearch Hook → 生成 src/hooks/__tests__/useSearch.spec.ts</span><br><span class="line">  2. 分析 SearchBar 组件 → 生成 src/components/search/__tests__/SearchBar.spec.ts</span><br><span class="line">  3. 分析 SearchResults 组件 → 生成 __tests__/SearchResults.spec.ts</span><br><span class="line"></span><br><span class="line">Agent → 运行 npm run test -- --run → ✅ All tests passed</span><br><span class="line">Agent → test-generation 验证通过</span><br><span class="line">Agent → 自动触发 code-review</span><br></pre></td></tr></table></figure><h3 id="Step-5：代码审查"><a href="#Step-5：代码审查" class="headerlink" title="Step 5：代码审查"></a>Step 5：代码审查</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Agent → 加载 code-review Skill</span><br><span class="line">Agent → 运行 git diff --name-only 获取变更范围</span><br><span class="line">Agent → 逐文件检查：</span><br><span class="line"></span><br><span class="line">  SearchBar.tsx:       类型安全 ✅ | 错误处理 ✅ | 性能 ✅ | 可维护性 ✅ | 安全 ✅</span><br><span class="line">  SearchResults.tsx:   类型安全 ✅ | 错误处理 ⚠️ | 性能 ✅ | 可维护性 ✅ | 安全 ✅</span><br><span class="line">  useSearch.ts:        类型安全 ✅ | 错误处理 ✅ | 性能 ✅ | 可维护性 ✅ | 安全 ✅</span><br><span class="line">  search.ts (API):     类型安全 ✅ | 错误处理 ✅ | 性能 ✅ | 可维护性 ✅ | 安全 ✅</span><br><span class="line"></span><br><span class="line">Agent → 发现 SearchResults 在空数据时缺少友好的空状态提示</span><br><span class="line">Agent → 标注为&quot;中等&quot;问题，提供修改建议</span><br><span class="line">Agent → 自动回到 coding 步骤修复此问题</span><br><span class="line">Agent → 修复后重新运行 test-generation → ✅</span><br><span class="line">Agent → 重新运行 code-review → ✅ 全部通过</span><br><span class="line">Agent → 输出审查报告 docs/reviews/search-review.md ✅</span><br></pre></td></tr></table></figure><h3 id="完整工作流示意图"><a href="#完整工作流示意图" class="headerlink" title="完整工作流示意图"></a>完整工作流示意图</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">用户: &quot;给商城加商品搜索功能&quot;</span><br><span class="line">  │</span><br><span class="line">  ▼</span><br><span class="line">┌──────────────────────────────────────────────────────────┐</span><br><span class="line">│ ① requirement-analysis  Skill                            │</span><br><span class="line">│    读取项目背景 → 拆解功能点 → 识别边界条件               │</span><br><span class="line">│    输出: docs/specs/search/requirement.md                 │</span><br><span class="line">└────────────────────────────────┬─────────────────────────┘</span><br><span class="line">                                 │ 自动触发</span><br><span class="line">                                 ▼</span><br><span class="line">┌──────────────────────────────────────────────────────────┐</span><br><span class="line">│ ② tech-design  Skill                                     │</span><br><span class="line">│    读取 requirement.md                                   │</span><br><span class="line">│    组件拆分 → 接口设计 → 状态管理方案                      │</span><br><span class="line">│    输出: docs/specs/search/design.md                      │</span><br><span class="line">└────────────────────────────────┬─────────────────────────┘</span><br><span class="line">                                 │ 人工确认</span><br><span class="line">                                 ▼</span><br><span class="line">┌──────────────────────────────────────────────────────────┐</span><br><span class="line">│ ③ coding  Skill                                          │</span><br><span class="line">│    读取 design.md                                        │</span><br><span class="line">│    创建组件 → 创建 Hook → 创建 API → 注册路由              │</span><br><span class="line">│    输出: 源代码变更                                        │</span><br><span class="line">└────────────────────────────────┬─────────────────────────┘</span><br><span class="line">                                 │ 自动触发</span><br><span class="line">                                 ▼</span><br><span class="line">┌──────────────────────────────────────────────────────────┐</span><br><span class="line">│ ④ test-generation  Skill                                 │</span><br><span class="line">│    读取源文件 → 生成单元测试 → 运行测试                     │</span><br><span class="line">│    输出: __tests__/*.spec.ts                              │</span><br><span class="line">└────────────────────────────────┬─────────────────────────┘</span><br><span class="line">                                 │ 自动触发</span><br><span class="line">                                 ▼</span><br><span class="line">┌──────────────────────────────────────────────────────────┐</span><br><span class="line">│ ⑤ code-review  Skill                                     │</span><br><span class="line">│    逐文件审查 → 发现问题 → 修复 → 重新验证                  │</span><br><span class="line">│    输出: docs/reviews/search-review.md                    │</span><br><span class="line">└────────────────────────────────┬─────────────────────────┘</span><br><span class="line">                                 │</span><br><span class="line">                                 ▼</span><br><span class="line">                    ✅ 功能完成，可供提交</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;整个过程，你只说了两句话：</p><ol><li>“给商城加一个商品搜索功能”</li><li>“可以了，开始编码”</li></ol><p>&emsp;&emsp;剩下的需求分析、技术设计、编码、测试、审查、修复，全部由 Agent 按 Skill 工作流自动完成。这还不是 AI 替你写代码——这是 AI 替你跑了一套完整的开发流程。</p><hr><h2 id="5-7-Skill-的迭代与管理"><a href="#5-7-Skill-的迭代与管理" class="headerlink" title="5.7 Skill 的迭代与管理"></a>5.7 Skill 的迭代与管理</h2><p>&emsp;&emsp;写完了 Skill，是不是就一劳永逸了？不是。Skill 和代码一样，需要持续迭代。</p><h3 id="测试一个-Skill"><a href="#测试一个-Skill" class="headerlink" title="测试一个 Skill"></a>测试一个 Skill</h3><p>&emsp;&emsp;新写的 Skill 怎么验证它好不好用？给 Agent 一个 mock 输入，观察它的表现。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">测试用例 1：requirement-analysis Skill</span><br><span class="line">  输入： &quot;帮我看一下这个需求，实现用户登录功能&quot;</span><br><span class="line">  期望行为：</span><br><span class="line">    - Agent 先读取项目结构</span><br><span class="line">    - 拆解登录功能的功能点</span><br><span class="line">    - 输出一份完整的 requirement.md</span><br><span class="line">  检查点：</span><br><span class="line">    - 是否包含了 P0/P1/P2 优先级标注？</span><br><span class="line">    - 是否包含了边界条件？</span><br><span class="line">    - Agent 有没有修改代码？（不应该）</span><br><span class="line"></span><br><span class="line">测试用例 2：test-generation Skill</span><br><span class="line">  输入： &quot;给 src/hooks/useAuth.ts 生成测试&quot;</span><br><span class="line">  期望行为：</span><br><span class="line">    - Agent 读取 useAuth.ts 源码</span><br><span class="line">    - 在 __tests__/ 下生成 useAuth.spec.ts</span><br><span class="line">    - 运行测试并验证通过</span><br><span class="line">  检查点：</span><br><span class="line">    - 测试是否覆盖了正常/异常/边界？</span><br><span class="line">    - 是否保持了原有业务代码不变？</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;如果某个测试用例中 Agent 的行为不符合预期，说明 Skill 的指令不够清晰或约束不够严格。这时候需要迭代 Skill 文件。</p><h3 id="Skill-版本管理"><a href="#Skill-版本管理" class="headerlink" title="Skill 版本管理"></a>Skill 版本管理</h3><p>&emsp;&emsp;Skill 文件应该放在项目的 <code>.opencode/skills/</code> 目录下，通过 Git 管理版本：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">.opencode/</span><br><span class="line">├── AGENTS.md</span><br><span class="line">└── skills/</span><br><span class="line">    ├── requirement-analysis/</span><br><span class="line">    │   └── SKILL.md</span><br><span class="line">    ├── tech-design/</span><br><span class="line">    │   └── SKILL.md</span><br><span class="line">    ├── coding/</span><br><span class="line">    │   └── SKILL.md</span><br><span class="line">    ├── test-generation/</span><br><span class="line">    │   └── SKILL.md</span><br><span class="line">    └── code-review/</span><br><span class="line">        └── SKILL.md</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;这样做的好处：</p><ol><li><strong>团队共享</strong> — 所有人使用同一套 Skill，输出一致</li><li><strong>版本追溯</strong> — 通过 <code>git log</code> 可以看到 Skill 的演进历史</li><li><strong>代码审查</strong> — Skill 的变更可以走正常的 MR 流程</li><li><strong>分支隔离</strong> — 实验性 Skill 可以在分支上测试</li></ol><h3 id="什么时候需要新建-更新-Skill"><a href="#什么时候需要新建-更新-Skill" class="headerlink" title="什么时候需要新建&#x2F;更新 Skill"></a>什么时候需要新建&#x2F;更新 Skill</h3><table><thead><tr><th>场景</th><th>动作</th><th>原因</th></tr></thead><tbody><tr><td>一个流程反复手动操作 3 次以上</td><td>新建 Skill</td><td>值得固化为自动化流程</td></tr><tr><td>Agent 执行某个步骤总是出错</td><td>更新 Skill</td><td>指令不够清晰，需要细化</td></tr><tr><td>技术栈升级了</td><td>更新 Skill</td><td>约束条件和技术参考需要更新</td></tr><tr><td>团队制定了新的规范</td><td>更新 Skill</td><td>验证标准需要更新</td></tr><tr><td>发现某个步骤可以优化</td><td>更新 Skill</td><td>流程优化应当及时反映在 Skill 中</td></tr><tr><td>新项目使用不同的框架</td><td>新建 Skill</td><td>编码方式不同，需要专门的 Skill</td></tr></tbody></table><p>&emsp;&emsp;一个实用建议：<strong>刚上手时不要追求完美，先写最简可用版本（MVP Skill）</strong>。只包含核心步骤和关键约束，然后在实际使用中逐步完善。比花一周时间设计一个”完美”的 Skill 更有价值。</p><hr><h2 id="5-8-总结"><a href="#5-8-总结" class="headerlink" title="5.8 总结"></a>5.8 总结</h2><p>&emsp;&emsp;回顾整篇文章，我们从”每次都给 Agent 写 Prompt”的困境出发，提出了一个更系统化的解决方案：<strong>把开发流程拆解成可复用的 Skill，在 AGENTS.md 中编排为完整 Workflow，让 Agent 自主决策工具和 MCP，自动完成整个开发流程。</strong></p><p>&emsp;&emsp;这个转变的意义在于：</p><ul><li><strong>从”使用者”变成”设计者”</strong> — 你不再每次告诉 Agent 怎么做，而是设计一套让 Agent 自己知道怎么做的体系</li><li><strong>从”单次对话”变成”永久资产”</strong> — Prompt 是一次性的，Skill 是随着项目积累的</li><li><strong>从”个人经验”变成”团队能力”</strong> — Skill 可以共享、审查、迭代，成为团队的工程资产</li></ul><p>&emsp;&emsp;最后分享一组公式，我觉得可以概括 Agent 工作流的精髓：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Agent 的执行质量 ≈ Skills 的质量 × Workflow 的设计</span><br><span class="line"></span><br><span class="line">Skills 的质量 = 指令清晰度 × 约束完整度 × 可验证性</span><br><span class="line">Workflow 的设计 = 流程完整性 × 衔接流畅度 × 容错能力</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;你的 Skill 写得越清晰、约束越完整、验证越严格，Agent 的执行就越可靠。你的 Workflow 设计得越完整、衔接越流畅、容错越完善，Agent 就越能独立完成复杂任务。</p><p>&emsp;&emsp;<strong>这个时代最大的杠杆，不是你用 AI 写了多少代码，而是你设计了多少能让 AI 自动运转的流程。</strong></p><p>&emsp;&emsp;下一章我们会聊到深度使用 AI 时一定会遇到的坑——AI 幻觉、安全风险、过度依赖等问题，以及对应的防御策略。从设计到避坑，才算完整的 Agent 工作流闭环。</p>]]>
    </content>
    <id>https://github.com/wz71014q/2026/03/15/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-Agent%E5%B7%A5%E4%BD%9C%E6%B5%81/</id>
    <link href="https://github.com/wz71014q/2026/03/15/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-Agent%E5%B7%A5%E4%BD%9C%E6%B5%81/"/>
    <published>2026-03-15T02:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="AI时代前端工程师实战指南：Agent工作流"><a href="#AI时代前端工程师实战指南：Agent工作流" class="headerlink" title="AI时代前端工程师实战指南：Agent工作流"></a>AI时代前端工程师实战指南：Agent工作流</h1><blockquote>
<p>本文定位不一样——不是教你怎么给 Agent 写 Prompt，而是教你怎么<strong>设计一套能被 Agent 自动执行的 Skill 体系</strong>。<br>把开发流程拆成可复用的指令模块，让 Agent 按工作流自动运转，你从”操作者”变成”设计者”。</p>
</blockquote>
<hr>
<h2 id="5-1-从”写-Prompt”到”设计-Skill”"><a href="#5-1-从”写-Prompt”到”设计-Skill”" class="headerlink" title="5.1 从”写 Prompt”到”设计 Skill”"></a>5.1 从”写 Prompt”到”设计 Skill”</h2><p>&emsp;&emsp;上一章我们讲了 Prompt 工程——如何写出结构清晰、约束明确的 Prompt，让 AI 输出更可靠。这是一个巨大的进步，从”随便问两句”到”系统化地写 Prompt”，已经是质的飞跃。</p>
<p>&emsp;&emsp;但用过一段时间后，你会发现一个新问题：<strong>每次都要重复写几乎一样的 Prompt</strong>。</p>
<p>&emsp;&emsp;比如你有一个开发习惯——写任何代码之前先做需求分析，然后出技术方案，再动手编码，最后加测试。这个流程你每次都要在 Prompt 里重申一遍：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">❌ 每次重复：</span><br><span class="line">&quot;先分析需求，列出所有功能点和边界条件&quot;</span><br><span class="line">&quot;然后出技术方案，考虑组件拆分和数据流&quot;</span><br><span class="line">&quot;再开始编码，遵循项目规范&quot;</span><br><span class="line">&quot;最后加测试，覆盖正常路径和异常路径&quot;</span><br></pre></td></tr></table></figure>]]>
    </summary>
    <title>AI时代前端工程师实战指南：Agent工作流</title>
    <updated>2026-05-22T05:52:32.582Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="AI时代前端工程师实战指南" scheme="https://github.com/wz71014q/categories/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="AI" scheme="https://github.com/wz71014q/tags/AI/"/>
    <category term="前端工程师" scheme="https://github.com/wz71014q/tags/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88/"/>
    <category term="实战指南" scheme="https://github.com/wz71014q/tags/%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="避坑" scheme="https://github.com/wz71014q/tags/%E9%81%BF%E5%9D%91/"/>
    <category term="安全" scheme="https://github.com/wz71014q/tags/%E5%AE%89%E5%85%A8/"/>
    <content>
      <![CDATA[<h1 id="AI时代前端工程师实战指南：避坑篇"><a href="#AI时代前端工程师实战指南：避坑篇" class="headerlink" title="AI时代前端工程师实战指南：避坑篇"></a>AI时代前端工程师实战指南：避坑篇</h1><p>深度使用过 AI 才知道的坑，以及对应的防御策略。</p><hr><h2 id="7-1-AI-幻觉：它真的知道自己在说什么吗？"><a href="#7-1-AI-幻觉：它真的知道自己在说什么吗？" class="headerlink" title="7.1 AI 幻觉：它真的知道自己在说什么吗？"></a>7.1 AI 幻觉：它真的知道自己在说什么吗？</h2><p>AI 的”知识”本质上是对训练数据的模式匹配，它不是真的”知道”某个事实。这会表现为：</p><h3 id="常见幻觉类型"><a href="#常见幻觉类型" class="headerlink" title="常见幻觉类型"></a>常见幻觉类型</h3><table><thead><tr><th>类型</th><th>表现</th><th>例子</th></tr></thead><tbody><tr><td><strong>虚构 API</strong></td><td>推荐一个不存在的库或API</td><td>“使用 <code>useEffectEvent</code> Hook”（React 19 的 RFC，还没稳定）</td></tr><tr><td><strong>错误版本</strong></td><td>引用过时的 API 用法</td><td>“Vue 3 的 <code>defineComponent</code> 是必须的”（不是必须的）</td></tr><tr><td><strong>张冠李戴</strong></td><td>把一个库的特性安到另一个库上</td><td>“React 的 v-model 指令”（那是 Vue 的）</td></tr><tr><td><strong>过度承诺</strong></td><td>说某个功能可以实现但实际上不行</td><td>“使用 Service Worker 可以实现实时推送”（不完全是）</td></tr></tbody></table><h3 id="防御策略"><a href="#防御策略" class="headerlink" title="防御策略"></a>防御策略</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">1. 让 AI 自己标注置信度</span><br><span class="line">   在 Prompt 末尾加一句：</span><br><span class="line">   &quot;对于你提到的库、API、版本号，请标注你的确定程度：</span><br><span class="line">   ✅ 确认存在且版本正确 / ⚠️ 比较确定但建议验证 / ❌ 推测&quot;</span><br><span class="line"></span><br><span class="line">2. 用 @Docs 和 @Web 查证</span><br><span class="line">   在 Cursor 中输入 @Docs React 确认 API 是否存在</span><br><span class="line">   或在 Prompt 中说 &quot;请在回答前用 @Web 搜索确认&quot;</span><br><span class="line"></span><br><span class="line">3. 关键信息独立验证</span><br><span class="line">   AI 推荐的 npm 包 → 去 npm 官网看下载量和最后更新时间</span><br><span class="line">   AI 推荐的 API → 去官方文档看</span><br><span class="line">   AI 生成的配置 → 手动跑一遍确认</span><br><span class="line"></span><br><span class="line">4. 经验法则</span><br><span class="line">   - AI 关于&quot;流行度&quot;和&quot;推荐&quot;的说法要打折</span><br><span class="line">   - AI 给出的版本号和日期，大概率是错的</span><br><span class="line">   - AI 说 &quot;这个很简单&quot; 时，通常它没考虑完整</span><br></pre></td></tr></table></figure><hr><h2 id="7-2-安全红线：AI-无心但有毒"><a href="#7-2-安全红线：AI-无心但有毒" class="headerlink" title="7.2 安全红线：AI 无心但有毒"></a>7.2 安全红线：AI 无心但有毒</h2><h3 id="代码泄露风险"><a href="#代码泄露风险" class="headerlink" title="代码泄露风险"></a>代码泄露风险</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">❌ 不要把以下内容发给 AI：</span><br><span class="line">- 数据库密码、API Key、Token</span><br><span class="line">- 客户的敏感数据（手机号、身份证、地址）</span><br><span class="line">- 公司的核心业务逻辑（特别是闭源项目）</span><br><span class="line">- 未公开的产品功能</span><br><span class="line"></span><br><span class="line">✅ 发给 AI 前的处理：</span><br><span class="line">- 用占位符替换敏感信息（如 password: &quot;xxx&quot;）</span><br><span class="line">- 代码中的 URL 替换为通用格式（如 /api/v1/xxx）</span><br><span class="line">- 业务数据用测试数据替代</span><br></pre></td></tr></table></figure><h3 id="AI-推荐的安全隐患"><a href="#AI-推荐的安全隐患" class="headerlink" title="AI 推荐的安全隐患"></a>AI 推荐的安全隐患</h3><p>AI 可能推荐不安全的做法：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">场景：AI 说&quot;用 innerHTML 直接插入用户评论内容即可&quot;</span><br><span class="line">→ 这是 XSS 攻击的典型入口</span><br><span class="line">→ 正确做法：用 textContent 或 DOMPurify 过滤</span><br><span class="line"></span><br><span class="line">场景：AI 说&quot;密码直接存 localStorage&quot;</span><br><span class="line">→ localStorage 可以被 XSS 读取</span><br><span class="line">→ 正确做法：HttpOnly Cookie + 后端 session</span><br><span class="line"></span><br><span class="line">场景：AI 说&quot;eval 很方便&quot;</span><br><span class="line">→ 永远不用 eval，AI 不知道你的安全上下文</span><br></pre></td></tr></table></figure><h3 id="防御策略-1"><a href="#防御策略-1" class="headerlink" title="防御策略"></a>防御策略</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">建立&quot;AI 安全审查&quot;意识：</span><br><span class="line">收到 AI 的代码 → 先过一遍脑中的安全检查清单：</span><br><span class="line"></span><br><span class="line">□ 有没有用户输入直接拼接到 DOM 中？</span><br><span class="line">□ 有没有敏感信息可能被暴露？</span><br><span class="line">□ 有没有引入第三方 CDN 资源？（供应链攻击风险）</span><br><span class="line">□ 有没有使用不安全的 JS API（eval、document.write）？</span><br><span class="line">□ 有没有绕过认证或权限控制的逻辑？</span><br></pre></td></tr></table></figure><hr><h2 id="7-3-过度依赖：当拐杖变成轮椅"><a href="#7-3-过度依赖：当拐杖变成轮椅" class="headerlink" title="7.3 过度依赖：当拐杖变成轮椅"></a>7.3 过度依赖：当拐杖变成轮椅</h2><h3 id="危险信号"><a href="#危险信号" class="headerlink" title="危险信号"></a>危险信号</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">以下信号说明你可能过度依赖 AI 了：</span><br><span class="line"></span><br><span class="line">⚠️ 没有 AI 就写不了代码，遇到问题第一反应是问 AI 而不是自己思考</span><br><span class="line">⚠️ AI 生成的代码读不懂就部署上去了</span><br><span class="line">⚠️ 同样的简单 Bug 反复出现，没有形成自己的知识体系</span><br><span class="line">⚠️ 对项目代码的整体架构缺乏理解，只知道自己写的部分</span><br></pre></td></tr></table></figure><h3 id="合理的使用边界"><a href="#合理的使用边界" class="headerlink" title="合理的使用边界"></a>合理的使用边界</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">用 AI 做                    | 不要用 AI 做</span><br><span class="line">---------------------------|--------------------------</span><br><span class="line">生成样板代码和重复劳动      | 核心业务逻辑</span><br><span class="line">生成测试用例               | 安全敏感代码</span><br><span class="line">辅助 Debug 提供思路         | 不经审查就部署</span><br><span class="line">生成文档和注释              | 架构决策</span><br><span class="line">Code Review 辅助            | 性能关键路径的初次实现</span><br><span class="line">学习新技术的辅助工具        | 替代问题解决能力</span><br></pre></td></tr></table></figure><h3 id="保持能力的策略"><a href="#保持能力的策略" class="headerlink" title="保持能力的策略"></a>保持能力的策略</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">1. &quot;先自己试 15 分钟&quot;原则</span><br><span class="line">   遇到问题，先自己尝试 15 分钟，再用 AI。这 15 分钟是保持思考能力的关键。</span><br><span class="line"></span><br><span class="line">2. &quot;必须读懂 AI 的代码&quot;原则</span><br><span class="line">   AI 生成的所有代码，必须逐行读懂了才合入。读不懂说明你自己还做不到，先学习。</span><br><span class="line"></span><br><span class="line">3. &quot;定期无 AI 日&quot;原则</span><br><span class="line">   每周抽出一天，不借助 AI 编码，保持基本功。</span><br></pre></td></tr></table></figure><hr><h2 id="7-4-什么时候不该用-AI"><a href="#7-4-什么时候不该用-AI" class="headerlink" title="7.4 什么时候不该用 AI"></a>7.4 什么时候不该用 AI</h2><table><thead><tr><th>场景</th><th>为什么不该用</th><th>应该怎么做</th></tr></thead><tbody><tr><td><strong>刚接手新项目</strong></td><td>需要自己理解代码结构，AI 给的是二手认知</td><td>先自己读一遍核心代码，再用 AI 验证理解</td></tr><tr><td><strong>排查复杂 Bug</strong></td><td>AI 可能给出猜测性的答案</td><td>先自己定位复现路径，缩小范围后再问 AI</td></tr><tr><td><strong>性能调优</strong></td><td>AI 给出的是通用建议，不是项目特化的</td><td>先用 Performance 面板实测，定位瓶颈再针对性优化</td></tr><tr><td><strong>敏感数据处理</strong></td><td>数据泄露风险</td><td>完全自己写，或使用去敏后的数据</td></tr><tr><td><strong>面试准备</strong></td><td>用 AI 理解概念可以，但必须能脱离 AI 表达</td><td>用自己的话重述概念，做模拟面试</td></tr><tr><td><strong>架构决策</strong></td><td>AI 不理解你的团队、业务、技术债</td><td>AI 提供决策依据，你自己做决策</td></tr></tbody></table><hr><h2 id="7-5-CI-CD-中的-AI-安全实践"><a href="#7-5-CI-CD-中的-AI-安全实践" class="headerlink" title="7.5 CI&#x2F;CD 中的 AI 安全实践"></a>7.5 CI&#x2F;CD 中的 AI 安全实践</h2><p>如果团队在 CI&#x2F;CD 中使用了 AI 生成代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Code Push</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">GitHub Actions / CI</span><br><span class="line">    │</span><br><span class="line">    ├── Lint (ESLint + Prettier)</span><br><span class="line">    ├── Type Check (tsc --noEmit)</span><br><span class="line">    ├── Test (Vitest)</span><br><span class="line">    ├── Security Scan (CodeQL / SonarQube)</span><br><span class="line">    │   └── 🔴 可加一个 &quot;AI 生成代码标记&quot; 检查</span><br><span class="line">    │       如果 PR 中有疑似 AI 生成的代码模式，</span><br><span class="line">    │       标记为需要人工重点审查</span><br><span class="line">    ├── Build</span><br><span class="line">    └── Deploy</span><br></pre></td></tr></table></figure><hr><h2 id="7-6-版权与合规"><a href="#7-6-版权与合规" class="headerlink" title="7.6 版权与合规"></a>7.6 版权与合规</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">法律上需要注意的问题：</span><br><span class="line"></span><br><span class="line">1. AI 生成的代码版权归谁？</span><br><span class="line">   - 不同平台政策不同（GitHub Copilot、Cursor、Claude Code）</span><br><span class="line">   - 商业项目建议查阅具体平台的 ToS</span><br><span class="line">   - 安全做法：不直接复制 AI 生成的大段代码，理解后自己改写</span><br><span class="line"></span><br><span class="line">2. AI 可能复现有版权的代码</span><br><span class="line">   - Copilot 曾因可能复现 GPL 代码被起诉</span><br><span class="line">   - 关键业务代码建议自己写核心逻辑</span><br><span class="line">   - 开源项目中使用 AI 生成代码需要更谨慎</span><br><span class="line"></span><br><span class="line">3. 企业合规</span><br><span class="line">   - 部分企业禁止使用外部 AI 服务（数据安全）</span><br><span class="line">   - 在入职前确认公司的 AI 使用政策</span><br><span class="line">   - 涉及客户数据时严格遵循合同约定</span><br></pre></td></tr></table></figure><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>使用 AI 辅助开发是一把双刃剑。用好了，效率提升 2-10 倍；用不好，可能会引入安全隐患、形成过度依赖。</p><p>核心原则只有三条：<strong>不信任、必验证、保持思考</strong>。不信任 AI 的输出，每次生成都必须经过验证，始终保持自己的技术判断力。做到这三点，你就能在享受 AI 效率提升的同时，避免掉入这些常见的陷阱。</p>]]>
    </content>
    <id>https://github.com/wz71014q/2026/01/15/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-%E9%81%BF%E5%9D%91%E7%AF%87/</id>
    <link href="https://github.com/wz71014q/2026/01/15/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-%E9%81%BF%E5%9D%91%E7%AF%87/"/>
    <published>2026-01-15T02:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="AI时代前端工程师实战指南：避坑篇"><a href="#AI时代前端工程师实战指南：避坑篇" class="headerlink" title="AI时代前端工程师实战指南：避坑篇"></a>AI时代前端工程师实战指南：避坑篇</h1><p>深度使用过 AI 才知道的坑，以及对应的防御策略。</p>
<hr>
<h2 id="7-1-AI-幻觉：它真的知道自己在说什么吗？"><a href="#7-1-AI-幻觉：它真的知道自己在说什么吗？" class="headerlink" title="7.1 AI 幻觉：它真的知道自己在说什么吗？"></a>7.1 AI 幻觉：它真的知道自己在说什么吗？</h2><p>AI 的”知识”本质上是对训练数据的模式匹配，它不是真的”知道”某个事实。这会表现为：</p>
<h3 id="常见幻觉类型"><a href="#常见幻觉类型" class="headerlink" title="常见幻觉类型"></a>常见幻觉类型</h3><table>
<thead>
<tr>
<th>类型</th>
<th>表现</th>
<th>例子</th>
</tr>
</thead>
<tbody><tr>
<td><strong>虚构 API</strong></td>
<td>推荐一个不存在的库或API</td>
<td>“使用 <code>useEffectEvent</code> Hook”（React 19 的 RFC，还没稳定）</td>
</tr>
<tr>
<td><strong>错误版本</strong></td>
<td>引用过时的 API 用法</td>
<td>“Vue 3 的 <code>defineComponent</code> 是必须的”（不是必须的）</td>
</tr>
<tr>
<td><strong>张冠李戴</strong></td>
<td>把一个库的特性安到另一个库上</td>
<td>“React 的 v-model 指令”（那是 Vue 的）</td>
</tr>
<tr>
<td><strong>过度承诺</strong></td>
<td>说某个功能可以实现但实际上不行</td>
<td>“使用 Service Worker 可以实现实时推送”（不完全是）</td>
</tr>
</tbody></table>
<h3 id="防御策略"><a href="#防御策略" class="headerlink" title="防御策略"></a>防御策略</h3>]]>
    </summary>
    <title>AI时代前端工程师实战指南：避坑篇</title>
    <updated>2026-05-22T04:02:51.910Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="AI时代前端工程师实战指南" scheme="https://github.com/wz71014q/categories/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="AI" scheme="https://github.com/wz71014q/tags/AI/"/>
    <category term="前端工程师" scheme="https://github.com/wz71014q/tags/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88/"/>
    <category term="实战指南" scheme="https://github.com/wz71014q/tags/%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="Prompt" scheme="https://github.com/wz71014q/tags/Prompt/"/>
    <category term="工程化" scheme="https://github.com/wz71014q/tags/%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
    <content>
      <![CDATA[<h1 id="AI时代前端工程师实战指南：Prompt工程"><a href="#AI时代前端工程师实战指南：Prompt工程" class="headerlink" title="AI时代前端工程师实战指南：Prompt工程"></a>AI时代前端工程师实战指南：Prompt工程</h1><p><strong>本文目的</strong>：从”随机问 AI”到”系统化地使用 AI”，建立可复用的 Prompt 工程体系。涉及 .cursorrules、CLAUDE.md、AGENTS.md、项目级 Prompt 模板库等多种配置文件的使用。</p><hr><h2 id="4-1-为什么需要-Prompt-工程？"><a href="#4-1-为什么需要-Prompt-工程？" class="headerlink" title="4.1 为什么需要 Prompt 工程？"></a>4.1 为什么需要 Prompt 工程？</h2><p>一个简单的实验：同样的问题，不同问法，AI 的回答质量天差地别。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">❌ 差：&quot;帮我写一个搜索组件&quot;</span><br><span class="line">    → AI 生成一个最简单的搜索框，大概率不符合项目规范</span><br><span class="line"></span><br><span class="line">✅ 好：见下方结构化 Prompt</span><br></pre></td></tr></table></figure><p><strong>Prompt 工程就是在系统化地缩小 AI 的”猜测空间”</strong>。你给的信息越精确，AI 的”猜测”就越接近你的真实需求。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">AI 的猜测空间：</span><br><span class="line">  无限可能 ──────────────→ 精确匹配需求</span><br><span class="line">     ↑                          ↑</span><br><span class="line">  模糊 Prompt              结构化 Prompt</span><br><span class="line">  （&quot;写个搜索&quot;）         （带约束、上下文、格式）</span><br></pre></td></tr></table></figure><hr><h2 id="4-2-结构化-Prompt-五要素"><a href="#4-2-结构化-Prompt-五要素" class="headerlink" title="4.2 结构化 Prompt 五要素"></a>4.2 结构化 Prompt 五要素</h2><p>这是本文推荐的 Prompt 框架，适用于大多数 AI Coding 工具（Cursor、Claude Code、OpenCode、ChatGPT）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────┐</span><br><span class="line">│  ① 角色定义                              │</span><br><span class="line">│  你是一个 [角色]，精通 [领域]               │</span><br><span class="line">├─────────────────────────────────────────┤</span><br><span class="line">│  ② 上下文                                │</span><br><span class="line">│  项目背景、技术栈、相关文件                │</span><br><span class="line">├─────────────────────────────────────────┤</span><br><span class="line">│  ③ 任务描述                              │</span><br><span class="line">│  具体要做什么，输入是什么，预期输出是什么    │</span><br><span class="line">├─────────────────────────────────────────┤</span><br><span class="line">│  ④ 输出格式                              │</span><br><span class="line">│  代码/文档/列表/JSON，是否需要注释          │</span><br><span class="line">├─────────────────────────────────────────┤</span><br><span class="line">│  ⑤ 约束条件                              │</span><br><span class="line">│  风格约束、性能约束、安全约束、负面清单      │</span><br><span class="line">└─────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="完整示例"><a href="#完整示例" class="headerlink" title="完整示例"></a>完整示例</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">┌─ ① 角色 ──────────────────────────────────────────┐</span><br><span class="line">│ 你是一个资深前端工程师，精通 Vue 3 Composition API    │</span><br><span class="line">├─ ② 上下文 ──────────────────────────────────────────┤</span><br><span class="line">│ 项目使用 Vue 3 + TypeScript + Tailwind CSS           │</span><br><span class="line">│ 参考现有组件：@src/components/BaseInput.vue          │</span><br><span class="line">├─ ③ 任务 ────────────────────────────────────────────┤</span><br><span class="line">│ 实现一个防抖搜索组件 SearchInput                      │</span><br><span class="line">│ 用户在输入框输入，300ms 后调用 /api/search?q=xxx      │</span><br><span class="line">│ 显示搜索中状态、空状态、错误状态                      │</span><br><span class="line">├─ ④ 格式 ────────────────────────────────────────────┤</span><br><span class="line">│ 使用 &lt;script setup lang=&quot;ts&quot;&gt;                        │</span><br><span class="line">│ Props 和 Emits 要有完整类型定义                       │</span><br><span class="line">│ 输出单个 .vue 文件                                    │</span><br><span class="line">├─ ⑤ 约束 ────────────────────────────────────────────┤</span><br><span class="line">│ - 防抖逻辑放在 composable 中（useDebounce）           │</span><br><span class="line">│ - 不要使用 any 类型                                   │</span><br><span class="line">│ - 不要修改现有文件                                    │</span><br><span class="line">│ - 处理组件卸载时的竞态条件                              │</span><br><span class="line">└─────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><hr><h2 id="4-3-Codebase-aware-Prompt-设计"><a href="#4-3-Codebase-aware-Prompt-设计" class="headerlink" title="4.3 Codebase-aware Prompt 设计"></a>4.3 Codebase-aware Prompt 设计</h2><p>Cursor&#x2F;Claude Code&#x2F;OpenCode 的一大优势是<strong>能感知整个项目</strong>。设计 Prompt 时要充分利用这一点。</p><h3 id="告诉-AI-参考谁"><a href="#告诉-AI-参考谁" class="headerlink" title="告诉 AI 参考谁"></a>告诉 AI 参考谁</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># ❌ 不好的做法：没有参考</span><br><span class="line">实现一个表单验证组件</span><br><span class="line"></span><br><span class="line"># ✅ 好的做法：告诉 AI 参考什么</span><br><span class="line">实现一个表单验证组件，参考以下现有代码的风格：</span><br><span class="line">- @src/components/LoginForm.vue（表单结构）</span><br><span class="line">- @src/utils/validators.ts（验证逻辑）</span><br><span class="line">- @src/types/form.ts（类型定义）</span><br></pre></td></tr></table></figure><h3 id="告诉-AI-不要碰什么"><a href="#告诉-AI-不要碰什么" class="headerlink" title="告诉 AI 不要碰什么"></a>告诉 AI 不要碰什么</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 明确负面清单</span><br><span class="line">请实现用户列表页，注意：</span><br><span class="line">- ❌ 不要修改 @src/api/user.ts（API 层不需要改动）</span><br><span class="line">- ❌ 不要修改 @src/router/index.ts（路由不需要改动）</span><br><span class="line">- ❌ 不要引入新的 npm 包</span><br><span class="line">- ✅ 只创建 @src/views/UserList.vue 和 @src/components/UserCard.vue</span><br></pre></td></tr></table></figure><h3 id="给-AI-提供错误-成功示例"><a href="#给-AI-提供错误-成功示例" class="headerlink" title="给 AI 提供错误&#x2F;成功示例"></a>给 AI 提供错误&#x2F;成功示例</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"># 告诉 AI 什么风格是你想要的 vs 不想要的</span><br><span class="line">✅ 正确的模式（我们项目中已有的）：</span><br><span class="line">export function useUser() &#123;</span><br><span class="line">  const user = ref&lt;User | null&gt;(null)</span><br><span class="line">  const loading = ref(false)</span><br><span class="line">  // ...</span><br><span class="line">  return &#123; user, loading &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">❌ 不正确的模式（不要用这种方式）：</span><br><span class="line">export default &#123;</span><br><span class="line">  setup() &#123;</span><br><span class="line">    // Options API，不要用</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="4-4-Context-Window-管理"><a href="#4-4-Context-Window-管理" class="headerlink" title="4.4 Context Window 管理"></a>4.4 Context Window 管理</h2><p>AI 的上下文窗口是有限的（Cursor 约 10 万 token，Claude Code 约 20 万 token），你给的内容越多，AI 的回答质量越稳定，但超出后质量会急剧下降。</p><h3 id="有效上下文-vs-无效上下文"><a href="#有效上下文-vs-无效上下文" class="headerlink" title="有效上下文 vs 无效上下文"></a>有效上下文 vs 无效上下文</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">✅ 有效上下文</span><br><span class="line">- 相关文件的代码结构和类型定义（不需要全部内容，可以只贴关键部分）</span><br><span class="line">- 项目规范（通过 .cursorrules / CLAUDE.md）</span><br><span class="line">- 错误信息和调用链</span><br><span class="line"></span><br><span class="line">❌ 无效上下文（占空间但不提供有用信息）</span><br><span class="line">- 整个 node_modules 或 dist 目录</span><br><span class="line">- 500 行无关的 CSS</span><br><span class="line">- 重复的对话历史</span><br><span class="line">- &quot;你好&quot;、&quot;继续&quot;、&quot;帮帮我&quot;等无效轮次</span><br></pre></td></tr></table></figure><h3 id="上下文策略"><a href="#上下文策略" class="headerlink" title="上下文策略"></a>上下文策略</h3><table><thead><tr><th>场景</th><th>建议的上下文策略</th></tr></thead><tbody><tr><td>新功能开发</td><td>引用依赖的文件 + 规范文件，其余不引用</td></tr><tr><td>Bug 修复</td><td>报错信息 + 调用链上相关文件 + 你尝试过的方案</td></tr><tr><td>重构</td><td>旧代码完整内容 + 期望的新结构描述</td></tr><tr><td>Code Review</td><td>diff 内容 + 项目规范 + 需要关注的重点</td></tr></tbody></table><h3 id="压缩技巧"><a href="#压缩技巧" class="headerlink" title="压缩技巧"></a>压缩技巧</h3><p>当你需要给 AI 大量上下文但窗口不够时：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">技巧 1：只贴接口/类型定义，不贴实现</span><br><span class="line">  ✅ 贴接口：export interface User &#123; id: number; name: string &#125;</span><br><span class="line">  ❌ 贴整个实现文件（500 行）</span><br><span class="line"></span><br><span class="line">技巧 2：用 @Folder 让 AI 自己读取</span><br><span class="line">  ✅ @src/api/（让 Agent 自己去读需要的文件）</span><br><span class="line">  ❌ 手动把所有 API 文件内容复制粘贴进来</span><br><span class="line"></span><br><span class="line">技巧 3：分步进行</span><br><span class="line">  第一轮：先让 AI 理解架构</span><br><span class="line">  第二轮：再让 AI 开始实现</span><br><span class="line">  第三轮：Review 和修复</span><br></pre></td></tr></table></figure><hr><h2 id="4-5-Prompt-模板化"><a href="#4-5-Prompt-模板化" class="headerlink" title="4.5 Prompt 模板化"></a>4.5 Prompt 模板化</h2><p>把常用的 Prompt 固化为模板，避免每次重写。</p><h3 id="模板管理方式"><a href="#模板管理方式" class="headerlink" title="模板管理方式"></a>模板管理方式</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">项目根目录/</span><br><span class="line">├── prompts/</span><br><span class="line">│   ├── component.md          # 创建新组件的 Prompt 模板</span><br><span class="line">│   ├── api-service.md        # 创建 API 服务层的 Prompt 模板</span><br><span class="line">│   ├── fix-bug.md            # Bug 修复的 Prompt 模板</span><br><span class="line">│   ├── code-review.md        # Code Review 的 Prompt 模板</span><br><span class="line">│   └── unit-test.md          # 生成单元测试的 Prompt 模板</span><br></pre></td></tr></table></figure><p><strong>Template 示例：<code>prompts/component.md</code></strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">你是一个资深前端工程师，精通 [框架] + TypeScript。</span><br><span class="line"></span><br><span class="line"><span class="section">## 上下文</span></span><br><span class="line"><span class="bullet">-</span> 项目：[项目名称]</span><br><span class="line"><span class="bullet">-</span> 技术栈：[Vue 3 / React 18]</span><br><span class="line"><span class="bullet">-</span> 样式方案：[Tailwind CSS / CSS Modules]</span><br><span class="line"><span class="bullet">-</span> 参考文件：[相关文件路径]</span><br><span class="line"></span><br><span class="line"><span class="section">## 任务</span></span><br><span class="line">创建一个 [组件名] 组件，功能是 [功能描述]。</span><br><span class="line"></span><br><span class="line"><span class="section">## Props 接口</span></span><br><span class="line"><span class="bullet">-</span> [prop1]: [类型] - [说明]</span><br><span class="line"><span class="bullet">-</span> [prop2]: [类型] - [说明]</span><br><span class="line"></span><br><span class="line"><span class="section">## Emits/事件</span></span><br><span class="line"><span class="bullet">-</span> [event1]: [payload] - [触发时机]</span><br><span class="line"></span><br><span class="line"><span class="section">## 状态说明</span></span><br><span class="line"><span class="bullet">-</span> [状态1]: [描述]</span><br><span class="line"><span class="bullet">-</span> [状态2]: [描述]</span><br><span class="line"></span><br><span class="line"><span class="section">## 约束</span></span><br><span class="line"><span class="bullet">-</span> 使用 [Composition API / Hooks]</span><br><span class="line"><span class="bullet">-</span> 完整的 TypeScript 类型</span><br><span class="line"><span class="bullet">-</span> 处理 loading / empty / error 状态</span><br><span class="line"><span class="bullet">-</span> 性能：数据量大时考虑虚拟滚动</span><br></pre></td></tr></table></figure><hr><h2 id="4-6-配置文件的-Prompt-作用"><a href="#4-6-配置文件的-Prompt-作用" class="headerlink" title="4.6 配置文件的 Prompt 作用"></a>4.6 配置文件的 Prompt 作用</h2><p>不同类型的配置文件在不同工具中起到”后台 Prompt”的作用：</p><table><thead><tr><th>文件</th><th>工具</th><th>作用</th><th>优先级</th></tr></thead><tbody><tr><td><code>.cursorrules</code></td><td>Cursor</td><td>指导 Cursor AI 的行为规范</td><td>最高，每次都读</td></tr><tr><td><code>.cursor/rules/*.mdc</code></td><td>Cursor (新版)</td><td>按文件路径匹配的规则</td><td>命中时生效</td></tr><tr><td><code>CLAUDE.md</code></td><td>Claude Code</td><td>项目级别的上下文和指令</td><td>每次都读</td></tr><tr><td><code>AGENTS.md</code></td><td>OpenCode</td><td>Agent 的知识库和行为规则</td><td>每次都读</td></tr><tr><td><code>.github/copilot-instructions.md</code></td><td>GitHub Copilot</td><td>Copilot 的代码风格指令</td><td>补全时参考</td></tr></tbody></table><p>以下是一个完整的 CLAUDE.md 配置示例，它为 Claude Code 提供了完整的项目上下文和行为指令：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CLAUDE.md — Claude Code 项目指南</span></span><br><span class="line"><span class="comment"># 位置：项目根目录</span></span><br><span class="line"><span class="comment"># 作用：指导 Claude Code 理解项目结构、约定和行为规范</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 项目概览</span></span><br><span class="line"><span class="string">这是一个</span> [<span class="string">Vue</span> <span class="number">3</span> <span class="string">/</span> <span class="string">React</span>] <span class="string">前端项目，使用</span> <span class="string">TypeScript</span> <span class="string">+</span> <span class="string">Vite</span> <span class="string">构建。</span></span><br><span class="line"><span class="string">详细的架构说明见</span> <span class="string">docs/ARCHITECTURE.md。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 技术栈</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">框架：[具体框架]</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">构建：Vite</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">语言：TypeScript</span> <span class="string">(strict)</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">样式：[Tailwind</span> <span class="string">/</span> <span class="string">CSS</span> <span class="string">Modules</span> <span class="string">/</span> <span class="string">styled-components]</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">测试：Vitest</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">Lint：ESLint</span> <span class="string">+</span> <span class="string">Prettier</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 代码约定</span></span><br><span class="line"><span class="number">1</span><span class="string">.</span> <span class="string">所有文件使用</span> <span class="number">2</span> <span class="string">空格缩进</span></span><br><span class="line"><span class="number">2</span><span class="string">.</span> <span class="string">组件文件：PascalCase（UserProfile.tsx）</span></span><br><span class="line"><span class="number">3</span><span class="string">.</span> <span class="string">工具文件：kebab-case（format-date.ts）</span></span><br><span class="line"><span class="number">4</span><span class="string">.</span> <span class="string">类型定义集中在</span> <span class="string">src/types/</span></span><br><span class="line"><span class="number">5</span><span class="string">.</span> <span class="string">API</span> <span class="string">调用统一在</span> <span class="string">src/api/</span> <span class="string">中，使用</span> <span class="string">axios</span> <span class="string">实例</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 开发工作流</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">npm</span> <span class="string">run</span> <span class="string">dev</span> <span class="string">—</span> <span class="string">启动开发服务器</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">npm</span> <span class="string">run</span> <span class="string">test</span> <span class="string">—</span> <span class="string">运行单元测试</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">npm</span> <span class="string">run</span> <span class="string">build</span> <span class="string">—</span> <span class="string">生产构建</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">npm</span> <span class="string">run</span> <span class="string">lint</span> <span class="string">—</span> <span class="string">代码检查</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 在使用 Claude Code 时的最佳实践</span></span><br><span class="line"><span class="number">1</span><span class="string">.</span> <span class="string">复杂任务先用</span> <span class="string">/plan</span> <span class="string">生成计划再执行</span></span><br><span class="line"><span class="number">2</span><span class="string">.</span> <span class="string">涉及多文件修改时，使用</span> <span class="string">Claude</span> <span class="string">Code</span> <span class="string">Agent</span> <span class="string">模式</span></span><br><span class="line"><span class="number">3</span><span class="string">.</span> <span class="string">代码生成后执行</span> <span class="string">npm</span> <span class="string">run</span> <span class="string">test</span> <span class="string">&amp;&amp;</span> <span class="string">npm</span> <span class="string">run</span> <span class="string">lint</span> <span class="string">验证</span></span><br><span class="line"><span class="number">4</span><span class="string">.</span> <span class="string">大量重构前先</span> <span class="string">git</span> <span class="string">stash</span> <span class="string">或创建新分支</span></span><br></pre></td></tr></table></figure><h3 id="配置文件的最佳实践"><a href="#配置文件的最佳实践" class="headerlink" title="配置文件的最佳实践"></a>配置文件的最佳实践</h3><p><strong>1. <code>.cursorrules</code> 应该包含什么？</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">✅ 必须有的：</span><br><span class="line">- 技术栈声明（框架、语言、构建工具）</span><br><span class="line">- 代码风格规范（缩进、命名、import 顺序）</span><br><span class="line">- 项目结构说明</span><br><span class="line"></span><br><span class="line">✅ 建议有的：</span><br><span class="line">- 常用组件的路径和用法</span><br><span class="line">- 当前正在开发的功能说明</span><br><span class="line">- 已知的技术债务和遗留问题</span><br><span class="line"></span><br><span class="line">❌ 不要放：</span><br><span class="line">- 400 行以上的大段内容（占用上下文）</span><br><span class="line">- 经常变化的内容（每次修改都会刷新 AI 的认知）</span><br><span class="line">- 已经过时的技术约束</span><br></pre></td></tr></table></figure><p><strong>2. 如何维护多份配置？</strong></p><p>对于 Monorepo 或多技术栈项目：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 项目根目录 .cursorrules — 通用规则</span><br><span class="line"># packages/web/.cursorrules — Web 前端特有规则</span><br><span class="line"># packages/api/.cursorrules — API 服务特有规则</span><br><span class="line"></span><br><span class="line"># Cursor 会从当前文件所在目录向上查找，合并所有找到的 .cursorrules</span><br><span class="line"># 越具体的规则优先级越高</span><br></pre></td></tr></table></figure><p><strong>3. 利用配置文件控制 AI 行为</strong></p><p>在 <code>.cursorrules</code> 中加入行为指令：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># AI 行为规则</span><br><span class="line">- 在修改现有代码前，先阅读完整的文件内容</span><br><span class="line">- 修改前先创建备份或使用 git</span><br><span class="line">- 每个函数必须有 JSDoc/TSDoc 注释</span><br><span class="line">- 所有错误必须被处理，不允许 throw 未捕获的错误</span><br></pre></td></tr></table></figure><p>而对于 GitHub Copilot，其自定义指令文件的格式与侧重点有所不同，更聚焦于代码补全时的风格控制：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># GitHub Copilot 自定义指令</span></span><br><span class="line"><span class="section"># 位置：.github/copilot-instructions.md</span></span><br><span class="line"><span class="section"># 作用：为 GitHub Copilot 提供项目级别的上下文，影响代码补全和 Chat 回答</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 技术栈</span></span><br><span class="line"><span class="bullet">-</span> 前端框架：Vue 3 (Composition API, <span class="code">`&lt;script setup&gt;`</span>) / React 18+</span><br><span class="line"><span class="bullet">-</span> 语言：TypeScript (strict mode)</span><br><span class="line"><span class="bullet">-</span> 构建工具：Vite</span><br><span class="line"><span class="bullet">-</span> 样式方案：Tailwind CSS / CSS Modules</span><br><span class="line"><span class="bullet">-</span> 测试框架：Vitest</span><br><span class="line"></span><br><span class="line"><span class="section">## 编码规范</span></span><br><span class="line"><span class="bullet">1.</span> 使用 2 空格缩进</span><br><span class="line"><span class="bullet">2.</span> 行尾加分号</span><br><span class="line"><span class="bullet">3.</span> 字符串使用单引号</span><br><span class="line"><span class="bullet">4.</span> 组件名使用 PascalCase</span><br><span class="line"><span class="bullet">5.</span> 文件名使用 kebab-case</span><br><span class="line"></span><br><span class="line"><span class="section">## 代码风格偏好</span></span><br><span class="line"><span class="bullet">-</span> 优先使用箭头函数</span><br><span class="line"><span class="bullet">-</span> 解构赋值优先</span><br><span class="line"><span class="bullet">-</span> 使用 optional chaining (<span class="code">`?.`</span>) 和 nullish coalescing (<span class="code">`??`</span>)</span><br><span class="line"><span class="bullet">-</span> import 语句分组：第三方库 → 项目内部 → 样式文件</span><br><span class="line"></span><br><span class="line"><span class="section">## 测试规范</span></span><br><span class="line"><span class="bullet">-</span> 测试文件命名为 <span class="emphasis">*.test.ts</span></span><br><span class="line"><span class="emphasis">- 测试文件与源文件放在同一目录</span></span><br><span class="line"><span class="emphasis">- 使用 describe/it/expect 结构</span></span><br><span class="line"><span class="emphasis">- 优先测试行为而非实现细节</span></span><br></pre></td></tr></table></figure><hr><h2 id="4-7-进阶技巧"><a href="#4-7-进阶技巧" class="headerlink" title="4.7 进阶技巧"></a>4.7 进阶技巧</h2><h3 id="技巧-1：Chain-of-Thought-CoT-Prompting"><a href="#技巧-1：Chain-of-Thought-CoT-Prompting" class="headerlink" title="技巧 1：Chain-of-Thought (CoT) Prompting"></a>技巧 1：Chain-of-Thought (CoT) Prompting</h3><p>让 AI 逐步推理，而不是直接给答案。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># ❌ 直接问</span><br><span class="line">这个列表渲染为什么卡？</span><br><span class="line"></span><br><span class="line"># ✅ CoT 方式</span><br><span class="line">请逐步分析列表卡顿的原因：</span><br><span class="line">1. 先告诉我数据量有多大</span><br><span class="line">2. 分析当前渲染策略</span><br><span class="line">3. 找出可能的性能瓶颈</span><br><span class="line">4. 提出优化方案，分析每个方案的 trade-off</span><br></pre></td></tr></table></figure><h3 id="技巧-2：Few-shot-Prompting"><a href="#技巧-2：Few-shot-Prompting" class="headerlink" title="技巧 2：Few-shot Prompting"></a>技巧 2：Few-shot Prompting</h3><p>给 AI 1-3 个输入输出示例，让 AI 理解你要的模式。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">请按照以下模式生成类似的组件：</span><br><span class="line"></span><br><span class="line">示例 1 - UserCard：</span><br><span class="line">  Props: &#123; user: User; showEmail?: boolean &#125;</span><br><span class="line">  功能：显示用户头像、名称、邮箱</span><br><span class="line">  状态：loading / ready / error</span><br><span class="line"></span><br><span class="line">示例 2 - ProductCard：</span><br><span class="line">  Props: &#123; product: Product; onAddToCart: () =&gt; void &#125;</span><br><span class="line">  功能：显示商品图片、名称、价格、加入购物车按钮</span><br><span class="line">  状态：loading / ready / sold_out</span><br><span class="line"></span><br><span class="line">现在请实现 OrderCard，类似的模式：</span><br><span class="line">  Props: &#123; order: Order &#125;</span><br><span class="line">  功能：[让 AI 自己推断]</span><br></pre></td></tr></table></figure><h3 id="技巧-3：Negative-Prompting"><a href="#技巧-3：Negative-Prompting" class="headerlink" title="技巧 3：Negative Prompting"></a>技巧 3：Negative Prompting</h3><p>明确告诉 AI 不要做什么，比告诉它要做什么更重要。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">重要约束：</span><br><span class="line">- ❌ 不要使用 any 类型</span><br><span class="line">- ❌ 不要引入新的第三方依赖</span><br><span class="line">- ❌ 不要修改 src/api/ 目录下的文件</span><br><span class="line">- ❌ 不要使用 display: none 来控制显隐</span><br><span class="line">- ✅ 使用 v-if 或条件渲染</span><br></pre></td></tr></table></figure><h3 id="技巧-4：迭代式-Refinement"><a href="#技巧-4：迭代式-Refinement" class="headerlink" title="技巧 4：迭代式 Refinement"></a>技巧 4：迭代式 Refinement</h3><p>不要期望 AI 一次给出完美答案，通过多轮迭代逼近目标。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">第一轮：生成基础实现</span><br><span class="line">第二轮：指出问题，要求修改</span><br><span class="line">第三轮：性能优化</span><br><span class="line">第四轮：边缘情况处理</span><br></pre></td></tr></table></figure><h3 id="技巧-5：技术选型-Prompt-模板"><a href="#技巧-5：技术选型-Prompt-模板" class="headerlink" title="技巧 5：技术选型 Prompt 模板"></a>技巧 5：技术选型 Prompt 模板</h3><p>在新项目启动或技术方案决策时，使用结构化的模板能让 AI 给出更全面的分析：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 技术选型 Prompt 模板</span></span><br><span class="line"><span class="quote">&gt; 用途：在新项目或新功能启动时，做技术方案选型</span></span><br><span class="line"><span class="quote">&gt; 适用工具：ChatGPT / Claude / Cursor</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 模板</span></span><br><span class="line"></span><br><span class="line">我正在做一个技术选型，请帮我分析候选方案。</span><br><span class="line"></span><br><span class="line">【项目背景】</span><br><span class="line"><span class="bullet">-</span> 项目类型：[描述]</span><br><span class="line"><span class="bullet">-</span> 团队规模：[人数]</span><br><span class="line"><span class="bullet">-</span> 团队技术背景：[成员熟悉的技术栈]</span><br><span class="line"><span class="bullet">-</span> 项目周期：[时长]</span><br><span class="line"></span><br><span class="line">【需求描述】</span><br><span class="line">&#123;需要做选型的具体需求&#125;</span><br><span class="line"></span><br><span class="line">【候选方案】</span><br><span class="line">&#123;列出已知候选方案，没有的话让 AI 生成&#125;</span><br><span class="line"></span><br><span class="line">请对每个候选方案进行以下分析：</span><br><span class="line"></span><br><span class="line"><span class="section">## 1. 方案简介</span></span><br><span class="line">一句话描述这个方案是什么。</span><br><span class="line"></span><br><span class="line"><span class="section">## 2. 核心能力</span></span><br><span class="line"><span class="bullet">-</span> 解决了什么问题</span><br><span class="line"><span class="bullet">-</span> 不解决什么问题</span><br><span class="line"></span><br><span class="line"><span class="section">## 3. 对比评分</span></span><br><span class="line"></span><br><span class="line">| 维度 | 方案A | 方案B | 方案C |</span><br><span class="line">|------|-------|-------|-------|</span><br><span class="line">| 学习成本 | 评分+说明 | ... | ... |</span><br><span class="line">| 开发效率 | 评分+说明 | ... | ... |</span><br><span class="line">| 运行性能 | 评分+说明 | ... | ... |</span><br><span class="line">| 生态成熟度 | 评分+说明 | ... | ... |</span><br><span class="line">| 维护成本 | 评分+说明 | ... | ... |</span><br><span class="line">| 可扩展性 | 评分+说明 | ... | ... |</span><br><span class="line"></span><br><span class="line">（评分：1-5 分，附简要理由）</span><br><span class="line"></span><br><span class="line"><span class="section">## 4. 适用场景</span></span><br><span class="line">每个方案最适合什么场景、不适合什么场景。</span><br><span class="line"></span><br><span class="line"><span class="section">## 5. 推荐方案及理由</span></span><br><span class="line"><span class="bullet">-</span> 推荐哪个方案</span><br><span class="line"><span class="bullet">-</span> 为什么（结合项目背景）</span><br><span class="line"><span class="bullet">-</span> 如果采用这个方案，建议的落地步骤</span><br><span class="line"><span class="bullet">-</span> 潜在风险及应对策略</span><br><span class="line"></span><br><span class="line"><span class="section">## 使用注意事项</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">1.</span> <span class="strong">**先定义评估维度**</span>：不同项目侧重点不同，调整评分维度的权重</span><br><span class="line"><span class="bullet">2.</span> <span class="strong">**AI 有流行度偏差**</span>：它可能推荐最流行而非最适合的方案，自己判断</span><br><span class="line"><span class="bullet">3.</span> <span class="strong">**数据要自己验证**</span>：AI 说的&quot;性能数据&quot;可能是编的，实测为准</span><br><span class="line"><span class="bullet">4.</span> <span class="strong">**最终决策自己做**</span>：AI 提供决策依据，决策责任在你</span><br></pre></td></tr></table></figure><hr><h2 id="4-8-Prompt-质量的自我检查清单"><a href="#4-8-Prompt-质量的自我检查清单" class="headerlink" title="4.8 Prompt 质量的自我检查清单"></a>4.8 Prompt 质量的自我检查清单</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">每次发送 Prompt 前，问自己：</span><br><span class="line"></span><br><span class="line">□ 角色定义清楚了吗？（你是一个...）</span><br><span class="line">□ 技术栈和项目背景说明了没有？</span><br><span class="line">□ 要做什么具体说清楚了？（不是&quot;写个功能&quot;，而是&quot;实现...的需求&quot;）</span><br><span class="line">□ 输出格式指定了吗？（代码/文档/JSON/列表）</span><br><span class="line">□ 约束条件写全了吗？（不能做什么？要参考什么？）</span><br><span class="line">□ 参考文件用 @ 引用了吗？</span><br><span class="line">□ 需要避免的坑写了吗？</span><br><span class="line">□ 上下文会不会太多或太少？</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://github.com/wz71014q/2025/10/15/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-Prompt%E5%B7%A5%E7%A8%8B/</id>
    <link href="https://github.com/wz71014q/2025/10/15/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-Prompt%E5%B7%A5%E7%A8%8B/"/>
    <published>2025-10-15T02:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="AI时代前端工程师实战指南：Prompt工程"><a href="#AI时代前端工程师实战指南：Prompt工程" class="headerlink" title="AI时代前端工程师实战指南：Prompt工程"></a>AI时代前端工程师实战指南：Prompt工程</h1><p><strong>本文目的</strong>：从”随机问 AI”到”系统化地使用 AI”，建立可复用的 Prompt 工程体系。涉及 .cursorrules、CLAUDE.md、AGENTS.md、项目级 Prompt 模板库等多种配置文件的使用。</p>
<hr>
<h2 id="4-1-为什么需要-Prompt-工程？"><a href="#4-1-为什么需要-Prompt-工程？" class="headerlink" title="4.1 为什么需要 Prompt 工程？"></a>4.1 为什么需要 Prompt 工程？</h2><p>一个简单的实验：同样的问题，不同问法，AI 的回答质量天差地别。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">❌ 差：&quot;帮我写一个搜索组件&quot;</span><br><span class="line">    → AI 生成一个最简单的搜索框，大概率不符合项目规范</span><br><span class="line"></span><br><span class="line">✅ 好：见下方结构化 Prompt</span><br></pre></td></tr></table></figure>

<p><strong>Prompt 工程就是在系统化地缩小 AI 的”猜测空间”</strong>。你给的信息越精确，AI 的”猜测”就越接近你的真实需求。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">AI 的猜测空间：</span><br><span class="line">  无限可能 ──────────────→ 精确匹配需求</span><br><span class="line">     ↑                          ↑</span><br><span class="line">  模糊 Prompt              结构化 Prompt</span><br><span class="line">  （&quot;写个搜索&quot;）         （带约束、上下文、格式）</span><br></pre></td></tr></table></figure>]]>
    </summary>
    <title>AI时代前端工程师实战指南：Prompt工程</title>
    <updated>2026-05-22T03:59:44.504Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="AI时代前端工程师实战指南" scheme="https://github.com/wz71014q/categories/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="AI" scheme="https://github.com/wz71014q/tags/AI/"/>
    <category term="前端工程师" scheme="https://github.com/wz71014q/tags/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88/"/>
    <category term="实战指南" scheme="https://github.com/wz71014q/tags/%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="Cursor" scheme="https://github.com/wz71014q/tags/Cursor/"/>
    <content>
      <![CDATA[<h1 id="AI时代前端工程师实战指南：Cursor实战"><a href="#AI时代前端工程师实战指南：Cursor实战" class="headerlink" title="AI时代前端工程师实战指南：Cursor实战"></a>AI时代前端工程师实战指南：Cursor实战</h1><p>学会 Cursor 的正确打开方式，从”只会 Tab 补全”到”熟练使用 Agent 模式”。在开始之前，先了解 Cursor 的两个重要配置文件：<code>.cursorrules</code>（项目根目录）和 <code>.cursor/rules/*.mdc</code>（<code>.cursor/rules/</code> 目录），它们决定了 AI 的行为规范。</p><hr><h2 id="3-1-三种模式与使用场景"><a href="#3-1-三种模式与使用场景" class="headerlink" title="3.1 三种模式与使用场景"></a>3.1 三种模式与使用场景</h2><p>Cursor 有三种交互模式，分别对应不同场景：</p><table><thead><tr><th>模式</th><th>触发方式</th><th>最佳场景</th><th>不要用它做</th></tr></thead><tbody><tr><td><strong>Tab 补全</strong></td><td>自动触发（灰色提示文字按 Tab）</td><td>写已知模式的代码、重复性编码</td><td>复杂逻辑、跨文件操作</td></tr><tr><td><strong>Ctrl+K (Inline Edit)</strong></td><td>选中代码后按 Ctrl+K</td><td>修改单段代码、解释代码、重构函数</td><td>创建新文件、多文件修改</td></tr><tr><td><strong>Ctrl+I (Composer)</strong></td><td>Ctrl+I</td><td><strong>这是 Cursor 的核心能力</strong></td><td>简单改一行变量名（Tab 更快）</td></tr></tbody></table><blockquote><p>Composer 有两种子模式：<strong>Normal</strong>（对话式，你一步步引导）和 <strong>Agent</strong>（自主式，AI 自己规划执行）。</p></blockquote><h3 id="选型决策树"><a href="#选型决策树" class="headerlink" title="选型决策树"></a>选型决策树</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">我想让 Cursor 做什么？</span><br><span class="line">│</span><br><span class="line">├─ 写一行已知模式的代码 → Tab 补全</span><br><span class="line">├─ 修改当前函数/组件 → Ctrl+K</span><br><span class="line">├─ 写一个新函数/组件（有清晰的需求）→ Ctrl+I Normal</span><br><span class="line">├─ 写一个涉及多文件的新功能 → Ctrl+I Agent</span><br><span class="line">├─ Debug 一段报错 → Ctrl+K 选中报错区域</span><br><span class="line">└─ 理解一个文件或文件夹 → Ctrl+K &quot;解释这个文件&quot;</span><br></pre></td></tr></table></figure><hr><h2 id="3-2-Agent-模式完全指南"><a href="#3-2-Agent-模式完全指南" class="headerlink" title="3.2 Agent 模式完全指南"></a>3.2 Agent 模式完全指南</h2><p>Agent 模式是 Cursor 和 VS Code + Copilot 的<strong>本质区别</strong>。</p><h3 id="Agent-能做什么"><a href="#Agent-能做什么" class="headerlink" title="Agent 能做什么"></a>Agent 能做什么</h3><ul><li>自动读取多个文件来理解上下文</li><li>创建、修改、删除文件</li><li>在终端运行命令（<code>npm install</code>、<code>git commit</code> 等）</li><li>自主规划步骤并执行</li></ul><h3 id="Agent-不能做什么"><a href="#Agent-不能做什么" class="headerlink" title="Agent 不能做什么"></a>Agent 不能做什么</h3><ul><li>不能记住上次会话的内容（每次启动都是新会话）</li><li>不能保证生成的代码 100% 正确（需要你验证）</li><li>不能用自然语言理解复杂的业务逻辑（它不理解你的业务，只理解代码）</li></ul><h3 id="Agent-模式的最佳实践"><a href="#Agent-模式的最佳实践" class="headerlink" title="Agent 模式的最佳实践"></a>Agent 模式的最佳实践</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">✅ 好的 Agent Prompt 结构：</span><br><span class="line"></span><br><span class="line">【目标】实现一个防抖搜索组件</span><br><span class="line">【约束】</span><br><span class="line">- 使用 Vue 3 Composition API</span><br><span class="line">- 输入框 300ms 防抖后调用 API</span><br><span class="line">- API 路径：/api/search?q=&#123;keyword&#125;</span><br><span class="line">- 显示加载状态和空状态</span><br><span class="line">【参考文件】</span><br><span class="line">- @src/components/Input.vue（参考现有的输入组件风格）</span><br><span class="line">- @src/api/search.ts（参考现有的 API 调用方式）</span><br><span class="line"></span><br><span class="line">❌ 差的 Agent Prompt：</span><br><span class="line">实现一个搜索组件</span><br><span class="line">（太模糊，AI 会按自己的假设做，结果大概率不符合预期）</span><br></pre></td></tr></table></figure><h3 id="关键技巧：用-精准控制上下文"><a href="#关键技巧：用-精准控制上下文" class="headerlink" title="关键技巧：用 @ 精准控制上下文"></a>关键技巧：用 @ 精准控制上下文</h3><table><thead><tr><th>指令</th><th>用法</th><th>作用</th></tr></thead><tbody><tr><td><code>@File</code></td><td><code>@src/components/Button.tsx</code></td><td>引用特定文件</td></tr><tr><td><code>@Folder</code></td><td><code>@src/components/</code></td><td>引用整个文件夹</td></tr><tr><td><code>@Code</code></td><td><code>@Code(变量名或类型)</code></td><td>引用代码中的特定符号</td></tr><tr><td><code>@Docs</code></td><td><code>@Docs Vue</code></td><td>引用官方文档（需要先添加文档源）</td></tr><tr><td><code>@Web</code></td><td><code>@Web React 19 新特性</code></td><td>联网搜索（Composer + Agent 模式可用）</td></tr><tr><td><code>@Terminal</code></td><td>在 Composer 中</td><td>引用终端输出来辅助 Debug</td></tr><tr><td><code>@Problems</code></td><td>在 Composer 中</td><td>引用 LSP 错误列表</td></tr></tbody></table><p><strong>配置 @Docs</strong>：Settings → Features → Docs → Add Doc，可以添加 Vue、React、Tailwind 等官方文档源。之后在对话中 <code>@Docs Vue</code> 就能引用最新的官方文档。</p><hr><h2 id="3-3-Debug-最佳实践"><a href="#3-3-Debug-最佳实践" class="headerlink" title="3.3 Debug 最佳实践"></a>3.3 Debug 最佳实践</h2><p>直接把报错信息贴给 AI 是最差的 Debug 方式。</p><h3 id="Debug-的正确姿势"><a href="#Debug-的正确姿势" class="headerlink" title="Debug 的正确姿势"></a>Debug 的正确姿势</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">❌ 错误方式：</span><br><span class="line">&quot;这个报错怎么解决？&quot;</span><br><span class="line">（贴了一行错误信息，没有任何上下文）</span><br><span class="line"></span><br><span class="line">✅ 正确方式：</span><br><span class="line"></span><br><span class="line">【错误现象】</span><br><span class="line">@Terminal（最后一次终端输出）</span><br><span class="line">或 @Problems（LSP 错误列表）</span><br><span class="line">或 选中报错代码区域按 Ctrl+K</span><br><span class="line"></span><br><span class="line">【代码上下文】</span><br><span class="line">@相关的文件引用</span><br><span class="line"></span><br><span class="line">【我尝试过的】</span><br><span class="line">- 已经检查了 API 返回的数据格式</span><br><span class="line">- 确认了参数类型匹配</span><br><span class="line"></span><br><span class="line">【期望行为】</span><br><span class="line">点击提交按钮后应该调 API 发送数据，但现在点了没反应</span><br></pre></td></tr></table></figure><h3 id="Debug-的四个层次"><a href="#Debug-的四个层次" class="headerlink" title="Debug 的四个层次"></a>Debug 的四个层次</h3><table><thead><tr><th>层次</th><th>做法</th><th>效果</th></tr></thead><tbody><tr><td>L1</td><td>贴报错信息</td><td>20% 能解决，通常是因为缺少上下文</td></tr><tr><td>L2</td><td>贴报错 + 相关代码</td><td>60% 能解决，AI 能看到上下文</td></tr><tr><td>L3</td><td>贴报错 + 代码 + 你怀疑的原因</td><td>80% 能解决，AI 可以验证你的假设</td></tr><tr><td>L4</td><td>让 Cursor Agent 自己复现问题</td><td>90% 能解决，Agent 可以运行代码、加日志、定位根因</td></tr></tbody></table><p><strong>L4 示例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">这个组件有个 bug：在筛选条件变化后，列表数据没有刷新。</span><br><span class="line">请帮我：</span><br><span class="line">1. 先理解当前的数据流（读取 @src/stores/productStore.ts 和相关文件）</span><br><span class="line">2. 在关键路径上加 console.log</span><br><span class="line">3. 告诉我问题出在哪里</span><br><span class="line">4. 给出修复方案</span><br></pre></td></tr></table></figure><hr><h2 id="3-4-用-Cursor-理解遗留代码"><a href="#3-4-用-Cursor-理解遗留代码" class="headerlink" title="3.4 用 Cursor 理解遗留代码"></a>3.4 用 Cursor 理解遗留代码</h2><p>面对一个陌生的大型代码库，不用从头读到尾。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">请帮我理解这个文件夹的结构和功能：</span><br><span class="line"></span><br><span class="line">@src/modules/report/</span><br><span class="line"></span><br><span class="line">请输出：</span><br><span class="line">1. 这个模块的核心职责</span><br><span class="line">2. 文件结构和每个文件的作用</span><br><span class="line">3. 数据流向（数据从哪里来，到哪里去）</span><br><span class="line">4. 关键函数和组件的调用链</span><br><span class="line">5. 如果我要加一个&quot;导出 PDF&quot;功能，应该改哪些文件</span><br></pre></td></tr></table></figure><h3 id="理解代码的三个层次"><a href="#理解代码的三个层次" class="headerlink" title="理解代码的三个层次"></a>理解代码的三个层次</h3><table><thead><tr><th>层次</th><th>做法</th><th>能做什么</th></tr></thead><tbody><tr><td><strong>宏观</strong></td><td><code>@Folder</code> + 问”这个模块是做什么的”</td><td>理解模块职责和文件结构</td></tr><tr><td><strong>中观</strong></td><td><code>@File</code> + “解释这个组件的逻辑”</td><td>理解单个文件的职责</td></tr><tr><td><strong>微观</strong></td><td>选中代码 + Ctrl+K “解释这段逻辑”</td><td>理解具体实现细节</td></tr></tbody></table><hr><h2 id="3-5-Cursor-配置"><a href="#3-5-Cursor-配置" class="headerlink" title="3.5 Cursor 配置"></a>3.5 Cursor 配置</h2><h3 id="cursorrules-文件（项目根目录）"><a href="#cursorrules-文件（项目根目录）" class="headerlink" title=".cursorrules 文件（项目根目录）"></a><code>.cursorrules</code> 文件（项目根目录）</h3><p><code>.cursorrules</code> 是 Cursor AI 的行为配置文件。放在项目根目录，Cursor 会自动读取。</p><p><strong>必配内容</strong>：</p><ul><li>项目的技术栈（框架、语言、构建工具）</li><li>代码风格偏好</li><li>项目结构约定</li><li>命名规范</li></ul><p>以下是一个完整的 <code>.cursorrules</code> 配置示例：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># .cursorrules — Cursor 项目规则文件</span></span><br><span class="line"><span class="comment"># 位置：项目根目录</span></span><br><span class="line"><span class="comment"># 作用：定义 Cursor AI（Tab 补全 / Ctrl+K / Agent）的行为规范</span></span><br><span class="line"><span class="comment"># 文档：https://docs.cursor.com/context/rules-for-ai</span></span><br><span class="line"></span><br><span class="line"><span class="string">你是一个资深前端工程师，精通</span> <span class="string">Vue</span> <span class="number">3</span><span class="string">、React、TypeScript。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 技术栈</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">框架：Vue</span> <span class="number">3</span> <span class="string">(Composition</span> <span class="string">API)</span> <span class="string">/</span> <span class="string">React</span> <span class="number">18</span><span class="string">+</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">构建：Vite</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">样式：Tailwind</span> <span class="string">CSS</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">语言：TypeScript</span> <span class="string">(strict</span> <span class="string">mode)</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">状态管理：Pinia</span> <span class="string">/</span> <span class="string">Zustand</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">测试：Vitest</span> <span class="string">+</span> <span class="string">Testing</span> <span class="string">Library</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 代码风格</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">使用</span> <span class="string">Composition</span> <span class="string">API</span> <span class="string">的</span> <span class="string">`&lt;script</span> <span class="string">setup&gt;`</span> <span class="string">语法（Vue</span> <span class="string">项目）</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">使用</span> <span class="string">React</span> <span class="string">Hooks</span> <span class="string">和函数组件（React</span> <span class="string">项目）</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">优先使用</span> <span class="string">`const`</span> <span class="string">而非</span> <span class="string">`let`</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">避免</span> <span class="string">`any`</span> <span class="string">类型，使用</span> <span class="string">`unknown`</span> <span class="string">代替</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">组件名使用</span> <span class="string">PascalCase，文件使用</span> <span class="string">kebab-case</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">使用</span> <span class="string">named</span> <span class="string">export</span> <span class="string">而非</span> <span class="string">default</span> <span class="string">export</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 核心原则</span></span><br><span class="line"><span class="number">1</span><span class="string">.</span> <span class="string">生成的代码要有完整的</span> <span class="string">TypeScript</span> <span class="string">类型定义</span></span><br><span class="line"><span class="number">2</span><span class="string">.</span> <span class="string">每个组件职责单一，不超</span> <span class="number">300</span> <span class="string">行</span></span><br><span class="line"><span class="number">3</span><span class="string">.</span> <span class="string">逻辑复用优先使用</span> <span class="string">composable</span> <span class="string">/</span> <span class="string">custom</span> <span class="string">hooks</span></span><br><span class="line"><span class="number">4</span><span class="string">.</span> <span class="string">性能敏感场景主动考虑</span> <span class="string">memo</span> <span class="string">/</span> <span class="string">virtual</span> <span class="string">scroll</span> <span class="string">/</span> <span class="string">lazy</span> <span class="string">load</span></span><br><span class="line"><span class="number">5</span><span class="string">.</span> <span class="string">错误处理全覆盖：try-catch</span> <span class="string">+</span> <span class="string">用户友好的错误提示</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 项目结构</span></span><br><span class="line"><span class="string">src/</span></span><br><span class="line"><span class="string">├──</span> <span class="string">components/</span>     <span class="comment"># 通用组件</span></span><br><span class="line"><span class="string">├──</span> <span class="string">composables/</span>    <span class="comment"># 逻辑复用（Vue）</span></span><br><span class="line"><span class="string">├──</span> <span class="string">hooks/</span>          <span class="comment"># 逻辑复用（React）</span></span><br><span class="line"><span class="string">├──</span> <span class="string">views/</span>          <span class="comment"># 页面级组件</span></span><br><span class="line"><span class="string">├──</span> <span class="string">api/</span>            <span class="comment"># API 调用</span></span><br><span class="line"><span class="string">├──</span> <span class="string">stores/</span>         <span class="comment"># 状态管理</span></span><br><span class="line"><span class="string">├──</span> <span class="string">types/</span>          <span class="comment"># 类型定义</span></span><br><span class="line"><span class="string">└──</span> <span class="string">utils/</span>          <span class="comment"># 工具函数</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 测试要求</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">工具函数和</span> <span class="string">composable</span> <span class="string">必须有单元测试</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">测试文件与源文件同目录，命名为</span> <span class="string">`*.test.ts`</span></span><br></pre></td></tr></table></figure><h3 id="cursor-rules-mdc（新版规则系统）"><a href="#cursor-rules-mdc（新版规则系统）" class="headerlink" title=".cursor/rules/*.mdc（新版规则系统）"></a><code>.cursor/rules/*.mdc</code>（新版规则系统）</h3><p>Cursor 新版支持按文件路径匹配的规则。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">.cursor/rules/</span><br><span class="line">├── frontend-standards.mdc     # 全局前端规范</span><br><span class="line">├── vue-components.mdc         # Vue 组件相关规则</span><br><span class="line">└── api-layer.mdc             # API 层相关规则</span><br></pre></td></tr></table></figure><p>每个 <code>.mdc</code> 文件头部可以定义匹配规则：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">Vue</span> <span class="string">组件规范</span></span><br><span class="line"><span class="attr">globs:</span> <span class="string">src/**/*.vue</span></span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure><p>以下是一个完整的 <code>.mdc</code> 规则文件示例：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 新版 Cursor 规则文件 (.mdc 格式)</span></span><br><span class="line"><span class="comment"># 位置：.cursor/rules/（需要手动创建 .cursor/rules/ 目录）</span></span><br><span class="line"><span class="comment"># 说明：Cursor 新版规则系统支持按文件路径匹配自动激活</span></span><br><span class="line"><span class="comment"># 文件名建议：frontend-standards.mdc</span></span><br><span class="line"><span class="comment"># 参考：https://docs.cursor.com/context/rules-for-ai</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">前端代码规范和项目约定</span></span><br><span class="line"><span class="attr">globs:</span> <span class="string">src/**/*.&#123;ts,tsx,vue&#125;</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="comment">## 技术栈规则</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">使用</span> <span class="string">TypeScript</span> <span class="string">strict</span> <span class="string">模式</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">框架：Vue</span> <span class="number">3</span> <span class="string">Composition</span> <span class="string">API</span> <span class="string">+</span> <span class="string">`&lt;script</span> <span class="string">setup&gt;`</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">样式：Tailwind</span> <span class="string">CSS</span> <span class="string">优先</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">状态管理：Pinia</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 组件设计规范</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">每个组件职责单一，不超过</span> <span class="number">300</span> <span class="string">行</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">Props</span> <span class="string">定义使用</span> <span class="string">`withDefaults`</span> <span class="string">+</span> <span class="string">类型声明</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">事件使用</span> <span class="string">`defineEmits`</span> <span class="string">声明</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">复杂组件抽取</span> <span class="string">composable</span> <span class="string">分离逻辑</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 命名规范</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">组件文件：PascalCase（`UserProfile.vue`）</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">Composables：`use`</span> <span class="string">前缀</span> <span class="string">+</span> <span class="string">camelCase（`useUserAuth.ts`）</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">类型定义：`interface`</span> <span class="string">前缀</span> <span class="string">`I`（`IUser`）或</span> <span class="string">PascalCase（推荐</span> <span class="string">`User`）</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">枚举：PascalCase（`UserRole.Admin`）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 代码质量</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">禁止</span> <span class="string">`any`，使用</span> <span class="string">`unknown`</span> <span class="string">替代</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">禁止</span> <span class="string">`@ts-ignore`</span> <span class="string">/</span> <span class="string">`@ts-expect-error`</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">禁止空的</span> <span class="string">catch</span> <span class="string">块</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">异步函数必须有错误处理</span></span><br></pre></td></tr></table></figure><hr><h2 id="3-6-常见陷阱"><a href="#3-6-常见陷阱" class="headerlink" title="3.6 常见陷阱"></a>3.6 常见陷阱</h2><table><thead><tr><th>陷阱</th><th>表现</th><th>原因</th><th>解决</th></tr></thead><tbody><tr><td>Context 溢出</td><td>AI 开始胡言乱语</td><td>对话太长，超出上下文窗口</td><td>开新会话，上下文精简</td></tr><tr><td>Agent 循环</td><td>Agent 陷入死循环改同一个文件</td><td>目标不明确或约束不足</td><td>终止后重新明确限制条件</td></tr><tr><td>幻觉 API</td><td>AI 用了不存在的 API&#x2F;配置</td><td>知识截止日期前的 API 变化</td><td>开启 @Docs 或 @Web 模式</td></tr><tr><td>过度修改</td><td>Agent 改了你不希望改的文件</td><td>没有在 Prompt 中指定范围</td><td>明确 “请不要修改 xxx 文件”</td></tr><tr><td>代码不一致</td><td>新代码风格和项目不一致</td><td>没有配置 .cursorrules</td><td>配置 <code>.cursorrules</code> 文件</td></tr></tbody></table>]]>
    </content>
    <id>https://github.com/wz71014q/2025/07/15/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-Cursor%E5%AE%9E%E6%88%98/</id>
    <link href="https://github.com/wz71014q/2025/07/15/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-Cursor%E5%AE%9E%E6%88%98/"/>
    <published>2025-07-15T02:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="AI时代前端工程师实战指南：Cursor实战"><a href="#AI时代前端工程师实战指南：Cursor实战" class="headerlink" title="AI时代前端工程师实战指南：Cursor实战"></a>AI时代前端工程师实战指南：Cursor实战</h1><p>学会 Cursor 的正确打开方式，从”只会 Tab 补全”到”熟练使用 Agent 模式”。在开始之前，先了解 Cursor 的两个重要配置文件：<code>.cursorrules</code>（项目根目录）和 <code>.cursor/rules/*.mdc</code>（<code>.cursor/rules/</code> 目录），它们决定了 AI 的行为规范。</p>
<hr>
<h2 id="3-1-三种模式与使用场景"><a href="#3-1-三种模式与使用场景" class="headerlink" title="3.1 三种模式与使用场景"></a>3.1 三种模式与使用场景</h2><p>Cursor 有三种交互模式，分别对应不同场景：</p>
<table>
<thead>
<tr>
<th>模式</th>
<th>触发方式</th>
<th>最佳场景</th>
<th>不要用它做</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Tab 补全</strong></td>
<td>自动触发（灰色提示文字按 Tab）</td>
<td>写已知模式的代码、重复性编码</td>
<td>复杂逻辑、跨文件操作</td>
</tr>
<tr>
<td><strong>Ctrl+K (Inline Edit)</strong></td>
<td>选中代码后按 Ctrl+K</td>
<td>修改单段代码、解释代码、重构函数</td>
<td>创建新文件、多文件修改</td>
</tr>
<tr>
<td><strong>Ctrl+I (Composer)</strong></td>
<td>Ctrl+I</td>
<td><strong>这是 Cursor 的核心能力</strong></td>
<td>简单改一行变量名（Tab 更快）</td>
</tr>
</tbody></table>
<blockquote>
<p>Composer 有两种子模式：<strong>Normal</strong>（对话式，你一步步引导）和 <strong>Agent</strong>（自主式，AI 自己规划执行）。</p>
</blockquote>
<h3 id="选型决策树"><a href="#选型决策树" class="headerlink" title="选型决策树"></a>选型决策树</h3>]]>
    </summary>
    <title>AI时代前端工程师实战指南：Cursor实战</title>
    <updated>2026-05-22T04:26:44.301Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="AI时代前端工程师实战指南" scheme="https://github.com/wz71014q/categories/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="AI" scheme="https://github.com/wz71014q/tags/AI/"/>
    <category term="前端工程师" scheme="https://github.com/wz71014q/tags/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88/"/>
    <category term="实战指南" scheme="https://github.com/wz71014q/tags/%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/"/>
    <category term="认知" scheme="https://github.com/wz71014q/tags/%E8%AE%A4%E7%9F%A5/"/>
    <content>
      <![CDATA[<h1 id="AI-时代前端工程师实战指南：认知篇"><a href="#AI-时代前端工程师实战指南：认知篇" class="headerlink" title="AI 时代前端工程师实战指南：认知篇"></a>AI 时代前端工程师实战指南：认知篇</h1><blockquote><p>从工具使用到 Agent 驱动开发，用 AI 重塑整个软件交付流程</p><p>适用读者：初中级、高级前端工程师<br>对应工具：Cursor · Claude Code · OpenCode · GitHub Copilot</p></blockquote><hr><p>如果要用五句话概括 AI 时代前端工程师需要建立的核心认知，那就是：</p><ol><li><strong>AI 不是搜索引擎</strong> — 它是你的结对程序员，区别在于你如何描述问题</li><li><strong>Prompt 是新的编程语言</strong> — 写好 Prompt 和写好代码同样重要</li><li><strong>Agent 是下一代的 IDE</strong> — 不只是代码补全，而是能自主执行任务的开发伙伴</li><li><strong>AI 提效是全链路的</strong> — 从需求分析到上线运维，每个环节都能看到显著提升</li><li><strong>面试考的不是工具</strong> — 是你的方法论：如何判断、如何使用、如何保证质量</li></ol><p>这些观点会贯穿整个系列。本文作为开篇，先帮你建立起对 AI 工具的全局认知，理解能力模型的迁移方向，为后续实战打好基础。</p><hr><h2 id="1-1-AI-工具分类矩阵"><a href="#1-1-AI-工具分类矩阵" class="headerlink" title="1.1 AI 工具分类矩阵"></a>1.1 AI 工具分类矩阵</h2><p>市面上所有 AI 编程工具可以分为三个层次，理解它们的区别是第一步：</p><table><thead><tr><th>层次</th><th>代表工具</th><th>工作模式</th><th>能力边界</th><th>适合场景</th></tr></thead><tbody><tr><td><strong>L1 补全型</strong></td><td>GitHub Copilot, Cursor Tab</td><td>根据上下文预测下一段代码</td><td>逐行&#x2F;逐块补全</td><td>编码过程中的辅助</td></tr><tr><td><strong>L2 对话型</strong></td><td>ChatGPT, Claude, Cursor Chat</td><td>多轮对话，理解需求生成代码</td><td>单次需求→单次输出</td><td>问答、思路探讨、代码生成</td></tr><tr><td><strong>L3 Agent型</strong></td><td>Cursor Agent, Claude Code, OpenCode</td><td>自主读取文件、执行命令、多步规划</td><td>从需求到可运行代码的完整流程</td><td>复杂任务、多文件修改、全流程开发</td></tr></tbody></table><p><strong>关键理解</strong>：</p><ul><li>L1 是”自动驾驶的加速踏板”——辅助你更快地写出当前行的代码</li><li>L2 是”导航系统”——告诉你该怎么走，但需要你自己操作</li><li>L3 是”自动驾驶”——你设定目的地，它负责规划路线、操作、到达</li></ul><blockquote><p>面试官问”你用过的 AI 工具有哪些”，你的回答如果只说”我用 ChatGPT 写代码”，说明你还在 L1-L2。如果你能说出”Cursor Agent 做多文件重构、Claude Code 做全流程开发、OpenCode 做多 Agent 协作”，说明你已经进入了 L3 的认知层。</p></blockquote><hr><h2 id="1-2-前端工程师的能力模型迁移"><a href="#1-2-前端工程师的能力模型迁移" class="headerlink" title="1.2 前端工程师的能力模型迁移"></a>1.2 前端工程师的能力模型迁移</h2><p>AI 时代，前端工程师的核心竞争力正在发生结构性变化：</p><h3 id="❌-不再稀缺的能力（AI-做得好）"><a href="#❌-不再稀缺的能力（AI-做得好）" class="headerlink" title="❌ 不再稀缺的能力（AI 做得好）"></a>❌ 不再稀缺的能力（AI 做得好）</h3><ul><li>写 CRUD 页面和表单</li><li>写单元测试和基础文档</li><li>实现常见 UI 组件</li><li>搬运 Stack Overflow 的代码片段</li><li>配置 Webpack&#x2F;Vite 等工具链</li></ul><h3 id="✅-正在变得稀缺的能力（AI-做不好，或需要人来把控）"><a href="#✅-正在变得稀缺的能力（AI-做不好，或需要人来把控）" class="headerlink" title="✅ 正在变得稀缺的能力（AI 做不好，或需要人来把控）"></a>✅ 正在变得稀缺的能力（AI 做不好，或需要人来把控）</h3><ul><li><strong>定义问题的能力</strong> — 把模糊的需求转化为清晰的技术方案</li><li><strong>架构设计的能力</strong> — 在多个方案中做出权衡决策</li><li><strong>质量控制的能力</strong> — 审查 AI 生成的代码，判断是否正确、安全、可维护</li><li><strong>集成整合的能力</strong> — 把 AI 生成的各个模块拼成完整系统</li><li><strong>判断力</strong> — 知道什么时候该用 AI，什么时候不该用</li></ul><h3 id="能力模型的”T型”变化"><a href="#能力模型的”T型”变化" class="headerlink" title="能力模型的”T型”变化"></a>能力模型的”T型”变化</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">          ┌────────────────────┐</span><br><span class="line">          │  AI 工具驾驭能力    │  ← 新增的顶层能力</span><br><span class="line">          │  (Prompt / Agent)   │</span><br><span class="line">├─────────┼────────────────────┼─────────┤</span><br><span class="line">│  HTML   │  JS/TS/框架 深度   │  性能   │</span><br><span class="line">│  CSS    │  浏览器原理         │  安全   │</span><br><span class="line">│  工程化  │  设计模式           │  网络   │</span><br><span class="line">├─────────┴────────────────────┴─────────┤</span><br><span class="line">│        前端基础（不变的部分）             │</span><br><span class="line">└────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p>核心结论：<strong>前端基础知识仍然是不可替代的护城河</strong>。AI 可以帮你写代码，但如果你不懂浏览器渲染原理，你就无法判断 AI 生成的性能优化方案是否合理。如果你不懂闭包和事件循环，你就无法 debug AI 写出来的异步代码。</p><hr><h2 id="1-3-AI-Native-工程师的五个层级"><a href="#1-3-AI-Native-工程师的五个层级" class="headerlink" title="1.3 AI Native 工程师的五个层级"></a>1.3 AI Native 工程师的五个层级</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Level 5: 创造工具 ── 编写 Skills / MCP Server / Agent 框架</span><br><span class="line">Level 4: 驾驭 Agent ── 搭建多 Agent 协作流程，自动化完整开发周期</span><br><span class="line">Level 3: 精通 Prompt ── 结构化 Prompt、Context 管理、模板化</span><br><span class="line">Level 2: 熟练使用 ── 知道什么场景用什么工具，能处理大部分日常开发</span><br><span class="line">Level 1: 初步接触 ── 偶尔用 ChatGPT 问问题，用 Copilot 补全代码</span><br></pre></td></tr></table></figure><p>本文的目标：帮你从 L2 走到 L5。</p><hr><h2 id="1-4-学习路径建议"><a href="#1-4-学习路径建议" class="headerlink" title="1.4 学习路径建议"></a>1.4 学习路径建议</h2><p>根据你的当前水平，可以选择不同的学习路线。以下是针对不同阶段工程师的推荐阅读路径：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">初级工程师（1-3年）</span><br><span class="line">  Cursor 实战 → Prompt 工程 → 避坑篇 → 面试篇</span><br><span class="line"></span><br><span class="line">中级工程师（3-5年）</span><br><span class="line">  Cursor 实战 → Prompt 工程 → 全链路提效 → Agent 工作流 → 面试篇</span><br><span class="line"></span><br><span class="line">高级/架构师</span><br><span class="line">  全链路提效（完整 SDD+TDD）→ Agent 工作流（Agent 全流程）→ Prompt 工程（规范化建设）→ 面试篇</span><br></pre></td></tr></table></figure><h3 id="入门路线（2-4-周）"><a href="#入门路线（2-4-周）" class="headerlink" title="入门路线（2-4 周）"></a>入门路线（2-4 周）</h3><ol><li>在 Cursor 中完成 3 个完整的小项目（如 todo list、天气应用）</li><li>学会使用 Ctrl+K 和 Ctrl+I 两种对话模式</li><li>掌握 @ 符号的日常用法（<code>@Files</code>、<code>@Web</code>）</li><li>能够用 AI 进行基本的 Debug</li></ol><h3 id="进阶路线（4-8-周）"><a href="#进阶路线（4-8-周）" class="headerlink" title="进阶路线（4-8 周）"></a>进阶路线（4-8 周）</h3><ol><li>建立自己的 Prompt 模板库</li><li>配置项目级 AI 规则文件（如 <code>.cursorrules</code> 和 <code>CLAUDE.md</code>）</li><li>用 TDD 流程：先让 AI 写测试，再让 AI 写实现</li><li>用 AI 做 Code Review</li></ol><h3 id="高阶路线（8-16-周）"><a href="#高阶路线（8-16-周）" class="headerlink" title="高阶路线（8-16 周）"></a>高阶路线（8-16 周）</h3><ol><li>理解 Agent 的工作机制和局限性</li><li>使用 OpenCode&#x2F;Claude Code 完成一个完整的 Feature 开发</li><li>编写自己的 Skill 或 MCP Server</li><li>设计团队的 AI 辅助开发流程</li></ol><hr><h2 id="1-5-本文涉及的工具和文件一览"><a href="#1-5-本文涉及的工具和文件一览" class="headerlink" title="1.5 本文涉及的工具和文件一览"></a>1.5 本文涉及的工具和文件一览</h2><p>本系列文章中会反复提到以下工具和它们的配置文件，这里统一做一个概览：</p><table><thead><tr><th>工具</th><th>一句话概括</th><th>关键配置文件</th></tr></thead><tbody><tr><td><strong>Cursor</strong></td><td>AI-first IDE，代码补全 + Agent 模式</td><td><code>.cursorrules</code>（项目根目录）&#x2F; <code>.cursor/rules/*.mdc</code></td></tr><tr><td><strong>Claude Code</strong></td><td>终端里的 AI Agent，可自主操作</td><td><code>CLAUDE.md</code>（项目根目录）</td></tr><tr><td><strong>OpenCode</strong></td><td>AI 赋能框架，支持多 Agent 协作</td><td><code>.opencode/AGENTS.md</code> &#x2F; <code>opencode.json</code></td></tr><tr><td><strong>GitHub Copilot</strong></td><td>AI 代码补全，IDE 插件</td><td><code>.github/copilot-instructions.md</code></td></tr></tbody></table><p>各个配置文件的详细说明：</p><table><thead><tr><th>文件名</th><th>所属工具</th><th>位置</th><th>作用</th></tr></thead><tbody><tr><td><code>.cursorrules</code></td><td>Cursor</td><td>项目根目录</td><td>定义 Cursor AI 的项目行为规则</td></tr><tr><td><code>.cursor/rules/*.mdc</code></td><td>Cursor（新版）</td><td><code>.cursor/rules/</code></td><td>新版 Cursor 规则系统，支持按目录&#x2F;文件匹配</td></tr><tr><td><code>CLAUDE.md</code></td><td>Claude Code</td><td>项目根目录</td><td>Claude Code 的项目级别指令</td></tr><tr><td><code>AGENTS.md</code></td><td>OpenCode</td><td><code>.opencode/AGENTS.md</code></td><td>OpenCode Agent 的层级知识库</td></tr><tr><td><code>.github/copilot-instructions.md</code></td><td>GitHub Copilot</td><td><code>.github/</code></td><td>Copilot 的自定义指令</td></tr><tr><td><code>opencode.json</code> &#x2F; <code>opencode.jsonc</code></td><td>OpenCode</td><td>项目根目录</td><td>OpenCode 全局配置</td></tr><tr><td><code>SKILL.md</code></td><td>OpenCode Skills</td><td><code>.opencode/skills/</code></td><td>自定义 Skill 的定义文件</td></tr></tbody></table><p>以上配置文件的完整示例可以在本系列的配置参考中找到，可直接复制到项目中使用。你也可以根据项目需求从 Prompt 模板库中按需复用各类模板。</p><hr><h2 id="系列导航"><a href="#系列导航" class="headerlink" title="系列导航"></a>系列导航</h2><p>本系列包含以下章节，本文作为第一章已发布：</p><table><thead><tr><th>章节</th><th>核心内容</th><th>状态</th></tr></thead><tbody><tr><td><strong>第一章 认知篇</strong></td><td>AI 工具分类、能力模型迁移、学习路径</td><td>✅ 已发布</td></tr><tr><td><strong>第二章 全链路提效篇</strong></td><td>需求→设计→编码→测试→Review→部署全流程 AI 提效，含 SDD+TDD 完整 SOP</td><td>即将发布</td></tr><tr><td><strong>第三章 基础篇：Cursor 实战</strong></td><td>三种模式、Agent 用法、@ 符号大全、Debug 技巧</td><td>即将发布</td></tr><tr><td><strong>第四章 进阶篇：Prompt 工程</strong></td><td>结构化 Prompt、Context 管理、模板化、配置文件详解</td><td>即将发布</td></tr><tr><td><strong>第五章 高阶篇：Agent 工作流</strong></td><td>Agent 原理、多 Agent 协作、Skills&#x2F;MCP、全自动 Feature 开发</td><td>即将发布</td></tr><tr><td><strong>第六章 面试篇</strong></td><td>高频 AI 面试题 + 参考答案、STAR 法则包装</td><td>即将发布</td></tr><tr><td><strong>第七章 避坑篇</strong></td><td>AI 幻觉、安全合规、能力退化、边界判断</td><td>即将发布</td></tr></tbody></table><hr><h2 id="行动指引：现在就开始"><a href="#行动指引：现在就开始" class="headerlink" title="行动指引：现在就开始"></a>行动指引：现在就开始</h2><p>读完本文后，如果只做一件事，那就是<strong>立刻打开一个项目，用 AI 完成一个小功能</strong>。以下是一个快速上手指南：</p><ol><li><strong>在 Cursor 中打开项目</strong>，将项目级 AI 规则文件复制到根目录</li><li><strong>自定义 CLAUDE.md</strong> 中的项目描述信息</li><li>从 <strong>Prompt 模板库</strong> 中按需复用模板</li><li><strong>阅读第二章</strong> 了解如何用 AI 驱动完整开发流程</li></ol><p>工具只是起点，方法才是核心。下一篇文章，我们将进入全链路提效篇，从需求到上线完整走一遍 AI 驱动的开发流程。</p><blockquote><p>最后更新：2025-04-15</p></blockquote>]]>
    </content>
    <id>https://github.com/wz71014q/2025/04/15/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-%E8%AE%A4%E7%9F%A5%E7%AF%87/</id>
    <link href="https://github.com/wz71014q/2025/04/15/AI%E6%97%B6%E4%BB%A3%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97-%E8%AE%A4%E7%9F%A5%E7%AF%87/"/>
    <published>2025-04-15T02:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="AI-时代前端工程师实战指南：认知篇"><a href="#AI-时代前端工程师实战指南：认知篇" class="headerlink" title="AI 时代前端工程师实战指南：认知篇"></a>AI 时代前端工程师实战指南：认知篇</h1><blockquote>
<p>从工具使用到 Agent 驱动开发，用 AI 重塑整个软件交付流程</p>
<p>适用读者：初中级、高级前端工程师<br>对应工具：Cursor · Claude Code · OpenCode · GitHub Copilot</p>
</blockquote>
<hr>
<p>如果要用五句话概括 AI 时代前端工程师需要建立的核心认知，那就是：</p>
<ol>
<li><strong>AI 不是搜索引擎</strong> — 它是你的结对程序员，区别在于你如何描述问题</li>
<li><strong>Prompt 是新的编程语言</strong> — 写好 Prompt 和写好代码同样重要</li>
<li><strong>Agent 是下一代的 IDE</strong> — 不只是代码补全，而是能自主执行任务的开发伙伴</li>
<li><strong>AI 提效是全链路的</strong> — 从需求分析到上线运维，每个环节都能看到显著提升</li>
<li><strong>面试考的不是工具</strong> — 是你的方法论：如何判断、如何使用、如何保证质量</li>
</ol>
<p>这些观点会贯穿整个系列。本文作为开篇，先帮你建立起对 AI 工具的全局认知，理解能力模型的迁移方向，为后续实战打好基础。</p>
<hr>
<h2 id="1-1-AI-工具分类矩阵"><a href="#1-1-AI-工具分类矩阵" class="headerlink" title="1.1 AI 工具分类矩阵"></a>1.1 AI 工具分类矩阵</h2>]]>
    </summary>
    <title>AI时代前端工程师实战指南：认知篇</title>
    <updated>2026-05-22T04:41:16.195Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="个人笔记" scheme="https://github.com/wz71014q/categories/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    <category term="移动端适配问题" scheme="https://github.com/wz71014q/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E9%80%82%E9%85%8D%E9%97%AE%E9%A2%98/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <category term="个人笔记" scheme="https://github.com/wz71014q/tags/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    <category term="移动端适配问题" scheme="https://github.com/wz71014q/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E9%80%82%E9%85%8D%E9%97%AE%E9%A2%98/"/>
    <content>
      <![CDATA[<h2 id="1px适配"><a href="#1px适配" class="headerlink" title="1px适配"></a>1px适配</h2><p>&emsp;&emsp;在 Retina 屏（DPR ≥ 2）上，CSS 的 1px 在物理上其实是 2px 或 3px，看起来比设计稿粗一圈。下面是几种常用的解决方案。</p><h3 id="方案一：transform-scale-模拟"><a href="#方案一：transform-scale-模拟" class="headerlink" title="方案一：transform scale 模拟"></a>方案一：transform scale 模拟</h3><p>&emsp;&emsp;用伪元素画一条 1px 的线，然后通过 <code>scaleY(0.5)</code> 在垂直方向压缩一半：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.hairline</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.hairline</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">bottom</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">1px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#ccc</span>;</span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">scaleY</span>(<span class="number">0.5</span>);</span><br><span class="line">  <span class="attribute">transform-origin</span>: <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;同理，<code>scale(0.5)</code> 可以做完整的 1px 边框：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.border-1px</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.border-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">200%</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">200%</span>;</span><br><span class="line">  <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#ccc</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">4px</span>;</span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">0.5</span>);</span><br><span class="line">  <span class="attribute">transform-origin</span>: left top;</span><br><span class="line">  <span class="attribute">pointer-events</span>: none;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="方案二：用-0-5px"><a href="#方案二：用-0-5px" class="headerlink" title="方案二：用 0.5px"></a>方案二：用 0.5px</h3><p>&emsp;&emsp;部分现代浏览器（iOS 8+）支持 0.5px 写法，但兼容性有限：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.border-thin</span> &#123;</span><br><span class="line">  <span class="attribute">border-width</span>: <span class="number">0.5px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="方案三：viewport-缩放（不推荐）"><a href="#方案三：viewport-缩放（不推荐）" class="headerlink" title="方案三：viewport 缩放（不推荐）"></a>方案三：viewport 缩放（不推荐）</h3><p>&emsp;&emsp;通过修改 viewport 的 scale 来让 1px 对应 1 个物理像素。但会影响整个页面的布局和 rem 计算，副作用大，很少用。</p><h2 id="系统软键盘对-fixed-布局影响"><a href="#系统软键盘对-fixed-布局影响" class="headerlink" title="系统软键盘对 fixed 布局影响"></a>系统软键盘对 fixed 布局影响</h2><p>&emsp;&emsp;移动端最头疼的问题之一。当页面底部有个 fixed 定位的输入框&#x2F;按钮时，软键盘弹出会导致布局错乱。</p><h3 id="问题表现"><a href="#问题表现" class="headerlink" title="问题表现"></a>问题表现</h3><p>&emsp;&emsp;点击底部固定输入框 → 软键盘弹出 → <code>position: fixed</code> 的元素不再相对视口定位，而是相对键盘上方的可视区域定位。导致：</p><ul><li>底部固定栏被顶到键盘上方</li><li>键盘收起后底部固定栏没有回到原位</li><li>Android 上表现尤其混乱（不同厂商差异大）</li></ul><h3 id="解决思路"><a href="#解决思路" class="headerlink" title="解决思路"></a>解决思路</h3><h4 id="1-避免在底部-fixed-元素中使用输入框"><a href="#1-避免在底部-fixed-元素中使用输入框" class="headerlink" title="1. 避免在底部 fixed 元素中使用输入框"></a>1. 避免在底部 fixed 元素中使用输入框</h4><p>&emsp;&emsp;最简单的方案：将底部固定栏改为 <code>position: static</code>，在键盘弹出时自动滚动到可视区域。或者让输入框在获得焦点时固定在顶部。</p><h4 id="2-用视口单位-页面滚动代替-fixed"><a href="#2-用视口单位-页面滚动代替-fixed" class="headerlink" title="2. 用视口单位 + 页面滚动代替 fixed"></a>2. 用视口单位 + 页面滚动代替 fixed</h4><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.fixed-bottom</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: fixed;</span><br><span class="line">  <span class="attribute">bottom</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">right</span>: <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> screen <span class="keyword">and</span> (<span class="attribute">max-height</span>: <span class="number">500px</span>) &#123;</span><br><span class="line">  <span class="selector-class">.fixed-bottom</span> &#123;</span><br><span class="line">    <span class="attribute">position</span>: static;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3-监听-resize-或-focusin-focusout"><a href="#3-监听-resize-或-focusin-focusout" class="headerlink" title="3. 监听 resize 或 focusin&#x2F;focusout"></a>3. 监听 resize 或 focusin&#x2F;focusout</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> bottomBar = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;.fixed-bottom&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;resize&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">activeElement</span>.<span class="property">tagName</span> === <span class="string">&#x27;INPUT&#x27;</span> ||</span><br><span class="line">      <span class="variable language_">document</span>.<span class="property">activeElement</span>.<span class="property">tagName</span> === <span class="string">&#x27;TEXTAREA&#x27;</span>) &#123;</span><br><span class="line">    bottomBar.<span class="property">style</span>.<span class="property">position</span> = <span class="string">&#x27;static&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;focusout&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  bottomBar.<span class="property">style</span>.<span class="property">position</span> = <span class="string">&#x27;fixed&#x27;</span>;</span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">scrollTo</span>(<span class="number">0</span>, <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">scrollHeight</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="4-使用-VisualViewport-API（现代方案）"><a href="#4-使用-VisualViewport-API（现代方案）" class="headerlink" title="4. 使用 VisualViewport API（现代方案）"></a>4. 使用 VisualViewport API（现代方案）</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="property">visualViewport</span>) &#123;</span><br><span class="line">  <span class="variable language_">window</span>.<span class="property">visualViewport</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;resize&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> diff = <span class="variable language_">window</span>.<span class="property">innerHeight</span> - <span class="variable language_">window</span>.<span class="property">visualViewport</span>.<span class="property">height</span>;</span><br><span class="line">    <span class="keyword">if</span> (diff &gt; <span class="number">100</span>) &#123;</span><br><span class="line">      bottomBar.<span class="property">style</span>.<span class="property">position</span> = <span class="string">&#x27;static&#x27;</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      bottomBar.<span class="property">style</span>.<span class="property">position</span> = <span class="string">&#x27;fixed&#x27;</span>;</span><br><span class="line">      bottomBar.<span class="property">style</span>.<span class="property">bottom</span> = <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="经验总结"><a href="#经验总结" class="headerlink" title="经验总结"></a>经验总结</h3><ul><li><strong>iOS</strong>：相对友好，键盘弹出后 fixed 元素的定位还比较稳定</li><li><strong>Android</strong>：碎片化严重，不同厂商（华为、小米、三星）表现差异大，建议在真机上测试</li><li>最稳妥的做法还是<strong>不要让输入框放在 fixed 栏里</strong>，改成随页面滚动</li></ul>]]>
    </content>
    <id>https://github.com/wz71014q/2021/01/10/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E9%80%82%E9%85%8D%E9%97%AE%E9%A2%98%E7%BA%AA%E5%BD%95/</id>
    <link href="https://github.com/wz71014q/2021/01/10/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E9%80%82%E9%85%8D%E9%97%AE%E9%A2%98%E7%BA%AA%E5%BD%95/"/>
    <published>2021-01-10T12:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="1px适配"><a href="#1px适配" class="headerlink" title="1px适配"></a>1px适配</h2><p>&emsp;&emsp;在 Retina 屏（DPR ≥ 2）上，CSS 的 1px 在物理上其实是 2px 或 3px，看起来比设计稿粗一圈。下面是几种常用的解决方案。</p>
<h3 id="方案一：transform-scale-模拟"><a href="#方案一：transform-scale-模拟" class="headerlink" title="方案一：transform scale 模拟"></a>方案一：transform scale 模拟</h3><p>&emsp;&emsp;用伪元素画一条 1px 的线，然后通过 <code>scaleY(0.5)</code> 在垂直方向压缩一半：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.hairline</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.hairline</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">bottom</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">1px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#ccc</span>;</span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">scaleY</span>(<span class="number">0.5</span>);</span><br><span class="line">  <span class="attribute">transform-origin</span>: <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>&emsp;&emsp;同理，<code>scale(0.5)</code> 可以做完整的 1px 边框：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.border-1px</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.border-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">200%</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">200%</span>;</span><br><span class="line">  <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#ccc</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">4px</span>;</span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">0.5</span>);</span><br><span class="line">  <span class="attribute">transform-origin</span>: left top;</span><br><span class="line">  <span class="attribute">pointer-events</span>: none;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="方案二：用-0-5px"><a href="#方案二：用-0-5px" class="headerlink" title="方案二：用 0.5px"></a>方案二：用 0.5px</h3>]]>
    </summary>
    <title>移动端适配问题记录</title>
    <updated>2026-05-22T05:09:50.864Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="webpack" scheme="https://github.com/wz71014q/categories/webpack/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <category term="webpack" scheme="https://github.com/wz71014q/tags/webpack/"/>
    <content>
      <![CDATA[<h2 id="项目背景"><a href="#项目背景" class="headerlink" title="项目背景"></a>项目背景</h2><p>&emsp;&emsp;项目中有些地方引用了本地的mock数据，这些数据全部在一个mock.js的文件中，由于数据较多，比较占位置，所以想在生产环境下移除该部分代码。具体实现还得靠webpack插件。以这个<a href="https://github.com/wz71014q/optimizewebpack">demo</a>为例进行讲解。</p><h2 id="关键点"><a href="#关键点" class="headerlink" title="关键点"></a>关键点</h2><ul><li><a href="https://webpack.docschina.org/plugins/define-plugin/">webpack.DefinePlugin</a> 定义工程中的全局变量，本项目中定义了ISDEBUG来判断运行环境是否是生产环境</li><li><a href="https://github.com/webpack-contrib/terser-webpack-plugin">terser-webpack-plugin</a> 压缩代码，与uglifyjs-webpack-plugin功能一样，甚至参数都相差不大。但是对于ES6的支持程度比后者更好。该插件优化项之一是去除项目中无效代码，这是核心依赖项</li><li><a href="https://github.com/webpack-contrib/webpack-bundle-analyzer">webpack-bundle-analyzer</a> 分析打包结果</li><li>项目中mock.js的引用方式要使用Commonjs标准的require(‘mock’)</li></ul><span id="more"></span><h2 id="项目目录结构"><a href="#项目目录结构" class="headerlink" title="项目目录结构"></a>项目目录结构</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">├─config <span class="comment">// webpack配置</span></span><br><span class="line">│   ├─entry.<span class="property">js</span></span><br><span class="line">│   ├─logConfig.<span class="property">js</span></span><br><span class="line">│   ├─server.<span class="property">js</span></span><br><span class="line">│   ├─webpack.<span class="property">common</span>.<span class="property">js</span></span><br><span class="line">│   ├─webpack.<span class="property">dev</span>.<span class="property">js</span></span><br><span class="line">│   └─webpack.<span class="property">pro</span>.<span class="property">js</span></span><br><span class="line">├─dist <span class="comment">// 打包输出目录</span></span><br><span class="line">├─entries <span class="comment">// 多页入口</span></span><br><span class="line">├─public <span class="comment">// 模板目录</span></span><br><span class="line">└─src</span><br><span class="line">    ├─mock <span class="comment">// 本地mock数据，将要在生产环境下移除的部分</span></span><br><span class="line">    │  └─mock.<span class="property">js</span></span><br><span class="line">    ├─service <span class="comment">// 引用mock数据的地方，这里要用require动态引入mock.js</span></span><br><span class="line">    │  ├─details.<span class="property">js</span></span><br><span class="line">    │  ├─life.<span class="property">js</span></span><br><span class="line">    │  └─menu.<span class="property">js</span></span><br><span class="line">    └─views <span class="comment">// 业务逻辑页面</span></span><br><span class="line">        ├─detail</span><br><span class="line">        │  └─index.<span class="property">vue</span></span><br><span class="line">        ├─life</span><br><span class="line">        │  └─index.<span class="property">vue</span></span><br><span class="line">        │─menu</span><br><span class="line">        │  └─index.<span class="property">vue</span></span><br><span class="line">        └─<span class="title class_">Home</span>.<span class="property">vue</span></span><br></pre></td></tr></table></figure><p>项目中service下的每个文件都会引用mock.js文件。优化前，这些文件内容如下：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// mock.js</span></span><br><span class="line"><span class="keyword">const</span> mockData = &#123;</span><br><span class="line">  <span class="attr">code</span>: <span class="number">0</span>,</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;菲尼克斯&#x27;</span>,</span><br><span class="line">    <span class="attr">job</span>: <span class="string">&#x27;净化者&#x27;</span>,</span><br><span class="line">    <span class="attr">position</span>: <span class="string">&quot;圣堂武士&quot;</span>,</span><br><span class="line">    <span class="attr">profession</span>: <span class="string">&quot;净化者&quot;</span>,</span><br><span class="line">    <span class="attr">duty</span>: <span class="string">&quot;执政官&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> mockData;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// service下的js文件</span></span><br><span class="line"><span class="keyword">import</span> mockData <span class="keyword">from</span> <span class="string">&#x27;mockDataPath&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">ISDEBUG</span> = process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="variable constant_">ISDEBUG</span>) &#123;</span><br><span class="line">  result = mockData;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> result;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--&gt;views下的页面文件&lt;--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span> <span class="attr">class</span>=<span class="string">&quot;test-text&quot;</span>&gt;</span></span><br><span class="line">      Detail</span><br><span class="line">    <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">      &#123;&#123; data &#125;&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">import</span> mockData <span class="keyword">from</span> <span class="string">&#x27;@/service/details&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">name</span>: <span class="string">&#x27;Detail&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">data</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">data</span>: <span class="string">&#x27;&#x27;</span></span></span><br><span class="line"><span class="language-javascript">    &#125;;</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">mounted</span> () &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">data</span> = mockData;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">lang</span>=<span class="string">&quot;scss&quot;</span> <span class="attr">scoped</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-class">.test-text</span> &#123;</span></span><br><span class="line"><span class="language-css">  <span class="attribute">font-size</span>: <span class="number">50px</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br></pre></td></tr></table></figure><p>没有优化的打包结果：</p><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/webpack/conditional/noMock.jpg" alt="noMock"></p><h2 id="优化步骤"><a href="#优化步骤" class="headerlink" title="优化步骤"></a>优化步骤</h2><h3 id="webpack增加配置：terser-webpack-plugin"><a href="#webpack增加配置：terser-webpack-plugin" class="headerlink" title="webpack增加配置：terser-webpack-plugin"></a>webpack增加配置：terser-webpack-plugin</h3><p>mock.js文件中的数据只是本地调试时使用的，所以，我们的目的是在生产环境下移除这部分代码。在上文我们提到的简化代码的插件<a href="https://github.com/webpack-contrib/terser-webpack-plugin">terser-webpack-plugin</a>，它使用<a href="https://github.com/terser/terser">terser.js</a>来优化代码，并且有一项功能，就是打包时移除无效代码:</p><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/webpack/conditional/terser.png" alt="mock"></p><p>什么是无效代码，就像这种：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="literal">false</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;这是一段无效代码&#x27;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所以我们需要对mock文件引用方式做一个调整</p><h3 id="更新本地文件引用方式"><a href="#更新本地文件引用方式" class="headerlink" title="更新本地文件引用方式"></a>更新本地文件引用方式</h3><h4 id="require与import的区别"><a href="#require与import的区别" class="headerlink" title="require与import的区别"></a>require与import的区别</h4><p>&emsp;&emsp;在这里需要说一下require与import引入模块的一些区别。因为我们需要把项目中引用模块的方式由ES6的import全部改成Commonjs标准的require。原因如下：</p><ul><li>import是ES6的模块引入方式，它是在JS引擎编译阶段执行的。在代码运行前，遇到import就会生成一个只读引用，然后在运行阶段，碰到有用到引入模块值的再去引用模块取值，可能项目中并没有引用引入模块的值，但是已经声明了，就会执行。所以无法在生产环境下移除该部分代码，</li><li>require是运行时执行，可以做到按需加载。</li><li>以后import可能也会支持按需引入，在ES2020提案中，可以使用<a href="https://es6.ruanyifeng.com/#docs/module">import(‘module’)</a>来按需加载。目前可以使用babel的<a href="https://www.npmjs.com/package/@babel/plugin-syntax-dynamic-import">@babel&#x2F;plugin-syntax-dynamic-import</a>插件进行兼容</li></ul><h4 id="优化结果"><a href="#优化结果" class="headerlink" title="优化结果"></a>优化结果</h4><p>修改文件引用方式：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// mock.js</span></span><br><span class="line"><span class="keyword">const</span> mockData = &#123;</span><br><span class="line">  <span class="attr">code</span>: <span class="number">0</span>,</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;菲尼克斯&#x27;</span>,</span><br><span class="line">    <span class="attr">job</span>: <span class="string">&#x27;净化者&#x27;</span>,</span><br><span class="line">    <span class="attr">position</span>: <span class="string">&quot;圣堂武士&quot;</span>,</span><br><span class="line">    <span class="attr">profession</span>: <span class="string">&quot;净化者&quot;</span>,</span><br><span class="line">    <span class="attr">duty</span>: <span class="string">&quot;执政官&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = mockData;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// service下的js文件</span></span><br><span class="line"><span class="keyword">let</span> result = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line"><span class="keyword">if</span> (<span class="variable constant_">ISDEBUG</span>) &#123; <span class="comment">// 全局变量ISDEBUG控制代码是否有效</span></span><br><span class="line">  <span class="keyword">const</span> mockData = <span class="built_in">require</span>(<span class="string">&#x27;mockDataPath&#x27;</span>); <span class="comment">// require按需加载</span></span><br><span class="line">  result = mockData;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> result;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack部分增加配置</span></span><br><span class="line">webpackChainConfig</span><br><span class="line">  .<span class="title function_">plugin</span>(<span class="string">&#x27;DefinePlugin&#x27;</span>)</span><br><span class="line">  .<span class="title function_">use</span>(webpack.<span class="property">DefinePlugin</span>, [&#123;</span><br><span class="line">    <span class="attr">ISDEBUG</span>: process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span> <span class="comment">// 定义全局变量ISDEBUG</span></span><br><span class="line">  &#125;])</span><br><span class="line">webpackChainConfig</span><br><span class="line">  .<span class="property">optimization</span></span><br><span class="line">  .<span class="title function_">minimizer</span>(<span class="string">&#x27;terser&#x27;</span>)</span><br><span class="line">  .<span class="title function_">use</span>(<span class="title class_">TerserPlugin</span>, [&#123;</span><br><span class="line">      <span class="attr">exclude</span>: <span class="regexp">/\.min\.js$/</span>, <span class="comment">// 排除已压缩项</span></span><br><span class="line">      <span class="attr">cache</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">parallel</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">sourceMap</span>: <span class="literal">false</span>,</span><br><span class="line">      <span class="attr">extractComments</span>: <span class="literal">false</span>, <span class="comment">// 不生成注释文档</span></span><br><span class="line">      <span class="attr">terserOptions</span>: &#123;</span><br><span class="line">        <span class="attr">warnings</span>: <span class="literal">false</span>,</span><br><span class="line">        <span class="attr">compress</span>: &#123;</span><br><span class="line">          <span class="attr">booleans</span>: <span class="literal">true</span>,</span><br><span class="line">          <span class="attr">if_return</span>: <span class="literal">true</span>,</span><br><span class="line">          <span class="attr">sequences</span>: <span class="literal">true</span>,</span><br><span class="line">          <span class="attr">unused</span>: <span class="literal">true</span>, <span class="comment">// 移除无效代码</span></span><br><span class="line">          <span class="attr">drop_debugger</span>: <span class="literal">true</span>,</span><br><span class="line">          <span class="attr">pure_funcs</span>: [<span class="string">&#x27;console.log&#x27;</span>] <span class="comment">// 移除console.log</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">output</span>: &#123;</span><br><span class="line">          <span class="attr">comments</span>: <span class="literal">false</span> <span class="comment">// 移除注释</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;]</span><br><span class="line">  )</span><br></pre></td></tr></table></figure><p>优化结果如下，mock.js已经没有了</p><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/webpack/conditional/mock.jpg" alt="mock"></p><h4 id="方案更新"><a href="#方案更新" class="headerlink" title="方案更新"></a>方案更新</h4><ul><li>使用webpack内置插件ProvidePlugin全局引入公共模块，需要使用时即可直接使用，无需每次手动require引入</li></ul><h2 id="尝试方案"><a href="#尝试方案" class="headerlink" title="尝试方案"></a>尝试方案</h2><p>&emsp;&emsp;在这之前，还试过webpack属性<a href="https://webpack.docschina.org/configuration/externals/">external</a>，但无法达成目的。因为它的作用是打包时排除某一个已经采用CDN或其他方式引用的库或文件。关键点是已经设置了CDN等<font color=#FF0000>别的引入方式</font>。因为打包之后该引用的地方还是会继续引用，区别是本地环境下，可能从本地node_modules引入。打包时，webpack会自动排除该文件，改从已经设置好的CDN引入了，这样可以极大的减少打包体积，但是会增加网络请求量，使用时也要权衡利弊。这个属性之前没怎么用过，不然也不会在这里采坑~</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li>使用<a href="https://webpack.docschina.org/plugins/define-plugin/">webpack.DefinePlugin</a>定义全局变量ISDEBUG控制代码是否有效；</li><li>使用require按需引入，或用ProvidePlugin全局引入；</li><li>使用<a href="https://github.com/webpack-contrib/terser-webpack-plugin">terser-webpack-plugin</a>优化代码，移除无效代码</li></ol><h2 id="参考内容"><a href="#参考内容" class="headerlink" title="参考内容"></a>参考内容</h2><p><a href="https://es6.ruanyifeng.com/#docs/module">ECMAScript 6 入门——阮一峰</a></p>]]>
    </content>
    <id>https://github.com/wz71014q/2020/04/30/webpack%E9%80%89%E6%8B%A9%E6%80%A7%E7%BC%96%E8%AF%91/</id>
    <link href="https://github.com/wz71014q/2020/04/30/webpack%E9%80%89%E6%8B%A9%E6%80%A7%E7%BC%96%E8%AF%91/"/>
    <published>2020-04-30T06:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="项目背景"><a href="#项目背景" class="headerlink" title="项目背景"></a>项目背景</h2><p>&emsp;&emsp;项目中有些地方引用了本地的mock数据，这些数据全部在一个mock.js的文件中，由于数据较多，比较占位置，所以想在生产环境下移除该部分代码。具体实现还得靠webpack插件。以这个<a href="https://github.com/wz71014q/optimizewebpack">demo</a>为例进行讲解。</p>
<h2 id="关键点"><a href="#关键点" class="headerlink" title="关键点"></a>关键点</h2><ul>
<li><a href="https://webpack.docschina.org/plugins/define-plugin/">webpack.DefinePlugin</a> 定义工程中的全局变量，本项目中定义了ISDEBUG来判断运行环境是否是生产环境</li>
<li><a href="https://github.com/webpack-contrib/terser-webpack-plugin">terser-webpack-plugin</a> 压缩代码，与uglifyjs-webpack-plugin功能一样，甚至参数都相差不大。但是对于ES6的支持程度比后者更好。该插件优化项之一是去除项目中无效代码，这是核心依赖项</li>
<li><a href="https://github.com/webpack-contrib/webpack-bundle-analyzer">webpack-bundle-analyzer</a> 分析打包结果</li>
<li>项目中mock.js的引用方式要使用Commonjs标准的require(‘mock’)</li>
</ul>]]>
    </summary>
    <title>webpack选择性编译</title>
    <updated>2026-05-15T08:53:54.866Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="HTML" scheme="https://github.com/wz71014q/categories/HTML/"/>
    <category term="Vue" scheme="https://github.com/wz71014q/categories/Vue/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <category term="HTML" scheme="https://github.com/wz71014q/tags/HTML/"/>
    <category term="Vue" scheme="https://github.com/wz71014q/tags/Vue/"/>
    <content>
      <![CDATA[<p>&emsp;&emsp;框架为什么要有生命周期？因为框架就像是组装好的电脑，每个人的电脑里软件都不一样，买来了电脑，需要让用户可以有办法自己装一些软件。也就是实际业务不同，你要借助框架去做一些事，所以需要框架给一些接口让外部业务去调用，去填充数据。从new Vue()开始，这个框架的生命周期(作为一个构造函数函数)就已经开始了，但是我们平时使用的是它在一些特定的时刻的抛出的接口，这个才是定义的生命周期。</p><h1 id="Vue构造函数"><a href="#Vue构造函数" class="headerlink" title="Vue构造函数"></a>Vue构造函数</h1><p>&emsp;&emsp;这里学习的源码版本是2.6.11，构造函数的定义在src\core\instance\index.js。源码中Vue的构造函数很简单，首先检测是不是作为构造函数使用，然后执行初始化 this._init(options)</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Vue</span> (<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span> &amp;&amp; !(<span class="variable language_">this</span> <span class="keyword">instanceof</span> <span class="title class_">Vue</span>) ) &#123;</span><br><span class="line">    <span class="title function_">warn</span>(<span class="string">&#x27;Vue is a constructor and should be called with the `new` keyword&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">_init</span>(options)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">initMixin</span>(<span class="title class_">Vue</span>)</span><br><span class="line"><span class="title function_">stateMixin</span>(<span class="title class_">Vue</span>)</span><br><span class="line"><span class="title function_">eventsMixin</span>(<span class="title class_">Vue</span>)</span><br><span class="line"><span class="title function_">lifecycleMixin</span>(<span class="title class_">Vue</span>)</span><br><span class="line"><span class="title function_">renderMixin</span>(<span class="title class_">Vue</span>)</span><br></pre></td></tr></table></figure><span id="more"></span><h1 id="init"><a href="#init" class="headerlink" title="_init"></a>_init</h1><p>初始化方法是在上面的initMixin中定义的，位置是在src\core\instance\init.js中</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">_init</span> = <span class="keyword">function</span> (<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> vm = <span class="variable language_">this</span>;</span><br><span class="line">  <span class="comment">// a uid</span></span><br><span class="line">  vm.<span class="property">_uid</span> = uid$2++;</span><br><span class="line">  <span class="keyword">var</span> startTag, endTag;</span><br><span class="line">  <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">  <span class="keyword">if</span> ( config.<span class="property">performance</span> &amp;&amp; mark) &#123; <span class="comment">// 性能监控</span></span><br><span class="line">    startTag = <span class="string">&quot;vue-perf-start:&quot;</span> + (vm.<span class="property">_uid</span>);</span><br><span class="line">    endTag = <span class="string">&quot;vue-perf-end:&quot;</span> + (vm.<span class="property">_uid</span>);</span><br><span class="line">    <span class="title function_">mark</span>(startTag);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// a flag to avoid this being observed</span></span><br><span class="line">  vm.<span class="property">_isVue</span> = <span class="literal">true</span>;</span><br><span class="line">  <span class="comment">// merge options</span></span><br><span class="line">  <span class="keyword">if</span> (options &amp;&amp; options.<span class="property">_isComponent</span>) &#123; <span class="comment">// 优化内部组件实例</span></span><br><span class="line">    <span class="title function_">initInternalComponent</span>(vm, options); <span class="comment">// 初始化内部组件</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    vm.<span class="property">$options</span> = <span class="title function_">mergeOptions</span>(</span><br><span class="line">      <span class="title function_">resolveConstructorOptions</span>(vm.<span class="property">constructor</span>),</span><br><span class="line">      options || &#123;&#125;,</span><br><span class="line">      vm</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">/* istanbul ignore else */</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="title function_">initProxy</span>(vm);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// expose real self</span></span><br><span class="line">  vm.<span class="property">_self</span> = vm;</span><br><span class="line">  <span class="title function_">initLifecycle</span>(vm);</span><br><span class="line">  <span class="title function_">initEvents</span>(vm);</span><br><span class="line">  <span class="title function_">initRender</span>(vm);</span><br><span class="line">  <span class="title function_">callHook</span>(vm, <span class="string">&#x27;beforeCreate&#x27;</span>);</span><br><span class="line">  <span class="title function_">initInjections</span>(vm); <span class="comment">// resolve injections before data/props</span></span><br><span class="line">  <span class="title function_">initState</span>(vm);</span><br><span class="line">  <span class="title function_">initProvide</span>(vm); <span class="comment">// resolve provide after data/props</span></span><br><span class="line">  <span class="title function_">callHook</span>(vm, <span class="string">&#x27;created&#x27;</span>);</span><br><span class="line">  <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">  <span class="keyword">if</span> ( config.<span class="property">performance</span> &amp;&amp; mark) &#123; <span class="comment">// 性能监控</span></span><br><span class="line">    vm.<span class="property">_name</span> = <span class="title function_">formatComponentName</span>(vm, <span class="literal">false</span>);</span><br><span class="line">    <span class="title function_">mark</span>(endTag);</span><br><span class="line">    <span class="title function_">measure</span>((<span class="string">&quot;vue &quot;</span> + (vm.<span class="property">_name</span>) + <span class="string">&quot; init&quot;</span>), startTag, endTag);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">$options</span>.<span class="property">el</span>) &#123; <span class="comment">// 开始挂载</span></span><br><span class="line">    vm.$mount(vm.<span class="property">$options</span>.<span class="property">el</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>它的作用是定义vm、uid，合并所有配置，执行生命周期系统、事件系统、渲染系统、数据系统的初始化，最后检测传入的挂载节点无误后执行挂载。其中mergeOptions的作用是合并当前构造函数(新建的Vue的实例)的options和其父级构造函数(Vue)的options属性，Vue自身的配置里包含了自带的组件，指令等，如<transition>。其中会执行对props、Inject、全局指令进行标准化的方法：normalizeProps(child, vm);normalizeInject(child, vm)，normalizeDirectives(child)，props里的类型检测就是在这进行的，另外会合并minxins里的options，具体实现暂且不谈。我们一个一个来看：</p><h2 id="initProxy"><a href="#initProxy" class="headerlink" title="initProxy"></a>initProxy</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> hasProxy = <span class="keyword">typeof</span> <span class="title class_">Proxy</span> !== <span class="string">&#x27;undefined&#x27;</span> &amp;&amp; <span class="title function_">isNative</span>(<span class="title class_">Proxy</span>); <span class="comment">// isNative检测是否JS原生方法</span></span><br><span class="line">initProxy = <span class="keyword">function</span> <span class="title function_">initProxy</span> (<span class="params">vm</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (hasProxy) &#123;</span><br><span class="line">    <span class="comment">// determine which proxy handler to use</span></span><br><span class="line">    <span class="keyword">var</span> options = vm.<span class="property">$options</span>;</span><br><span class="line">    <span class="keyword">var</span> handlers = options.<span class="property">render</span> &amp;&amp; options.<span class="property">render</span>.<span class="property">_withStripped</span></span><br><span class="line">      ? getHandler</span><br><span class="line">      : hasHandler;</span><br><span class="line">    vm.<span class="property">_renderProxy</span> = <span class="keyword">new</span> <span class="title class_">Proxy</span>(vm, handlers);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    vm.<span class="property">_renderProxy</span> = vm;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>initProxy位置在src\core\instance\proxy.js，作用是新建一层代理，对传入的数据进行一次过滤，比如，不能在模板和data中使用以$、_开头的变量，因为这些是Vue的内部持有变量。处理逻辑在getHandler和hasHandler方法中。</p><h2 id="initlifecycle"><a href="#initlifecycle" class="headerlink" title="initlifecycle"></a>initlifecycle</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">initLifecycle</span> (<span class="params">vm</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> options = vm.<span class="property">$options</span>;</span><br><span class="line">  <span class="comment">// locate first non-abstract parent</span></span><br><span class="line">  <span class="keyword">var</span> parent = options.<span class="property">parent</span>; <span class="comment">// 找到第一个父级</span></span><br><span class="line">  <span class="keyword">if</span> (parent &amp;&amp; !options.<span class="property">abstract</span>) &#123;</span><br><span class="line">    <span class="keyword">while</span> (parent.<span class="property">$options</span>.<span class="property">abstract</span> &amp;&amp; parent.<span class="property">$parent</span>) &#123;</span><br><span class="line">      parent = parent.<span class="property">$parent</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    parent.<span class="property">$children</span>.<span class="title function_">push</span>(vm);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 根元素</span></span><br><span class="line">  vm.<span class="property">$parent</span> = parent;</span><br><span class="line">  vm.<span class="property">$root</span> = parent ? parent.<span class="property">$root</span> : vm;</span><br><span class="line">  <span class="comment">// 变量初始化</span></span><br><span class="line">  vm.<span class="property">$children</span> = [];</span><br><span class="line">  vm.<span class="property">$refs</span> = &#123;&#125;;</span><br><span class="line">  vm.<span class="property">_watcher</span> = <span class="literal">null</span>;</span><br><span class="line">  vm.<span class="property">_inactive</span> = <span class="literal">null</span>;</span><br><span class="line">  vm.<span class="property">_directInactive</span> = <span class="literal">false</span>;</span><br><span class="line">  vm.<span class="property">_isMounted</span> = <span class="literal">false</span>;</span><br><span class="line">  vm.<span class="property">_isDestroyed</span> = <span class="literal">false</span>;</span><br><span class="line">  vm.<span class="property">_isBeingDestroyed</span> = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个方法在src\core\instance\lifecycle.js，主要是找到$root，并且初始化$parent、$refs、$children等属性</p><h2 id="initEvents"><a href="#initEvents" class="headerlink" title="initEvents"></a>initEvents</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">initEvents</span> (<span class="params">vm</span>) &#123;</span><br><span class="line">  vm.<span class="property">_events</span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="literal">null</span>);</span><br><span class="line">  vm.<span class="property">_hasHookEvent</span> = <span class="literal">false</span>;</span><br><span class="line">  <span class="comment">// init parent attached events</span></span><br><span class="line">  <span class="keyword">var</span> listeners = vm.<span class="property">$options</span>.<span class="property">_parentListeners</span>;</span><br><span class="line">  <span class="keyword">if</span> (listeners) &#123;</span><br><span class="line">    <span class="title function_">updateComponentListeners</span>(vm, listeners);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>位置是src\core\instance\events.js，initEvents作用是初始化Vue的事件系统的，在Vue实例上新增一个_events属性，并存储初始化的事件。updateComponentListeners的主要作用就是将父组件向子组件添加的事件注册到子组件实例中的_events对象里。</p><h2 id="initRender"><a href="#initRender" class="headerlink" title="initRender"></a>initRender</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">initRender</span> (<span class="params">vm</span>) &#123;</span><br><span class="line">    vm.<span class="property">_vnode</span> = <span class="literal">null</span>;</span><br><span class="line">    vm.<span class="property">_staticTrees</span> = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">var</span> options = vm.<span class="property">$options</span>;</span><br><span class="line">    <span class="keyword">var</span> parentVnode = vm.<span class="property">$vnode</span> = options.<span class="property">_parentVnode</span>;</span><br><span class="line">    <span class="keyword">var</span> renderContext = parentVnode &amp;&amp; parentVnode.<span class="property">context</span>;</span><br><span class="line">    vm.<span class="property">$slots</span> = <span class="title function_">resolveSlots</span>(options.<span class="property">_renderChildren</span>, renderContext); <span class="comment">// 解析slots</span></span><br><span class="line">    vm.<span class="property">$scopedSlots</span> = emptyObject;</span><br><span class="line">    vm.<span class="property">_c</span> = <span class="keyword">function</span> (<span class="params">a, b, c, d</span>) &#123; <span class="keyword">return</span> <span class="title function_">createElement</span>(vm, a, b, c, d, <span class="literal">false</span>); &#125;;</span><br><span class="line">    vm.<span class="property">$createElement</span> = <span class="keyword">function</span> (<span class="params">a, b, c, d</span>) &#123; <span class="keyword">return</span> <span class="title function_">createElement</span>(vm, a, b, c, d, <span class="literal">true</span>); &#125;; <span class="comment">// $createElement是手写render方法时调用的，包含对输入数据的过滤</span></span><br><span class="line">    <span class="keyword">var</span> parentData = parentVnode &amp;&amp; parentVnode.<span class="property">data</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* istanbul ignore else */</span></span><br><span class="line">    &#123;</span><br><span class="line">      <span class="title function_">defineReactive</span>(vm, <span class="string">&#x27;$attrs&#x27;</span>, parentData &amp;&amp; parentData.<span class="property">attrs</span> || emptyObject, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        !isUpdatingChildComponent &amp;&amp; <span class="title function_">warn</span>(<span class="string">&quot;$attrs is readonly.&quot;</span>, vm);</span><br><span class="line">      &#125;, <span class="literal">true</span>);</span><br><span class="line">      <span class="title function_">defineReactive</span>(vm, <span class="string">&#x27;$listeners&#x27;</span>, options.<span class="property">_parentListeners</span> || emptyObject, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        !isUpdatingChildComponent &amp;&amp; <span class="title function_">warn</span>(<span class="string">&quot;$listeners is readonly.&quot;</span>, vm);</span><br><span class="line">      &#125;, <span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>initRender在src\core\instance\render.js。initRender方法主要是初始化渲染属性，定义渲染方法。并且初始化插槽、$attrs、$listeners，其中的defineReactive就是定义响应式系统的一部分内容。initRender之后会触发beforeCreate方法，这时methods、data等还没有初始化，所以没法调用其中的属性。</p><h2 id="initInjections和initProvide"><a href="#initInjections和initProvide" class="headerlink" title="initInjections和initProvide"></a>initInjections和initProvide</h2><p>初始化inject&#x2F;provide</p><h2 id="initState"><a href="#initState" class="headerlink" title="initState"></a>initState</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">initState</span> (<span class="params">vm</span>) &#123;</span><br><span class="line">  vm.<span class="property">_watchers</span> = [];</span><br><span class="line">  <span class="keyword">var</span> opts = vm.<span class="property">$options</span>;</span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">props</span>) &#123; <span class="title function_">initProps</span>(vm, opts.<span class="property">props</span>); &#125;</span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">methods</span>) &#123; <span class="title function_">initMethods</span>(vm, opts.<span class="property">methods</span>); &#125;</span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">data</span>) &#123;</span><br><span class="line">    <span class="title function_">initData</span>(vm);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">observe</span>(vm.<span class="property">_data</span> = &#123;&#125;, <span class="literal">true</span> <span class="comment">/* asRootData */</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">computed</span>) &#123; <span class="title function_">initComputed</span>(vm, opts.<span class="property">computed</span>); &#125;</span><br><span class="line">  <span class="keyword">if</span> (opts.<span class="property">watch</span> &amp;&amp; opts.<span class="property">watch</span> !== nativeWatch) &#123;</span><br><span class="line">    <span class="title function_">initWatch</span>(vm, opts.<span class="property">watch</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>initState的位置在src\core\instance\state.js，作用是初始化props、data、methods、watch、computed等属性。</p><ol><li>initProps主要是将props设置成响应式数据；</li><li>initMethods主要是对命名进行检查——不能与props或data中的变量重名，然后将方法挂载到vm实例上。</li><li>initData也会进行重名检查，并调用observe方法将数据对象标记为响应式对象。</li><li>initComputed对computed属性进行检查，新建一个watchers空对象，再遍历传入的options.computed，每个属性新建一个Watcher实例，表示增加了一个需要被监听的数据依赖。</li><li>initWatch是初始化watch系统，初始化data、computed、watch最终都会调用Object.defineProperty进行数据拦截，它们都是响应式系统的一部分</li><li>initState之后触发created，这个时候数据已经加载完毕，但是还没有挂载，适合做一些与异步请求的业务</li></ol><h2 id="调用生命周期方法的方法callHook"><a href="#调用生命周期方法的方法callHook" class="headerlink" title="调用生命周期方法的方法callHook"></a>调用生命周期方法的方法callHook</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">callHook</span> (<span class="params">vm, hook</span>) &#123;</span><br><span class="line">  <span class="comment">// #7573 disable dep collection when invoking lifecycle hooks</span></span><br><span class="line">  <span class="title function_">pushTarget</span>();</span><br><span class="line">  <span class="keyword">var</span> handlers = vm.<span class="property">$options</span>[hook];</span><br><span class="line">  <span class="keyword">var</span> info = hook + <span class="string">&quot; hook&quot;</span>;</span><br><span class="line">  <span class="keyword">if</span> (handlers) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, j = handlers.<span class="property">length</span>; i &lt; j; i++) &#123;</span><br><span class="line">      <span class="title function_">invokeWithErrorHandling</span>(handlers[i], vm, <span class="literal">null</span>, vm, info);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">_hasHookEvent</span>) &#123;</span><br><span class="line">    vm.$emit(<span class="string">&#x27;hook:&#x27;</span> + hook);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">popTarget</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>callHook在src\core\instance\lifecycle.js中，生命周期内的方法是由callHook调用的，也就是遍历生命周期方法的数组(合并mixins的属性时会创建数组)，顺序执行其中的钩子函数，其中的invokeWithErrorHandling会捕捉执行过程的错误</p><h2 id="挂载-mount"><a href="#挂载-mount" class="headerlink" title="挂载$mount"></a>挂载$mount</h2><p>src\platforms\weex\runtime\index.js</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$mount</span> = <span class="keyword">function</span> (<span class="params">el, hydrating</span>) &#123;</span><br><span class="line">  el = el &amp;&amp; inBrowser ? <span class="title function_">query</span>(el) : <span class="literal">undefined</span>; <span class="comment">// 获取挂载节点</span></span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">mountComponent</span>(<span class="variable language_">this</span>, el, hydrating)</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> mount = <span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$mount</span>;</span><br><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$mount</span> = <span class="keyword">function</span> (<span class="params">el, hydrating</span>) &#123;</span><br><span class="line">  el = el &amp;&amp; <span class="title function_">query</span>(el);</span><br><span class="line">  <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">  <span class="keyword">if</span> (el === <span class="variable language_">document</span>.<span class="property">body</span> || el === <span class="variable language_">document</span>.<span class="property">documentElement</span>) &#123; <span class="comment">// 不能挂载到html/body</span></span><br><span class="line">     <span class="title function_">warn</span>(<span class="string">&quot;Do not mount Vue to &lt;html&gt; or &lt;body&gt; - mount to normal elements instead.&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">var</span> options = <span class="variable language_">this</span>.<span class="property">$options</span>;</span><br><span class="line">  <span class="comment">// resolve template/el and convert to render function</span></span><br><span class="line">  <span class="keyword">if</span> (!options.<span class="property">render</span>) &#123; <span class="comment">// render是已经编译好的渲染方法，如果没有渲染方法，则需要获取用户传入的模板内容将模板字符串转化为渲染方法</span></span><br><span class="line">    <span class="keyword">var</span> template = options.<span class="property">template</span>;</span><br><span class="line">    <span class="keyword">if</span> (template) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">typeof</span> template === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (template.<span class="title function_">charAt</span>(<span class="number">0</span>) === <span class="string">&#x27;#&#x27;</span>) &#123;</span><br><span class="line">          template = <span class="title function_">idToTemplate</span>(template);</span><br><span class="line">          <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">          <span class="keyword">if</span> ( !template) &#123;</span><br><span class="line">            <span class="title function_">warn</span>((<span class="string">&quot;Template element not found or is empty: &quot;</span> + (options.<span class="property">template</span>)), <span class="variable language_">this</span>);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (template.<span class="property">nodeType</span>) &#123;</span><br><span class="line">        template = template.<span class="property">innerHTML</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="title function_">warn</span>(<span class="string">&#x27;invalid template option:&#x27;</span> + template, <span class="variable language_">this</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el) &#123;</span><br><span class="line">      template = <span class="title function_">getOuterHTML</span>(el);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (template) &#123;</span><br><span class="line">      <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">      <span class="keyword">if</span> ( config.<span class="property">performance</span> &amp;&amp; mark) &#123;</span><br><span class="line">        <span class="title function_">mark</span>(<span class="string">&#x27;compile&#x27;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">var</span> ref = <span class="title function_">compileToFunctions</span>(template, &#123;</span><br><span class="line">        <span class="attr">outputSourceRange</span>: <span class="string">&quot;development&quot;</span> !== <span class="string">&#x27;production&#x27;</span>,</span><br><span class="line">        <span class="attr">shouldDecodeNewlines</span>: shouldDecodeNewlines,</span><br><span class="line">        <span class="attr">shouldDecodeNewlinesForHref</span>: shouldDecodeNewlinesForHref,</span><br><span class="line">        <span class="attr">delimiters</span>: options.<span class="property">delimiters</span>,</span><br><span class="line">        <span class="attr">comments</span>: options.<span class="property">comments</span></span><br><span class="line">      &#125;, <span class="variable language_">this</span>);</span><br><span class="line">      <span class="keyword">var</span> render = ref.<span class="property">render</span>;</span><br><span class="line">      <span class="keyword">var</span> staticRenderFns = ref.<span class="property">staticRenderFns</span>;</span><br><span class="line">      options.<span class="property">render</span> = render;</span><br><span class="line">      options.<span class="property">staticRenderFns</span> = staticRenderFns;</span><br><span class="line">      <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">      <span class="keyword">if</span> ( config.<span class="property">performance</span> &amp;&amp; mark) &#123; <span class="comment">// 性能监控</span></span><br><span class="line">        <span class="title function_">mark</span>(<span class="string">&#x27;compile end&#x27;</span>);</span><br><span class="line">        <span class="title function_">measure</span>((<span class="string">&quot;vue &quot;</span> + (<span class="variable language_">this</span>.<span class="property">_name</span>) + <span class="string">&quot; compile&quot;</span>), <span class="string">&#x27;compile&#x27;</span>, <span class="string">&#x27;compile end&#x27;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> mount.<span class="title function_">call</span>(<span class="variable language_">this</span>, el, hydrating)</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>compileToFunctions的作用是将模板编译成渲染函数，该函数接收待编译的模板字符串和编译选项作为参数，返回一个对象，对象里面的render属性即是编译好的渲染函数，最后将渲染函数设置到$options上。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src\core\instance\lifecycle.js</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">mountComponent</span> (<span class="params">vm, el, hydrating</span>) &#123;</span><br><span class="line">  vm.<span class="property">$el</span> = el;</span><br><span class="line">  <span class="keyword">if</span> (!vm.<span class="property">$options</span>.<span class="property">render</span>) &#123;</span><br><span class="line">    vm.<span class="property">$options</span>.<span class="property">render</span> = createEmptyVNode; <span class="comment">// 创建默认的渲染函数</span></span><br><span class="line">    &#123;</span><br><span class="line">      <span class="comment">/* istanbul ignore if */</span> <span class="comment">// 检查挂载节点是否存在并输出开发环境警告信息</span></span><br><span class="line">      <span class="keyword">if</span> ((vm.<span class="property">$options</span>.<span class="property">template</span> &amp;&amp; vm.<span class="property">$options</span>.<span class="property">template</span>.<span class="title function_">charAt</span>(<span class="number">0</span>) !== <span class="string">&#x27;#&#x27;</span>) ||</span><br><span class="line">        vm.<span class="property">$options</span>.<span class="property">el</span> || el) &#123;</span><br><span class="line">        <span class="title function_">warn</span>(</span><br><span class="line">          <span class="string">&#x27;You are using the runtime-only build of Vue where the template &#x27;</span> +</span><br><span class="line">          <span class="string">&#x27;compiler is not available. Either pre-compile the templates into &#x27;</span> +</span><br><span class="line">          <span class="string">&#x27;render functions, or use the compiler-included build.&#x27;</span>,</span><br><span class="line">          vm</span><br><span class="line">        );</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">warn</span>(</span><br><span class="line">          <span class="string">&#x27;Failed to mount component: template or render function not defined.&#x27;</span>,</span><br><span class="line">          vm</span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">callHook</span>(vm, <span class="string">&#x27;beforeMount&#x27;</span>);</span><br><span class="line">  <span class="keyword">var</span> updateComponent;</span><br><span class="line">  <span class="comment">/* istanbul ignore if */</span></span><br><span class="line">  <span class="keyword">if</span> ( config.<span class="property">performance</span> &amp;&amp; mark) &#123;</span><br><span class="line">    updateComponent = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">var</span> name = vm.<span class="property">_name</span>;</span><br><span class="line">      <span class="keyword">var</span> id = vm.<span class="property">_uid</span>;</span><br><span class="line">      <span class="keyword">var</span> startTag = <span class="string">&quot;vue-perf-start:&quot;</span> + id;</span><br><span class="line">      <span class="keyword">var</span> endTag = <span class="string">&quot;vue-perf-end:&quot;</span> + id;</span><br><span class="line">      <span class="title function_">mark</span>(startTag);</span><br><span class="line">      <span class="keyword">var</span> vnode = vm.<span class="title function_">_render</span>();</span><br><span class="line">      <span class="title function_">mark</span>(endTag);</span><br><span class="line">      <span class="title function_">measure</span>((<span class="string">&quot;vue &quot;</span> + name + <span class="string">&quot; render&quot;</span>), startTag, endTag);</span><br><span class="line">      <span class="title function_">mark</span>(startTag);</span><br><span class="line">      vm.<span class="title function_">_update</span>(vnode, hydrating);</span><br><span class="line">      <span class="title function_">mark</span>(endTag);</span><br><span class="line">      <span class="title function_">measure</span>((<span class="string">&quot;vue &quot;</span> + name + <span class="string">&quot; patch&quot;</span>), startTag, endTag);</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    updateComponent = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">      vm.<span class="title function_">_update</span>(vm.<span class="title function_">_render</span>(), hydrating); <span class="comment">// _render是对VNODE的一些处理</span></span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Watcher</span>(vm, updateComponent, noop, &#123;</span><br><span class="line">    <span class="attr">before</span>: <span class="keyword">function</span> <span class="title function_">before</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (vm.<span class="property">_isMounted</span> &amp;&amp; !vm.<span class="property">_isDestroyed</span>) &#123;</span><br><span class="line">        <span class="title function_">callHook</span>(vm, <span class="string">&#x27;beforeUpdate&#x27;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="literal">true</span> <span class="comment">/* isRenderWatcher */</span>);</span><br><span class="line">  hydrating = <span class="literal">false</span>;</span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">$vnode</span> == <span class="literal">null</span>) &#123;</span><br><span class="line">    vm.<span class="property">_isMounted</span> = <span class="literal">true</span>;</span><br><span class="line">    <span class="title function_">callHook</span>(vm, <span class="string">&#x27;mounted&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> vm</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src\core\observer\scheduler.js</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">callUpdatedHooks</span> (<span class="params">queue</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> i = queue.<span class="property">length</span>;</span><br><span class="line">  <span class="keyword">while</span> (i--) &#123;</span><br><span class="line">    <span class="keyword">var</span> watcher = queue[i];</span><br><span class="line">    <span class="keyword">var</span> vm = watcher.<span class="property">vm</span>;</span><br><span class="line">    <span class="keyword">if</span> (vm.<span class="property">_watcher</span> === watcher &amp;&amp; vm.<span class="property">_isMounted</span> &amp;&amp; !vm.<span class="property">_isDestroyed</span>) &#123;</span><br><span class="line">      <span class="title function_">callHook</span>(vm, <span class="string">&#x27;updated&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>beforeMount是在渲染函数完成之后触发。之后调用vm._render()方法将render函数转化为Virtual DOM，并最终通过vm._update()方法将Virtual DOM渲染为真实的DOM节点。同时，还会创建一个Watcher实例，并将定义好的updateComponent函数传入，开启对模板中数据（状态）的监控，之后就正式挂载到DOM上，并触发mounted。beforeUpdate和updated分别在数据变化之前和更新之后触发</p><h1 id="销毁过程"><a href="#销毁过程" class="headerlink" title="销毁过程"></a>销毁过程</h1><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">$destroy</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> vm = <span class="variable language_">this</span>;</span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">_isBeingDestroyed</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">callHook</span>(vm, <span class="string">&#x27;beforeDestroy&#x27;</span>);</span><br><span class="line">  vm.<span class="property">_isBeingDestroyed</span> = <span class="literal">true</span>;</span><br><span class="line">  <span class="comment">// remove self from parent</span></span><br><span class="line">  <span class="keyword">var</span> parent = vm.<span class="property">$parent</span>;</span><br><span class="line">  <span class="keyword">if</span> (parent &amp;&amp; !parent.<span class="property">_isBeingDestroyed</span> &amp;&amp; !vm.<span class="property">$options</span>.<span class="property">abstract</span>) &#123;</span><br><span class="line">    <span class="title function_">remove</span>(parent.<span class="property">$children</span>, vm);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// teardown watchers</span></span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">_watcher</span>) &#123;</span><br><span class="line">    vm.<span class="property">_watcher</span>.<span class="title function_">teardown</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">var</span> i = vm.<span class="property">_watchers</span>.<span class="property">length</span>;</span><br><span class="line">  <span class="keyword">while</span> (i--) &#123;</span><br><span class="line">    vm.<span class="property">_watchers</span>[i].<span class="title function_">teardown</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// remove reference from data ob</span></span><br><span class="line">  <span class="comment">// frozen object may not have observer.</span></span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">_data</span>.<span class="property">__ob__</span>) &#123;</span><br><span class="line">    vm.<span class="property">_data</span>.<span class="property">__ob__</span>.<span class="property">vmCount</span>--;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// call the last hook...</span></span><br><span class="line">  vm.<span class="property">_isDestroyed</span> = <span class="literal">true</span>;</span><br><span class="line">  <span class="comment">// invoke destroy hooks on current rendered tree</span></span><br><span class="line">  vm.<span class="title function_">__patch__</span>(vm.<span class="property">_vnode</span>, <span class="literal">null</span>);</span><br><span class="line">  <span class="comment">// fire destroyed hook</span></span><br><span class="line">  <span class="title function_">callHook</span>(vm, <span class="string">&#x27;destroyed&#x27;</span>);</span><br><span class="line">  <span class="comment">// turn off all instance listeners.</span></span><br><span class="line">  vm.$off();</span><br><span class="line">  <span class="comment">// remove __vue__ reference</span></span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">$el</span>) &#123;</span><br><span class="line">    vm.<span class="property">$el</span>.<span class="property">__vue__</span> = <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// release circular reference (#6759)</span></span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">$vnode</span>) &#123;</span><br><span class="line">    vm.<span class="property">$vnode</span>.<span class="property">parent</span> = <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>组件销毁时会调用原型上的$destroy方法，首先会检测是否有销毁过程正在进行，如果没有即触发beforeDestroy。如果组件需要卸载一些监听事件可以在这个生命周期进行：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">beforeDestroy</span> () &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">$root</span>.$off(<span class="variable language_">this</span>.<span class="property">$route</span>.<span class="property">name</span> + <span class="string">&#x27;Back&#x27;</span>)</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">$root</span>.$off(<span class="variable language_">this</span>.<span class="property">$route</span>.<span class="property">name</span> + <span class="string">&#x27;Next&#x27;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接着正式开始注销：首先从父组件移除自身，接着执行vm._watcher.teardown() 将实例自身从其他数据的依赖列表中删除，teardown方法的作用是从所有依赖向的Dep列表中将自己删除。然后移除实例内数据对其他数据的依赖。接下来移除实例内响应式数据的引用、给当前实例上添加_isDestroyed属性来表示当前实例已经被销毁，同时将实例的VNode树设置为null，触发destroyed并移除所有事件监听器，销毁完毕。</p>]]>
    </content>
    <id>https://github.com/wz71014q/2020/03/30/Vue%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/</id>
    <link href="https://github.com/wz71014q/2020/03/30/Vue%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/"/>
    <published>2020-03-30T12:36:10.000Z</published>
    <summary>
      <![CDATA[<p>&emsp;&emsp;框架为什么要有生命周期？因为框架就像是组装好的电脑，每个人的电脑里软件都不一样，买来了电脑，需要让用户可以有办法自己装一些软件。也就是实际业务不同，你要借助框架去做一些事，所以需要框架给一些接口让外部业务去调用，去填充数据。从new Vue()开始，这个框架的生命周期(作为一个构造函数函数)就已经开始了，但是我们平时使用的是它在一些特定的时刻的抛出的接口，这个才是定义的生命周期。</p>
<h1 id="Vue构造函数"><a href="#Vue构造函数" class="headerlink" title="Vue构造函数"></a>Vue构造函数</h1><p>&emsp;&emsp;这里学习的源码版本是2.6.11，构造函数的定义在src\core\instance\index.js。源码中Vue的构造函数很简单，首先检测是不是作为构造函数使用，然后执行初始化 this._init(options)</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Vue</span> (<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span> &amp;&amp; !(<span class="variable language_">this</span> <span class="keyword">instanceof</span> <span class="title class_">Vue</span>) ) &#123;</span><br><span class="line">    <span class="title function_">warn</span>(<span class="string">&#x27;Vue is a constructor and should be called with the `new` keyword&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">_init</span>(options)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">initMixin</span>(<span class="title class_">Vue</span>)</span><br><span class="line"><span class="title function_">stateMixin</span>(<span class="title class_">Vue</span>)</span><br><span class="line"><span class="title function_">eventsMixin</span>(<span class="title class_">Vue</span>)</span><br><span class="line"><span class="title function_">lifecycleMixin</span>(<span class="title class_">Vue</span>)</span><br><span class="line"><span class="title function_">renderMixin</span>(<span class="title class_">Vue</span>)</span><br></pre></td></tr></table></figure>]]>
    </summary>
    <title>Vue生命周期</title>
    <updated>2026-05-15T08:53:54.861Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="Vue" scheme="https://github.com/wz71014q/categories/Vue/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <category term="Vue" scheme="https://github.com/wz71014q/tags/Vue/"/>
    <content>
      <![CDATA[<p>&emsp;&emsp;双向绑定一直是一个高频考点，今天就来实现一个简单的Vue双向绑定。设框架名称为Wue，双向绑定指令为w-model。主要方法是observer、watcher、complier。分别用来给属性绑定setter，连接observer与complier，根据模版语法添加对应的watcher并且更新模版中的值</p><h2 id="observer思路"><a href="#observer思路" class="headerlink" title="observer思路"></a>observer思路</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">start=&gt;start: 开始</span><br><span class="line">end=&gt;end: 结束</span><br><span class="line">op1=&gt;operation: 遍历Wue.data中的所有属性，为每个属性添加getter/setter</span><br><span class="line">op2=&gt;operation: 手动设置data中的属性值，触发setter</span><br><span class="line">op3=&gt;operation: setter触发watcher</span><br><span class="line"></span><br><span class="line">start-&gt;op1-&gt;op2-&gt;op3-&gt;end</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>watcher(在本文中更像是writer)相当于中间人。手动设置data属性后，setter触发对应data属性的watcher，watcher更新UI；input等加了双向绑定指令”w-model”的值更新后，触发对应绑定事件，在回调方法中触发watcher，更新data属性</p><span id="more"></span><h2 id="watcher思路"><a href="#watcher思路" class="headerlink" title="watcher思路"></a>watcher思路</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">start=&gt;start: 开始</span><br><span class="line">end=&gt;end: 结束</span><br><span class="line">op1=&gt;operation: 触发watcher，更新input的value</span><br><span class="line">op2=&gt;operation: 触发watcher，更新data的属性</span><br><span class="line">condition=&gt;condition: data属性更新</span><br><span class="line">dataIo=&gt;inputoutput: data属性更新</span><br><span class="line">inputIo=&gt;inputoutput: input属性更新</span><br><span class="line"></span><br><span class="line">start-&gt;condition</span><br><span class="line">condition(yes)-&gt;dataIo-&gt;op1-&gt;end</span><br><span class="line">condition(no)-&gt;inputIo-&gt;op2-&gt;end</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>watcher是在分析dom元素时添加的。先遍历dom节点，找到添加了w-model或使用指定的插值语法输入的属性，给它们添加watcher。这部分的实现者是complier</p><h2 id="complier思路"><a href="#complier思路" class="headerlink" title="complier思路"></a>complier思路</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">start=&gt;start: 开始</span><br><span class="line">end=&gt;end: 结束</span><br><span class="line">op1=&gt;operation: 遍历dom节点</span><br><span class="line">op2=&gt;operation: input标签添加input事件，更新数据后更改data中的属性</span><br><span class="line">op3=&gt;operation: 给双大括号语法中的属性添加watcher，将其值替换为data属性中的值</span><br><span class="line">condition=&gt;condition: 是否有w-model或双大括号语法</span><br><span class="line"></span><br><span class="line">start-&gt;op1-&gt;condition</span><br><span class="line">condition(yes)-&gt;op2-&gt;op3-&gt;end</span><br><span class="line">condition(no)-&gt;end</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">&quot;X-UA-Compatible&quot;</span> <span class="attr">content</span>=<span class="string">&quot;ie=edge&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">title</span>&gt;</span>手写 vue 双向绑定<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;root&quot;</span>&gt;</span></span><br><span class="line">    w-model-name: <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">w-model</span>=<span class="string">&quot;name&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;&#123; name &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    w-model-age: <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">w-model</span>=<span class="string">&quot;age&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;&#123; age &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    no w-model: <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">class</span>=<span class="string">&quot;submit&quot;</span> <span class="attr">type</span>=<span class="string">&quot;button&quot;</span> <span class="attr">value</span>=<span class="string">&quot;setData&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// main.js</span></span><br><span class="line"><span class="comment">// 初始化</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;./index.html&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> observer <span class="keyword">from</span> <span class="string">&#x27;./observer&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> getElement <span class="keyword">from</span> <span class="string">&#x27;./complier&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Wue</span>(<span class="params">options = &#123;&#125;</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">$el</span> = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(options.<span class="property">el</span>);</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">$data</span> = options.<span class="property">data</span>;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">_watchers</span> = &#123;&#125;; <span class="comment">// 监听池，存放data和w-model绑定的中每个数据的watcher</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">_observer</span>(<span class="variable language_">this</span>.<span class="property">$data</span>);</span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">_complier</span>(<span class="variable language_">this</span>.<span class="property">$el</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Wue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">_observer</span> = observer;</span><br><span class="line"><span class="title class_">Wue</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">_complier</span> = getElement;</span><br><span class="line"><span class="comment">// 创建新的Wue实例</span></span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title class_">Wue</span>(&#123;</span><br><span class="line">  <span class="attr">el</span>: <span class="string">&#x27;#root&#x27;</span>,</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;Alice&#x27;</span>,</span><br><span class="line">    <span class="attr">age</span>: <span class="number">18</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">window</span>.<span class="property">app</span> = app;</span><br><span class="line"><span class="comment">// 手动修改Data数据，触发UI更新</span></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;.submit&#x27;</span>).<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  app.<span class="property">$data</span>.<span class="property">name</span> = <span class="string">&#x27;Tom&#x27;</span>;</span><br><span class="line">&#125;, <span class="literal">false</span>)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// complier.js</span></span><br><span class="line"><span class="comment">// 从根节点开始，遍历所有节点，获取所有带v-model和&#123;&#123;&#125;&#125;中的属性(已在data定义或w-model这种默认指令)，为它们添加watcher。并且input更新后手动修改data</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Watcher</span> <span class="keyword">from</span> <span class="string">&#x27;./watcher&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getElement</span>(<span class="params">element</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> _this = <span class="variable language_">this</span>;</span><br><span class="line">  <span class="keyword">const</span> nodeList = element.<span class="property">children</span>; <span class="comment">// 获取每一级的根节点: HTMLCollection</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; nodeList.<span class="property">length</span>; i += <span class="number">1</span>) &#123;</span><br><span class="line">    complier.<span class="title function_">call</span>(_this, nodeList[i]);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">complier</span>(<span class="params">element</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> _this = <span class="variable language_">this</span>;</span><br><span class="line">  <span class="keyword">if</span> (element.<span class="property">children</span>.<span class="property">length</span>) &#123;</span><br><span class="line">    <span class="title function_">complier</span>(element); <span class="comment">// 递归深度遍历dom树</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (element.<span class="title function_">hasAttribute</span>(<span class="string">&#x27;w-model&#x27;</span>) &amp;&amp; element.<span class="property">tagName</span> === <span class="string">&#x27;INPUT&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> attr = element.<span class="title function_">getAttribute</span>(<span class="string">&#x27;w-model&#x27;</span>);</span><br><span class="line">    _this.<span class="property">_watchers</span>[attr].<span class="title function_">push</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">Watcher</span>(&#123;</span><br><span class="line">        <span class="attr">el</span>: element,</span><br><span class="line">        <span class="attr">val</span>: attr,</span><br><span class="line">        <span class="attr">vm</span>: _this,</span><br><span class="line">        <span class="attr">attr</span>: <span class="string">&#x27;value&#x27;</span>,</span><br><span class="line">      &#125;),</span><br><span class="line">    );</span><br><span class="line">    element.<span class="title function_">addEventListener</span>(</span><br><span class="line">      <span class="string">&#x27;input&#x27;</span>,</span><br><span class="line">      <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        _this.<span class="property">$data</span>[attr] = element.<span class="property">value</span>;</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="literal">false</span>,</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 遍历节点，获取&#123;&#123;&#125;&#125;中的值，在Wue.data中查找对应的属性，添加watcher</span></span><br><span class="line">  <span class="keyword">const</span> tagReg = <span class="regexp">/^\&#123;\&#123;\s*(.*\S)\s*\&#125;\&#125;$/</span>;</span><br><span class="line">  <span class="keyword">let</span> textNode = element.<span class="property">textContent</span>; <span class="comment">// 当前节点的文本内容</span></span><br><span class="line">  <span class="keyword">if</span> (tagReg.<span class="title function_">test</span>(textNode)) &#123;</span><br><span class="line">    textNode = textNode.<span class="title function_">replace</span>(tagReg, <span class="function">(<span class="params">matched, matchedVal</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">let</span> watcher = _this.<span class="property">_watchers</span>[matchedVal];</span><br><span class="line">      <span class="keyword">if</span> (!watcher) &#123;</span><br><span class="line">        <span class="comment">// 没有事件池 创建事件池</span></span><br><span class="line">        watcher = [];</span><br><span class="line">      &#125;</span><br><span class="line">      watcher.<span class="title function_">push</span>(</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Watcher</span>(&#123;</span><br><span class="line">          <span class="attr">el</span>: element,</span><br><span class="line">          <span class="attr">vm</span>: _this,</span><br><span class="line">          <span class="attr">val</span>: matchedVal,</span><br><span class="line">          <span class="attr">attr</span>: <span class="string">&#x27;innerHTML&#x27;</span>,</span><br><span class="line">        &#125;),</span><br><span class="line">      );</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> getElement;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>observer.js</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 为data中的每个属性添加getter、setter。数据变化后触发watcher</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">observer</span>(<span class="params">obj</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> _this = <span class="variable language_">this</span>;</span><br><span class="line">  <span class="keyword">if</span> (!obj || <span class="keyword">typeof</span> obj !== <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">keys</span>(obj).<span class="title function_">forEach</span>(<span class="function"><span class="params">key</span> =&gt;</span> &#123;</span><br><span class="line">    initWatcher.<span class="title function_">call</span>(_this, key);</span><br><span class="line">    defineObjProperty.<span class="title function_">call</span>(_this, obj, key, obj[key]);</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 初始化数据订阅池</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">initWatcher</span>(<span class="params">key</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">_watchers</span>[key] = [];</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 设置对象成员的访问器属性，监听数据变化</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">defineObjProperty</span>(<span class="params">obj, key, value</span>) &#123;</span><br><span class="line">  <span class="title function_">observer</span>(value);</span><br><span class="line">  <span class="keyword">const</span> watchersPool = <span class="variable language_">this</span>.<span class="property">_watchers</span>;</span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(obj, key, &#123;</span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="title function_">get</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> value;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="title function_">set</span>(<span class="params">newVal</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (newVal !== value) &#123;</span><br><span class="line">        value = newVal;</span><br><span class="line">        <span class="title class_">Object</span>.<span class="title function_">keys</span>(watchersPool).<span class="title function_">forEach</span>(<span class="function"><span class="params">key</span> =&gt;</span> &#123;</span><br><span class="line">          watchersPool[key].<span class="title function_">forEach</span>(<span class="function"><span class="params">item</span> =&gt;</span> &#123;</span><br><span class="line">            item.<span class="title function_">update</span>();</span><br><span class="line">          &#125;);</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> observer;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// watcher.js</span></span><br><span class="line"><span class="comment">// Watcher，个人觉得更应该叫Writer。用来将complier与observer连接起来。当input数据发生变化时，触发input事件，再触发watcher，修改data中的数据</span></span><br><span class="line"><span class="comment">// 反过来，data数据变化，触发setter，进而触发watcher，修改input的数据</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Watcher</span>(<span class="params">&#123;el, vm, val, attr&#125; = options</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">el</span> = el;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">vm</span> = vm;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">val</span> = val;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">attr</span> = attr; <span class="comment">// dom获取值，如value获取input的值 / innerHTML获取dom的值</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">update</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Watcher</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">update</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 更新input值</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">el</span>[<span class="variable language_">this</span>.<span class="property">attr</span>] = <span class="variable language_">this</span>.<span class="property">vm</span>.<span class="property">$data</span>[<span class="variable language_">this</span>.<span class="property">val</span>];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Watcher</span>;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://github.com/wz71014q/2019/08/10/Vue%E5%8F%8C%E5%90%91%E7%BB%91%E5%AE%9A/</id>
    <link href="https://github.com/wz71014q/2019/08/10/Vue%E5%8F%8C%E5%90%91%E7%BB%91%E5%AE%9A/"/>
    <published>2019-08-10T11:00:00.000Z</published>
    <summary>
      <![CDATA[<p>&emsp;&emsp;双向绑定一直是一个高频考点，今天就来实现一个简单的Vue双向绑定。设框架名称为Wue，双向绑定指令为w-model。主要方法是observer、watcher、complier。分别用来给属性绑定setter，连接observer与complier，根据模版语法添加对应的watcher并且更新模版中的值</p>
<h2 id="observer思路"><a href="#observer思路" class="headerlink" title="observer思路"></a>observer思路</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">start=&gt;start: 开始</span><br><span class="line">end=&gt;end: 结束</span><br><span class="line">op1=&gt;operation: 遍历Wue.data中的所有属性，为每个属性添加getter/setter</span><br><span class="line">op2=&gt;operation: 手动设置data中的属性值，触发setter</span><br><span class="line">op3=&gt;operation: setter触发watcher</span><br><span class="line"></span><br><span class="line">start-&gt;op1-&gt;op2-&gt;op3-&gt;end</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>watcher(在本文中更像是writer)相当于中间人。手动设置data属性后，setter触发对应data属性的watcher，watcher更新UI；input等加了双向绑定指令”w-model”的值更新后，触发对应绑定事件，在回调方法中触发watcher，更新data属性</p>]]>
    </summary>
    <title>Vue双向绑定</title>
    <updated>2026-05-15T08:53:54.861Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="面试" scheme="https://github.com/wz71014q/categories/%E9%9D%A2%E8%AF%95/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <content>
      <![CDATA[<p>&ensp;&ensp;图解经典面试题之连续赋值</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a=b=&#123;<span class="attr">n</span>: <span class="number">1</span>&#125;;</span><br><span class="line">a.<span class="property">x</span>=a=&#123;<span class="attr">n</span>: <span class="number">2</span>&#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b);</span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/jsbasic/assignment.png" alt="assignment"></p>]]>
    </content>
    <id>https://github.com/wz71014q/2019/08/06/%E9%9D%A2%E8%AF%95%E9%A2%98%E7%9B%AE%E4%B9%8Bjs%E8%BF%9E%E7%BB%AD%E8%B5%8B%E5%80%BC/</id>
    <link href="https://github.com/wz71014q/2019/08/06/%E9%9D%A2%E8%AF%95%E9%A2%98%E7%9B%AE%E4%B9%8Bjs%E8%BF%9E%E7%BB%AD%E8%B5%8B%E5%80%BC/"/>
    <published>2019-08-06T11:08:00.000Z</published>
    <summary>
      <![CDATA[<p>&ensp;&ensp;图解经典面试题之连续赋值</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="]]>
    </summary>
    <title>js面试题目之连续赋值</title>
    <updated>2026-05-15T08:53:54.868Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="CSS" scheme="https://github.com/wz71014q/categories/CSS/"/>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="http" scheme="https://github.com/wz71014q/categories/http/"/>
    <category term="个人笔记" scheme="https://github.com/wz71014q/categories/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    <category term="面试" scheme="https://github.com/wz71014q/categories/%E9%9D%A2%E8%AF%95/"/>
    <category term="网络" scheme="https://github.com/wz71014q/categories/%E7%BD%91%E7%BB%9C/"/>
    <category term="https" scheme="https://github.com/wz71014q/categories/https/"/>
    <category term="TCP" scheme="https://github.com/wz71014q/categories/TCP/"/>
    <category term="DNS" scheme="https://github.com/wz71014q/categories/DNS/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <category term="http" scheme="https://github.com/wz71014q/tags/http/"/>
    <category term="个人笔记" scheme="https://github.com/wz71014q/tags/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    <category term="面试" scheme="https://github.com/wz71014q/tags/%E9%9D%A2%E8%AF%95/"/>
    <category term="网络" scheme="https://github.com/wz71014q/tags/%E7%BD%91%E7%BB%9C/"/>
    <category term="https" scheme="https://github.com/wz71014q/tags/https/"/>
    <category term="TCP" scheme="https://github.com/wz71014q/tags/TCP/"/>
    <category term="DNS" scheme="https://github.com/wz71014q/tags/DNS/"/>
    <content>
      <![CDATA[<h2 id="解析过程"><a href="#解析过程" class="headerlink" title="解析过程"></a>解析过程</h2><p>1、DNS解析<br>2、TCP连接<br>3、发起http请求<br>4、服务器处理http请求<br>5、服务器响应http请求<br>6、浏览器渲染页面<br>7、连接结束</p><h3 id="DNS解析"><a href="#DNS解析" class="headerlink" title="DNS解析"></a>DNS解析</h3><p>&emsp;&emsp;输入网址，浏览器并不知道要去哪里找资源，需要DNS递归查询对应IP</p><ol><li>DNS递归查询首先是在本地服务器进行查询，本地服务器找不到后再向根域名服务器查询，根域名服务器找不到再向上一级顶级com域名服务器查找。直到最终找到并返回IP，然后本地域名服务器会将该ip缓存起来，供下次使用。比如输入<a href="http://www.google.com，那么查询过程为.->.com->google.com->www.google.com">www.google.com，那么查询过程为.-&gt;.com-&gt;google.com-&gt;www.google.com</a></li><li>此处优化方法：<br>  DNS缓存：浏览器、系统（host文件）、路由器、ISP DNS缓存（互联网提供商）、本地域名服务器等都有缓存<br>  DNS负载均衡：根据地理位置、服务器负载量等来选择最合适的一个服务器来进行查询</li></ol><h3 id="TCP连接：三次握手，四次挥手"><a href="#TCP连接：三次握手，四次挥手" class="headerlink" title="TCP连接：三次握手，四次挥手"></a>TCP连接：三次握手，四次挥手</h3><h4 id="TCP报文结构："><a href="#TCP报文结构：" class="headerlink" title="TCP报文结构："></a>TCP报文结构：</h4><span id="more"></span><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/TCP/structure.jpg" alt="TCP-structure"></p><p>TCP报文结构中有固定的20字节的首部，里面有源端口、目的端口。序号、确认号、校验和、数据偏移等</p><ul><li>seq（序号）: TCP连接的数据字节流中的每一个字节都有一个序号，seq表示的是本报文发送的第一个字节的序号。</li><li>ack(确认号)：表示期望收到的下一个报文段第一个字节的编号</li><li>标志位SYN（同步标识）：本bit位为1时表示发送的是一个连接请求或者连接接受的报文</li><li>标志位ACK（确认标识）：本bit位为1 表示确认号有效，表示确实收到seq为x的包，下次会回复ack &#x3D; x + 1的值</li><li>标志位FIN（完成标识）：本bit位为1时，释放一个连接。表示发送端的数据已全部发送，要求释放传输通道。</li><li>标志位RST（复位标识）：为1时表示本次TCP连接出现严重错误，需要重新连接</li><li>标志位PSH（推送标识）：推送位。</li><li>标志位URG（紧急标识）：紧急bit位，表示紧急指针有效,这意味着不必等待前段数据被响应处理完即可发送给接收端</li></ul><h4 id="三次握手过程"><a href="#三次握手过程" class="headerlink" title="三次握手过程"></a>三次握手过程</h4><p>从网上盗的图：</p><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/TCP/connect.jpg" alt="TCP-connect"></p><ol><li>白话讲解</li></ol><ul><li>第一次握手：客户端：喂，听得到吗？</li><li>第二次握手：服务器：听得到，你听得到我吗？</li><li>第三次握手：客户端：听得到，咱们开始传输吧。</li></ul><ol start="2"><li>专业讲解：</li></ol><ul><li>第一次握手：客户端主动发起请求，第一个报文首部标志位为：SYN&#x3D;1，seq&#x3D;x</li><li>第二次握手：服务端表示收到请求并响应请求：SYN&#x3D;1, ack&#x3D;x+1,seq&#x3D;y,ACK&#x3D;1</li><li>第三次握手：客户端向服务器发送确认报文：seq&#x3D;y+1, ACK&#x3D;1。此时连接已建立</li></ul><h4 id="四次挥手"><a href="#四次挥手" class="headerlink" title="四次挥手"></a>四次挥手</h4><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/TCP/break.jpg" alt="TCP-break"></p><ol><li>白话讲解（假设客户端先请求断开）：</li></ol><ul><li>第一次挥手：客户端：我说完了，咱们断开吧</li><li>第二次挥手：服务端：收到，我还没说完，￥@#<em>（%&amp;……</em></li><li>第三次挥手：服务端：我说完了，咱们断开吧</li><li>第四次挥手：客户端：收到！(此时已断开，如果长时间没收到答复，也会默认断开)</li></ul><ol start="2"><li>专业解释：</li></ol><ul><li>第一次挥手：客户端：FIN&#x3D;1，seq&#x3D;u，客户端已停止向服务端发送数据</li><li>第二次挥手：服务端：ack&#x3D;u+1,seq&#x3D;v，客户端已停止，但服务端还在发送数据</li><li>第三次挥手：服务端：内容发送完成后，在最后一段报文的首部：FIN&#x3D;1，ACK&#x3D;1，ack&#x3D;u+1，seq&#x3D;w，并且不再发送数据</li><li>第四次挥手：客户端：对服务器应答，表示确认断开,ACK&#x3D;1，seq&#x3D;u+1，ack&#x3D;w+1</li></ul><h4 id="TCP与UDP的区别"><a href="#TCP与UDP的区别" class="headerlink" title="TCP与UDP的区别"></a>TCP与UDP的区别</h4><p>UDP报文结构：</p><p>16位源端口，16位目标端口，16位UDP长度，16位UDP校验和，数据</p><p>TCP是面向连接的，传输之前必须先握手，UPD不面向连接，直接传就完事了，但是对方收没收到是没底的。因此TCP更安全可靠，UDP效率更高</p><h3 id="OSI五层模型"><a href="#OSI五层模型" class="headerlink" title="OSI五层模型"></a>OSI五层模型</h3><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/osi/osi.png" alt="osi-module"></p><p>TCP属于传输层，TCP连接建立起来后会通知上层应用，进行其他活动。根据OSI(Open System Interconnect，开放式系统互联)五层模型，它的上层是应用层，下层还有网络层、数据链路层和物理层。其中，发送方从上到下一层层包装，分别依次序加上TCP首部、IP首部、以太网首部，通过物理层传输到接收方，接收方再由下到上逐层解析，最后到达应用层。</p><h3 id="http请求"><a href="#http请求" class="headerlink" title="http请求"></a>http请求</h3><h4 id="http请求-1"><a href="#http请求-1" class="headerlink" title="http请求"></a>http请求</h4><p>TCP连接建立起来后开始发送一个http请求，http请求报文由请求行(request line)、请求头(header)、空行和请求数据4个部分组成</p><ol><li>请求行</li></ol><ul><li>请求方法：GET(http&#x2F;0.9)，POST(http&#x2F;1.0)，HEAD(http&#x2F;1.0)，PUT(http&#x2F;1.1)，DELETE(http&#x2F;1.1)，OPTIONS(http&#x2F;1.1)，TRACE(http&#x2F;1.1)，CONNECT(http&#x2F;1.1)</li><li>请求地址：URL(统一资源定位符)，组成格式：&lt;协议(http&#x2F;https)&gt;:&#x2F;&#x2F;<Host>:&lt;端口&gt;&#x2F;&lt;路径&gt;</li><li>协议版本：http0.9、http1.0</li></ul><ol start="2"><li>请求头</li></ol><p>常见http请求头：</p><ul><li>Host: 服务器IP地址或域名</li><li>User-Agent: 发送请求的客户端系统和浏览器信息</li><li>Connection: Keep-Alive 等，表示连接状态</li><li>Accept-Encoding: 通知服务端可以发送的数据压缩格式</li><li>Accept-Charset: 通知服务端可以发送的编码格式</li><li>Accept-Language: 通知服务端可以发送的语言</li><li>Cache-Control: 通过指定指令来实现缓存机制。比如private，public、no-cache</li><li>Cookie: 存储一些用户信息、sessionId等</li><li>Range: byte&#x3D;0-5 指定第一个字节和最后一个字节的位置，告诉服务器自己想提取哪些字节</li><li>Content-Security-Policy 用来开启CSP(内容安全策略)</li></ul><ol start="3"><li>请求数据</li></ol><p>可选有或者没有，比如name&#x3D;Alice</p><h4 id="http响应"><a href="#http响应" class="headerlink" title="http响应"></a>http响应</h4><p>http响应报文主要包含状态行、响应头部、空行及响应数据组成。</p><ol><li>状态行：主要由协议版本、状态码、状态码描述组成</li></ol><ul><li>协议版本：http&#x2F;1.1</li><li>状态码：见<a href="https://wz71014q.github.io/2018/08/09/http%E5%8F%91%E5%B1%95%E5%8E%86%E5%8F%B2/">http发展历史</a>;</li><li>状态码描述：当前状态的描述。比如: 200 ok</li></ul><ol start="2"><li>响应头部</li></ol><p>常见http响应头：</p><ul><li>Content-Type: text&#x2F;html;charset&#x3D;UTF-8 告诉客户端，资源文件的类型，还有字符编码，客户端据此进行解码</li><li>Content-Encoding: 告诉客户端，服务端发送的资源都是gzip编码的</li><li>Date: 服务器发送资源的GMT(格林尼治时间)时间</li><li>Server: 服务器和相对应的版本</li><li>Transfer-Encoding: chunked 通知客户端，服务器发送的资源是分块发送的</li><li>Last-Modified: 最后一次更新时间</li><li>Connection: Keep-Alive 等，表示连接状态</li><li>Access-Control-Allow-Origin: *  表示所有网站可以跨域资源共享</li><li>Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS 允许哪些方法访问</li><li>Access-Control-Allow-Credentials: true 是否允许发送Cookie</li><li>Content-Range 响应覆盖的范围和整体长度</li><li>ETag 判断服务器资源是否更改</li><li>Refresh 用于重定向</li></ul><ol start="3"><li>响应数据</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable constant_">HTTP</span>/<span class="number">1.1</span> <span class="number">200</span> <span class="variable constant_">OK</span>　　<span class="comment">// 状态行</span></span><br><span class="line"><span class="title class_">Date</span>: <span class="title class_">Sun</span>, <span class="number">17</span> <span class="title class_">Mar</span> <span class="number">2017</span> <span class="number">08</span>:<span class="number">12</span>:<span class="number">54</span> <span class="variable constant_">GMT</span>　　<span class="comment">// 响应头部</span></span><br><span class="line"><span class="title class_">Server</span>: <span class="title class_">Apache</span>/<span class="number">2.2</span><span class="number">.8</span> (<span class="title class_">Win32</span>) <span class="variable constant_">PHP</span>/<span class="number">5.2</span><span class="number">.5</span></span><br><span class="line">X-<span class="title class_">Powered</span>-<span class="title class_">By</span>: <span class="variable constant_">PHP</span>/<span class="number">5.2</span><span class="number">.5</span></span><br><span class="line"><span class="title class_">Set</span>-<span class="title class_">Cookie</span>: <span class="variable constant_">PHPSESSID</span>=c0huq7pdkmm5gg6osoe3mgjmm3; path=/</span><br><span class="line"><span class="title class_">Expires</span>: <span class="title class_">Thu</span>, <span class="number">19</span> <span class="title class_">Nov</span> <span class="number">1981</span> <span class="number">08</span>:<span class="number">52</span>:<span class="number">00</span> <span class="variable constant_">GMT</span></span><br><span class="line"><span class="title class_">Cache</span>-<span class="title class_">Control</span>: no-store, no-cache, must-revalidate, post-check=<span class="number">0</span>, pre-check=<span class="number">0</span></span><br><span class="line"><span class="title class_">Pragma</span>: no-cache</span><br><span class="line"><span class="title class_">Content</span>-<span class="title class_">Length</span>: <span class="number">4393</span></span><br><span class="line"><span class="title class_">Keep</span>-<span class="title class_">Alive</span>: timeout=<span class="number">5</span>, max=<span class="number">100</span></span><br><span class="line"><span class="title class_">Connection</span>: <span class="title class_">Keep</span>-<span class="title class_">Alive</span></span><br><span class="line"><span class="title class_">Content</span>-<span class="title class_">Type</span>: text/html; charset=utf-<span class="number">8</span></span><br><span class="line"><span class="comment">// 空行</span></span><br><span class="line"><span class="comment">// 响应数据</span></span><br><span class="line">&lt;html&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">title</span>&gt;</span>HTTP响应示例<span class="tag">&lt;<span class="name">title</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">Hello HTTP!</span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span></span><br></pre></td></tr></table></figure><ol start="4"><li>http缓存优化方案</li></ol><ul><li><p>在一次请求中，服务器会返回客户端Last-Modified时间，那么下次请求中，客户端就可以在请求头中添加If-Modified-Since+时间，如果没变，服务端会回状态码304，那么客户端就可以继续使用本地缓存展示内容。如果改变了，就返回200，并返回新的资源。</p></li><li><p>或者使用ETag（资源标识符）。在一次请求中，服务端会返回ETag，它是一串根据内容生成的字符串。下次请求时，客户端会加上请求头：<br>If-None-Match：33a64df551425fcc55e4d42a148795d9f25f89d4，服务端将该值与资源的ETag进行比较，如果相同，返回304，如果不同，就返回200和新的资源。</p></li></ul><h3 id="https请求"><a href="#https请求" class="headerlink" title="https请求"></a>https请求</h3><p>https与http的区别在于，应用层中，应用数据先经过SSL&#x2F;TLS进行加密，再经过TCP连接</p><h4 id="加密原理"><a href="#加密原理" class="headerlink" title="加密原理"></a>加密原理</h4><p>&emsp;&emsp;<a href="https://developers.weixin.qq.com/community/develop/article/doc/000046a5fdc7802a15f7508b556413">安利这篇文章</a></p><p>&emsp;&emsp;客户端在向服务端发送请求时，服务端会返回自己的公钥（保险箱），客户端将数据根据服务端的公钥加密（把数据放在保险箱里），再返回给服务端，服务端用之前给客户端公钥相匹配的私钥（保险箱钥匙）解密，即得到传输的数据。<br>&emsp;&emsp;为了保证服务端返回的公钥(保险箱)没有被中间黑客替换，需要证明该公钥（保险箱）确实是服务端给的。这时候数字证书(身份证)登场了。数字证书(身份证)是政府机构或者一些受信任的组织（证明人）为网站做的证明。它是CA将私钥发放给受信任的机构，公钥会发给各客户端。服务端在给客户端发送公钥时就可以用该证书加密公钥，发送给客户端，客户端用CA的公钥解密并验证完整性，通过后再传输数据。所以，流程变化如下：</p><ol><li>客户端发送请求</li><li>服务端返回自己的用数字证书加密过的公钥</li><li>客户端解析证书，包括正确性和完整性</li><li>传送加密信息</li><li>服务端解密信息，并使用客户端传给服务端的私钥加密信息</li><li>服务端向客户端传输加密后的信息</li><li>客户端解密</li></ol><h4 id="SSL-TLS握手过程"><a href="#SSL-TLS握手过程" class="headerlink" title="SSL&#x2F;TLS握手过程"></a>SSL&#x2F;TLS握手过程</h4><ol><li>客户端向服务端发送Client Hello信息，里面包含一个客户端生成的随机数Random1，支持的加密套件、SSL版本</li><li>服务端返回Server Hello消息。服务端根据刚才客户端上报的支持的加密套件，确定一种加密方式，在这条信息中一块传给客户端。同时生成另一个随机数一块传给客户端</li><li>Certificate: 服务端将自己的证书下发给客户端，客户端用对应公钥解密，取出其中的公钥</li><li>Certificate Request(可选): 服务端要求客户端上报证书。</li><li>Sever Hello Done: 服务端通知客户端 Server Hello 结束</li><li>Certificate Verify: 客户端收到证书后，先从CA验证证书是否合法，合法后取出公钥。再生成一个随机数Random3，用公钥非对称加密(RSA等)Random3，生成PreMaster Key。</li><li>Client Key Exchange：客户端根据服务器传来的公钥生成了 PreMaster Key后，将这个 key 传给服务端，服务端再用自己的私钥解出这个 PreMaster Key 得到客户端生成的 Random3。至此，客户端和服务端都拥有 Random1 + Random2 + Random3，两边再根据同样的算法就可以生成一份秘钥，握手结束后的应用层数据都是使用这个秘钥进行对称加密。</li></ol><h3 id="页面渲染"><a href="#页面渲染" class="headerlink" title="页面渲染"></a>页面渲染</h3><p>浏览器拿到HTML文档后，开始解析并渲染页面</p><h4 id="渲染过程"><a href="#渲染过程" class="headerlink" title="渲染过程"></a>渲染过程</h4><ol><li>解析HTML生成DOM(文档对象模型)树</li><li>解析CSS生成CSSOM(CSS对象模型)树</li><li>将DOM树与CSSOM树结合为渲染树</li><li>根据渲染树设计布局(layout)，计算节点大小、布局</li><li>绘制节点</li></ol><h4 id="JS解析"><a href="#JS解析" class="headerlink" title="JS解析"></a>JS解析</h4><p>浏览器的内核是多线程的，一个浏览器至少会有三个线程：javascript处理线程、GUI渲染线程、事件触发线程</p><ul><li>JavaScript是事件驱动型的单线程语言，js处理引擎也是单线程，js一直等待着任务队列中任务的到来，然后加以处理</li><li>GUI渲染线程负责浏览器的渲染工作，浏览器发生回流或重绘时该线程就会触发。GUI线程与JavaScript线程是互斥的。只要JavaScript线程执行，GUI渲染线程就会挂起。一般是等待JavaScript线程执行结束，再从等待队列中取出GUI渲染线程执行。</li><li>事件触发线程。当一个事件被触发时，该线程会把该事件添加进事件触发队列的队尾，等待js引擎处理。</li><li>JS文件默认是阻塞进程的，所以为了用户体验，最佳实践是link标签放head标签而script放body底部。或者加上defer，延迟加载；加上async，异步加载。</li></ul><h4 id="注意点"><a href="#注意点" class="headerlink" title="注意点"></a>注意点</h4><p><a href="https://wz71014q.github.io/2019/03/23/CSS-%E5%9B%9E%E6%B5%81%E4%B8%8E%E9%87%8D%E7%BB%98/">回流与重绘</a></p><h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><p><a href="https://www.jianshu.com/p/668970142765">https://www.jianshu.com/p/668970142765</a><br><a href="https://segmentfault.com/a/1190000013522717">https://segmentfault.com/a/1190000013522717</a><br><a href="https://juejin.im/post/5c17d3cd5188250d9e604628">https://juejin.im/post/5c17d3cd5188250d9e604628</a><br><a href="https://my.oschina.net/elitetao/blog/781227">https://my.oschina.net/elitetao/blog/781227</a><br><a href="https://developers.weixin.qq.com/community/develop/article/doc/000046a5fdc7802a15f7508b556413">https://developers.weixin.qq.com/community/develop/article/doc/000046a5fdc7802a15f7508b556413</a><br><a href="https://www.jianshu.com/p/7158568e4867">https://www.jianshu.com/p/7158568e4867</a></p>]]>
    </content>
    <id>https://github.com/wz71014q/2019/08/06/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%BB%8E%E8%BE%93%E5%85%A5url%E5%88%B0%E9%A1%B5%E9%9D%A2%E5%B1%95%E7%A4%BA%E5%8F%91%E7%94%9F%E4%BA%86%E4%BB%80%E4%B9%88/</id>
    <link href="https://github.com/wz71014q/2019/08/06/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%BB%8E%E8%BE%93%E5%85%A5url%E5%88%B0%E9%A1%B5%E9%9D%A2%E5%B1%95%E7%A4%BA%E5%8F%91%E7%94%9F%E4%BA%86%E4%BB%80%E4%B9%88/"/>
    <published>2019-08-06T11:08:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="解析过程"><a href="#解析过程" class="headerlink" title="解析过程"></a>解析过程</h2><p>1、DNS解析<br>2、TCP连接<br>3、发起http请求<br>4、服务器处理http请求<br>5、服务器响应http请求<br>6、浏览器渲染页面<br>7、连接结束</p>
<h3 id="DNS解析"><a href="#DNS解析" class="headerlink" title="DNS解析"></a>DNS解析</h3><p>&emsp;&emsp;输入网址，浏览器并不知道要去哪里找资源，需要DNS递归查询对应IP</p>
<ol>
<li>DNS递归查询首先是在本地服务器进行查询，本地服务器找不到后再向根域名服务器查询，根域名服务器找不到再向上一级顶级com域名服务器查找。直到最终找到并返回IP，然后本地域名服务器会将该ip缓存起来，供下次使用。比如输入<a href="http://www.google.com，那么查询过程为.->.com->google.com->www.google.com">www.google.com，那么查询过程为.-&gt;.com-&gt;google.com-&gt;www.google.com</a></li>
<li>此处优化方法：<br>  DNS缓存：浏览器、系统（host文件）、路由器、ISP DNS缓存（互联网提供商）、本地域名服务器等都有缓存<br>  DNS负载均衡：根据地理位置、服务器负载量等来选择最合适的一个服务器来进行查询</li>
</ol>
<h3 id="TCP连接：三次握手，四次挥手"><a href="#TCP连接：三次握手，四次挥手" class="headerlink" title="TCP连接：三次握手，四次挥手"></a>TCP连接：三次握手，四次挥手</h3><h4 id="TCP报文结构："><a href="#TCP报文结构：" class="headerlink" title="TCP报文结构："></a>TCP报文结构：</h4>]]>
    </summary>
    <title>面试题-从输入url到页面展示发生了什么</title>
    <updated>2026-05-15T08:53:54.868Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="HTML" scheme="https://github.com/wz71014q/categories/HTML/"/>
    <category term="兼容" scheme="https://github.com/wz71014q/categories/%E5%85%BC%E5%AE%B9/"/>
    <category term="CSS" scheme="https://github.com/wz71014q/tags/CSS/"/>
    <content>
      <![CDATA[<p>&emsp;&emsp;前端内容繁复，平时难免会碰到一些奇奇怪怪或者暂时无法理解的问题，所以特开此文将这些问题记录下来。</p><h2 id="屏幕适配问题"><a href="#屏幕适配问题" class="headerlink" title="屏幕适配问题"></a>屏幕适配问题</h2><p>&emsp;&emsp;问题描述：移动端的屏幕适配问题，至今没有想明白是什么原因</p><p>&emsp;&emsp;出现场景：移动端App webview内。</p><p>&emsp;&emsp;复现步骤：刚开始手机字体大小为正常，打开页面后将App切后台，调整手机字体大小到最大，即出现这种情况。理论上webview内部不应该受到系统字体的影响，在原生中将该功能禁用即可。但是奇怪的是根元素的字体大小确实设置为18px，最终计算出来的字体大小却是27px。</p><p>html中已经禁止了页面缩放，但是并没有效果。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/problems/scale.png" alt="scale"></p><p>&emsp;&emsp;解决方案：计算最后放大了多少倍，同比缩小：</p><span id="more"></span><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> resizeEvt = <span class="string">&#x27;orientationchange&#x27;</span> <span class="keyword">in</span> <span class="variable language_">window</span> ? <span class="string">&#x27;orientationchange&#x27;</span> : <span class="string">&#x27;resize&#x27;</span>;</span><br><span class="line">  <span class="keyword">var</span> resizeTimeout;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">resizeThrottler</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!resizeTimeout) &#123;</span><br><span class="line">      resizeTimeout = <span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        resizeTimeout = <span class="literal">null</span>;</span><br><span class="line">        <span class="title function_">actualResizeHandler</span>();</span><br><span class="line">      &#125;, <span class="number">66</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">actualResizeHandler</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> fontsize = <span class="regexp">/^\d+/</span>.<span class="title function_">exec</span>(<span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">style</span>.<span class="property">fontSize</span>);</span><br><span class="line">    <span class="keyword">var</span> cmtFont =  <span class="regexp">/^\d+/</span>.<span class="title function_">exec</span>(<span class="variable language_">window</span>.<span class="title function_">getComputedStyle</span>(<span class="variable language_">document</span>.<span class="title function_">getElementsByTagName</span>(<span class="string">&quot;html&quot;</span>)[<span class="number">0</span>]).<span class="property">fontSize</span>);</span><br><span class="line">    <span class="keyword">var</span> radio = <span class="title class_">Number</span>(fontsize) / <span class="title class_">Number</span>(cmtFont);</span><br><span class="line">    <span class="keyword">if</span> (radio !== <span class="number">1</span>) &#123;</span><br><span class="line">      <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">style</span>.<span class="property">fontSize</span> = <span class="number">18</span> * radio + <span class="string">&#x27;px&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="variable language_">document</span>.<span class="property">addEventListener</span>) <span class="keyword">return</span>;</span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(resizeEvt, resizeThrottler, <span class="literal">false</span>);</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;DOMContentLoaded&#x27;</span>, resizeThrottler, <span class="literal">false</span>);</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>一次偶然的发现，找出了上述问题的原因：DPR。由于系统字体变大，导致DPR变大，进而最终计算属性与设置属性不同。</p>]]>
    </content>
    <id>https://github.com/wz71014q/2019/06/21/%E7%96%91%E9%9A%BE%E6%9D%82%E7%97%87%E8%AE%B0%E5%BD%95/</id>
    <link href="https://github.com/wz71014q/2019/06/21/%E7%96%91%E9%9A%BE%E6%9D%82%E7%97%87%E8%AE%B0%E5%BD%95/"/>
    <published>2019-06-21T12:00:00.000Z</published>
    <summary>
      <![CDATA[<p>&emsp;&emsp;前端内容繁复，平时难免会碰到一些奇奇怪怪或者暂时无法理解的问题，所以特开此文将这些问题记录下来。</p>
<h2 id="屏幕适配问题"><a href="#屏幕适配问题" class="headerlink" title="屏幕适配问题"></a>屏幕适配问题</h2><p>&emsp;&emsp;问题描述：移动端的屏幕适配问题，至今没有想明白是什么原因</p>
<p>&emsp;&emsp;出现场景：移动端App webview内。</p>
<p>&emsp;&emsp;复现步骤：刚开始手机字体大小为正常，打开页面后将App切后台，调整手机字体大小到最大，即出现这种情况。理论上webview内部不应该受到系统字体的影响，在原生中将该功能禁用即可。但是奇怪的是根元素的字体大小确实设置为18px，最终计算出来的字体大小却是27px。</p>
<p>html中已经禁止了页面缩放，但是并没有效果。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure>

<p><img src="https://raw.githubusercontent.com/wz71014q/img/master/problems/scale.png" alt="scale"></p>
<p>&emsp;&emsp;解决方案：计算最后放大了多少倍，同比缩小：</p>]]>
    </summary>
    <title>疑难杂症记录</title>
    <updated>2026-05-15T08:53:54.866Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="Vue" scheme="https://github.com/wz71014q/categories/Vue/"/>
    <category term="jest" scheme="https://github.com/wz71014q/categories/jest/"/>
    <category term="单元测试" scheme="https://github.com/wz71014q/categories/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <category term="Vue" scheme="https://github.com/wz71014q/tags/Vue/"/>
    <category term="jest" scheme="https://github.com/wz71014q/tags/jest/"/>
    <category term="单元测试" scheme="https://github.com/wz71014q/tags/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/"/>
    <content>
      <![CDATA[<p>&emsp;&emsp;为了提高代码设计水平，测试是必不可少的。<a href="https://jestjs.io/zh-Hans/">jest</a>是facebook出的一个测试框架，里面自带断言库，而且VUE有个<a href="https://vue-test-utils.vuejs.org/zh/">Vue Test Utils</a>提供了官方支持，因此这里使用jest构建VUE单元测试部分。开始前建议先浏览一下官方网站，进行初步了解。</p><h2 id="起步"><a href="#起步" class="headerlink" title="起步"></a>起步</h2><h3 id="安装-简易demo"><a href="#安装-简易demo" class="headerlink" title="安装&amp;简易demo"></a>安装&amp;简易demo</h3><p>新建一个文件夹，然后执行：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm init</span><br><span class="line">npm install jest -g</span><br></pre></td></tr></table></figure><p>这就安装好了，接下来写一个简单的函数：</p><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/jest/jestSimpleDemo.png" alt="jestSimpleDemo"></p><p>然后在你的项目文件夹下执行jest, 就会自动搜索所有.test.js和.spec.js文件进行测试。</p><h3 id="增加配置"><a href="#增加配置" class="headerlink" title="增加配置"></a>增加配置</h3><p>接着我们改一下目录，增加一个SRC文件夹，里面放着我们的原文件，然后新建test文件夹，将测试文件全部放进去<br><img src="https://raw.githubusercontent.com/wz71014q/img/master/jest/jestES6Demo.png" alt="jestES6Demo"><br>在demo.test.js中添加代码：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// demo.test.js</span></span><br><span class="line"><span class="keyword">import</span> checkNumber <span class="keyword">from</span> <span class="string">&#x27;../src/demo/demo&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;decribe用来生成一个组&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">test</span>(<span class="string">&#x27;checkNumber&#x27;</span>, <span class="function">()=&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">expect</span>(<span class="title function_">checkNumber</span>(<span class="number">2</span>, <span class="number">3</span>)).<span class="title function_">toBe</span>(<span class="number">5</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><span id="more"></span><p>这时直接运行会报错，因为默认不支持ES6的部分语法，所以需要安装babel（<font color=#FF0000>注意babel的版本</font>）进行转译：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install @babel/core @babel/preset-env babel-jest -D</span><br></pre></td></tr></table></figure><p>在根目录新增babel.config.js</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// babel.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">presets</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">&#x27;@babel/preset-env&#x27;</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">targets</span>: &#123;</span><br><span class="line">          <span class="attr">node</span>: <span class="string">&#x27;current&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  ],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>并且在package.json中配置好入口文件</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// package.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;jestdemo&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;main&quot;</span><span class="punctuation">:</span> <span class="string">&quot;index.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;jest&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;license&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ISC&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;@babel/core&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.4.5&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@babel/preset-env&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.4.5&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;babel-jest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^24.8.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;jest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^24.8.0&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>jest.config.js是jest配置文件，后面一些配置就在这里添加。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// jest.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>jest运行时会自动寻找默认的jest.config.js配置文件。如果要自定义目录，需要在package.json中指定路径： </p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">   <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;jest --config ./test/jest.config.js&quot;</span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>执行:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run test</span><br></pre></td></tr></table></figure><p>得到以下结果，说明执行成功！<br><img src="https://raw.githubusercontent.com/wz71014q/img/master/jest/jestDemoSuccess.png" alt="jestDemoSuccess"></p><h2 id="测试Vue组件"><a href="#测试Vue组件" class="headerlink" title="测试Vue组件"></a>测试Vue组件</h2><p>&emsp;&emsp;Vue官方给我们提供了<a href="https://vue-test-utils.vuejs.org/zh/">Vue Test Utils</a>单元测试工具，Vue Test Utils 通过将组件隔离挂载，然后模拟必要的输入 (prop、注入和用户事件) 和对输出 (渲染结果、触发的自定义事件) 的断言来测试 Vue 组件。被挂载的组件会返回到一个包裹器内，而包裹器会暴露很多封装、遍历和查询其内部的 Vue 组件实例的便捷的方法。</p><h3 id="简易Demo"><a href="#简易Demo" class="headerlink" title="简易Demo"></a>简易Demo</h3><p>以一个简单的组件为例</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// hello.vue</span></span><br><span class="line">&lt;template&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    hello &#123;&#123; msg &#125;&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">p</span> <span class="attr">class</span>=<span class="string">&quot;text&quot;</span>&gt;</span>I&#x27;m jest demo<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">&quot;count&quot;</span> @<span class="attr">click</span>=<span class="string">&quot;increment&quot;</span>&gt;</span>Increment &#123;&#123; count &#125;&#125;<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">&lt;/template&gt;</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="attr">name</span>: <span class="string">&#x27;Hello&#x27;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="title function_">data</span>(<span class="params"></span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="keyword">return</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="attr">msg</span>: <span class="string">&#x27;picker&#x27;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="attr">count</span>: <span class="number">0</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="attr">methods</span>: &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="title function_">increment</span>(<span class="params"></span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="variable language_">this</span>.<span class="property">count</span>++;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br></pre></td></tr></table></figure><!-- 这种只有组件内部数据交互，没有引用外部图片的资源的文件，只要添加对.vue文件的支持就可以了 --><p>添加依赖</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install vue-jest @vue/test-utils jest-serializer-vue -D</span><br></pre></td></tr></table></figure><p>参考vue-cli2，修改<a href="https://jestjs.io/docs/zh-Hans/configuration">jest配置</a></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// jest.config.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">rootDir</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;./&#x27;</span>),</span><br><span class="line">  <span class="attr">moduleFileExtensions</span>: [<span class="string">&#x27;js&#x27;</span>, <span class="string">&#x27;json&#x27;</span>, <span class="string">&#x27;jsx&#x27;</span>, <span class="string">&#x27;ts&#x27;</span>, <span class="string">&#x27;tsx&#x27;</span>, <span class="string">&#x27;node&#x27;</span>, <span class="string">&#x27;vue&#x27;</span>], <span class="comment">// 处理这些文件</span></span><br><span class="line">  <span class="attr">moduleDirectories</span>: [<span class="string">&#x27;node_modules&#x27;</span>, <span class="string">&#x27;assets&#x27;</span>], <span class="comment">// 从这些目录去查找资源</span></span><br><span class="line">  <span class="attr">moduleNameMapper</span>: &#123; <span class="comment">// 进行转译, identity-obj-proxy用来模拟输入</span></span><br><span class="line">    <span class="string">&#x27;^@/(.*)$&#x27;</span>: <span class="string">&#x27;&lt;rootDir&gt;/assets/&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$&#x27;</span>: <span class="string">&#x27;identity-obj-proxy&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;\\.(css|less|scss)$&#x27;</span>: <span class="string">&#x27;identity-obj-proxy&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">transform</span>: &#123; <span class="comment">// 可以理解成loader</span></span><br><span class="line">    <span class="string">&#x27;^.+\\.js$&#x27;</span>: <span class="string">&#x27;babel-jest&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;.*\\.(vue)$&#x27;</span>: <span class="string">&#x27;vue-jest&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">transformIgnorePatterns</span>: [<span class="string">&#x27;&lt;rootDir&gt;/node_modules/&#x27;</span>],</span><br><span class="line">  <span class="attr">testPathIgnorePatterns</span>: [ <span class="comment">// 忽略测试文件</span></span><br><span class="line">    <span class="string">&#x27;&lt;rootDir&gt;/projects/&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;&lt;rootDir&gt;/test/sub.test.js&#x27;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">testRegex</span>: <span class="string">&#x27;hello.test.js&#x27;</span>, <span class="comment">// 测试指定格式文件，默认全部.test.js(x)和.spec.js(x)文件</span></span><br><span class="line">  <span class="attr">snapshotSerializers</span>: [<span class="string">&#x27;&lt;rootDir&gt;/node_modules/jest-serializer-vue&#x27;</span>], <span class="comment">// 快照的序列化工具</span></span><br><span class="line">  <span class="attr">collectCoverage</span>: <span class="literal">true</span>, <span class="comment">// 测试覆盖率</span></span><br><span class="line">  <span class="attr">coverageDirectory</span>: <span class="string">&#x27;&lt;rootDir&gt;/test/coverage&#x27;</span>,</span><br><span class="line">  <span class="attr">collectCoverageFrom</span>: [ <span class="comment">// 测试覆盖率</span></span><br><span class="line">    <span class="string">&#x27;&lt;rootDir&gt;/testDemo/**/*.&#123;js,vue&#125;&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;!**/node_modules/**&#x27;</span></span><br><span class="line">  ]</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>写测试文件:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// hello.test.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Vue</span> <span class="keyword">from</span> <span class="string">&#x27;vue&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; mount &#125; <span class="keyword">from</span> <span class="string">&#x27;@vue/test-utils&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; renderToString &#125; <span class="keyword">from</span> <span class="string">&#x27;@vue/server-test-utils&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> hello <span class="keyword">from</span> <span class="string">&#x27;../testDemo/hello.vue&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> wrapper;</span><br><span class="line"><span class="keyword">let</span> vm;</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;vue组件测试&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">beforeEach</span>(<span class="function">() =&gt;</span> &#123; <span class="comment">// 每次测试前确保我们的测试实例都是是干净完整的。返回一个wrapper对象</span></span><br><span class="line">    wrapper = <span class="title function_">mount</span>(hello);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">afterEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    vm &amp;&amp; vm.$destroy();</span><br><span class="line">    wrapper &amp;&amp; wrapper.<span class="title function_">destroy</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;Dom&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">contains</span>(<span class="string">&#x27;div&#x27;</span>)).<span class="title function_">toBe</span>(<span class="literal">true</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;Content&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">find</span>(<span class="string">&#x27;.text&#x27;</span>).<span class="title function_">text</span>())</span><br><span class="line">      .<span class="title function_">toEqual</span>(<span class="string">&#x27;I\&#x27;m jest demo&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;Trigger&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> button = wrapper.<span class="title function_">find</span>(<span class="string">&#x27;.count&#x27;</span>);</span><br><span class="line">    button.<span class="title function_">trigger</span>(<span class="string">&#x27;click&#x27;</span>);</span><br><span class="line">    <span class="title function_">expect</span>(button.<span class="title function_">text</span>())</span><br><span class="line">      .<span class="title function_">toEqual</span>(<span class="string">&#x27;Increment 1&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;renderToString render component as a html&#x27;</span>, <span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> str = <span class="keyword">await</span> <span class="title function_">renderToString</span>(hello);</span><br><span class="line">    <span class="title function_">expect</span>(str).<span class="title function_">toContain</span>(<span class="string">&#x27;&lt;p class=&quot;text&quot;&gt;I\&#x27;m jest demo&lt;/p&gt;&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>其中的API根据<a href="https://vue-test-utils.vuejs.org/zh/api/wrapper/#setvalue">官方文档</a>自行调整，配置完毕后再次运行，出现以下结果即说明测试通过：</p><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/jest/helloVueTest.png" alt="helloVueTest"></p><p>图中的表格就是我们配置的测试覆盖率。其中的几个参数意思是：</p><blockquote><p>%stmts是语句覆盖率（statement coverage）：是否每个语句都执行了<br>%Branch是分支覆盖率（branch coverage）：是否每个if代码块都执行了<br>%Funcs是函数覆盖率（function coverage）：是否每个函数都调用了<br>%Lines行覆盖率（line coverage）：是否每一行都执行了</p></blockquote><p>从图中可以看出hello.vue文件的执行率全部为100%，并且全部通过测试。说明组件逻辑计算没有问题，实际使用怎样不能确定，但是肯定没有人为的失误问题。组件测试不一定非要追求100%，有些组件其实只要测试输入输出可以达到要求即可。</p><p>前面jest.conf.js中设置了覆盖率测试结果的输出路径(coverageDirectory)为test文件夹下的coverage，测试完成后就会自动生成：<br><img src="https://raw.githubusercontent.com/wz71014q/img/master/jest/coverage.png" alt="helloVueTest"></p><h3 id="组件挂载方式"><a href="#组件挂载方式" class="headerlink" title="组件挂载方式"></a>组件挂载方式</h3><p>&emsp;&emsp;我们要测试VUE组件，你得找个地方挂载它，这样才可以调用VUE实例的一些属性与方法。可以使用Vue Test Utils官方API创建一个包裹器wrapper，将组件挂载上去，也可以收到创建VNode，只渲染，不挂载：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Vue Test Utils API</span></span><br><span class="line"><span class="keyword">import</span> &#123; mount &#125; <span class="keyword">from</span> <span class="string">&#x27;@vue/test-utils&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> hello <span class="keyword">from</span> <span class="string">&#x27;./hello.vue&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">beforeEach</span>(<span class="function">() =&gt;</span> &#123; <span class="comment">// 每次测试前确保我们的测试实例都是是干净完整的。返回一个wrapper对象</span></span><br><span class="line">  wrapper = <span class="title function_">mount</span>(hello);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;hello&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;renders a div&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">contains</span>(<span class="string">&#x27;div&#x27;</span>)).<span class="title function_">toBe</span>(<span class="literal">true</span>);</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">//使用 Vue.$mount() 手动挂载</span></span><br><span class="line"><span class="keyword">import</span> hello <span class="keyword">from</span> <span class="string">&#x27;./hello.vue&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Constructor</span> = <span class="title class_">Vue</span>.<span class="title function_">extend</span>(hello);</span><br><span class="line"><span class="comment">// 渲染但不往Dom挂载，即可调用Vue实例的属性和方法</span></span><br><span class="line"><span class="keyword">const</span> vm = <span class="keyword">new</span> <span class="title class_">Constructor</span>().$mount();</span><br></pre></td></tr></table></figure><ul><li>简单组件可以用Vue Test Utils的API，挂载时顺便将slot、propsData设置好，检测输出的结果是否符合预期。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 举例</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Vue</span> <span class="keyword">from</span> <span class="string">&#x27;vue&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> sinon <span class="keyword">from</span> <span class="string">&#x27;sinon&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; mount, shallowMount &#125; <span class="keyword">from</span> <span class="string">&#x27;@vue/test-utils&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> index <span class="keyword">from</span> <span class="string">&#x27;../index.vue&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> wrapper;</span><br><span class="line"><span class="keyword">let</span> vm;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> leftSlot = <span class="string">&#x27;&lt;div class=&quot;leftSlot&quot;&gt;left icon&lt;/div&gt;&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> left = <span class="string">&#x27;&lt;p class=&quot;leftText&quot;&gt;left text&lt;/p&gt;&#x27;</span></span><br><span class="line"><span class="keyword">const</span> right = <span class="string">&#x27;&lt;p class=&quot;rightText&quot;&gt;right text&lt;/p&gt;&#x27;</span></span><br><span class="line"><span class="keyword">const</span> title = <span class="string">&#x27;&lt;p class=&quot;title&quot;&gt;header title&lt;/p&gt;&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;check index&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">beforeEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    wrapper = <span class="title function_">mount</span>(index, &#123;</span><br><span class="line">      <span class="attr">propsData</span>: &#123;</span><br><span class="line">        <span class="attr">theme</span>: <span class="string">&#x27;transparent&#x27;</span>,</span><br><span class="line">        <span class="attr">leftOptions</span>: &#123;</span><br><span class="line">          <span class="attr">showBack</span>: <span class="literal">true</span>,</span><br><span class="line">          <span class="attr">backText</span>: <span class="string">&#x27;goBack&#x27;</span>,</span><br><span class="line">          <span class="attr">preventGoBack</span>: <span class="literal">false</span>,</span><br><span class="line">          <span class="attr">showMore</span>: <span class="literal">true</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;header组件&#x27;</span>,</span><br><span class="line">        <span class="attr">transition</span>: <span class="title class_">String</span>,</span><br><span class="line">        <span class="attr">rightOptions</span>: &#123;</span><br><span class="line">          <span class="attr">showMore</span>: <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">afterEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    vm &amp;&amp; vm.$destroy();</span><br><span class="line">    wrapper &amp;&amp; wrapper.<span class="title function_">destroy</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;DOM&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">find</span>(<span class="string">&#x27;.title&#x27;</span>).<span class="title function_">text</span>()).<span class="title function_">toBe</span>(<span class="string">&#x27;header组件&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li>复杂组件可以创建一个Vue的实例对象，在这个对象里引用你的组件，调用相关的API测试结果。下面这个例子是element-ui组件单元测试创建VUE实例的方法。使用时直接将引入的组件挂在创建的实例上即可。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> createElm = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> elm = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;div&#x27;</span>);</span><br><span class="line">  <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(elm);</span><br><span class="line">  <span class="keyword">return</span> elm;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 创建一个 Vue 的实例对象</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>  &#123;<span class="type">Object|String</span>&#125;  Compo   组件配置，可直接传 template</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>  &#123;<span class="type">Boolean=false</span>&#125; mounted 是否添加到 DOM 上</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> &#123;<span class="type">Object</span>&#125; <span class="variable">vm</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> createVue = <span class="keyword">function</span>(<span class="params">Compo, mounted = <span class="literal">false</span></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="title class_">Compo</span>) === <span class="string">&#x27;[object String]&#x27;</span>) &#123;</span><br><span class="line">    <span class="title class_">Compo</span> = &#123; <span class="attr">template</span>: <span class="title class_">Compo</span> &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Vue</span>(<span class="title class_">Compo</span>).$mount(mounted === <span class="literal">false</span> ? <span class="literal">null</span> : <span class="title function_">createElm</span>());</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 组件挂载</span></span><br><span class="line"><span class="keyword">import</span> child <span class="keyword">from</span> <span class="string">&#x27;../child.vue&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;create&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> vm = <span class="title function_">createVue</span>(&#123;</span><br><span class="line">    <span class="attr">template</span>: <span class="string">`</span></span><br><span class="line"><span class="string">      &lt;div&gt;</span></span><br><span class="line"><span class="string">        &lt;button class=&quot;btn&quot;&gt;a button&lt;/button&gt;</span></span><br><span class="line"><span class="string">        &lt;child</span></span><br><span class="line"><span class="string">          ref=&quot;autocomplete&quot;</span></span><br><span class="line"><span class="string">          v-model=&quot;state&quot;</span></span><br><span class="line"><span class="string">          :options=&quot;options&quot;</span></span><br><span class="line"><span class="string">          @handle=&quot;handle&quot;</span></span><br><span class="line"><span class="string">        &gt;</span></span><br><span class="line"><span class="string">          hello world</span></span><br><span class="line"><span class="string">        &lt;/child&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">    `</span>,</span><br><span class="line">    <span class="title function_">data</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">restaurants</span>: [],</span><br><span class="line">        <span class="attr">state</span>: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">        <span class="attr">options</span>: &#123;</span><br><span class="line">          <span class="attr">show</span>: <span class="literal">false</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">methods</span>: &#123;</span><br><span class="line">      <span class="title function_">handle</span>(<span class="params">res</span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;handle&#x27;</span> + res);</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="title function_">mounted</span>(<span class="params"></span>) &#123;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> elm = vm.<span class="property">$el</span>;</span><br><span class="line">  <span class="keyword">let</span> btnElm = elm.<span class="title function_">querySelector</span>(<span class="string">&#x27;btn&#x27;</span>);</span><br><span class="line">  <span class="title function_">expect</span>(btnElm.<span class="title function_">text</span>()).<span class="title function_">toBe</span>(<span class="string">&#x27;a button&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="复杂Vue组件"><a href="#复杂Vue组件" class="headerlink" title="复杂Vue组件"></a>复杂Vue组件</h3><p>&emsp;&emsp;这里的复杂表示组件内包含Vuex、VueRouter等常用配套插件，不包括组件的业务逻辑</p><h4 id="配合-Vue-Router-使用"><a href="#配合-Vue-Router-使用" class="headerlink" title="配合 Vue Router 使用"></a><a href="https://vue-test-utils.vuejs.org/zh/guides/using-with-vue-router.html">配合 Vue Router 使用</a></h4><p>路由测试有三种方法：</p><ol><li>使用了 router-link 或 router-view 的组件可以使用stub，但是一般项目都是编程式导航，因此使用第2种和第3种方式较好</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; shallowMount &#125; <span class="keyword">from</span> <span class="string">&#x27;@vue/test-utils&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">shallowMount</span>(<span class="title class_">Component</span>, &#123;</span><br><span class="line">  <span class="attr">stubs</span>: [<span class="string">&#x27;router-link&#x27;</span>, <span class="string">&#x27;router-view&#x27;</span>]</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><ol start="2"><li>引入要测试路由的组件，并创建一个真正的路由实例</li></ol><p>如图，路由中有两个组件，App.vue是根组件</p><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/jest/router.png" alt="router"></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// router.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Vue</span> <span class="keyword">from</span> <span class="string">&#x27;vue&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">VueRouter</span> <span class="keyword">from</span> <span class="string">&#x27;vue-router&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Home</span> <span class="keyword">from</span> <span class="string">&#x27;./Home.vue&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Menu</span> <span class="keyword">from</span> <span class="string">&#x27;./Menu.vue&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> routes = [</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;Home&#x27;</span>, <span class="attr">path</span>: <span class="string">&#x27;/Home&#x27;</span>, <span class="attr">component</span>: <span class="title class_">Home</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;Menu&#x27;</span>, <span class="attr">path</span>: <span class="string">&#x27;/Menu&#x27;</span>, <span class="attr">component</span>: <span class="title class_">Menu</span> &#125;</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line"><span class="title class_">Vue</span>.<span class="title function_">use</span>(<span class="title class_">VueRouter</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">new</span> <span class="title class_">VueRouter</span>(&#123; routes &#125;);</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// router.test.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; mount, createLocalVue &#125; <span class="keyword">from</span> <span class="string">&#x27;@vue/test-utils&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">VueRouter</span> <span class="keyword">from</span> <span class="string">&#x27;vue-router&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">App</span> <span class="keyword">from</span> <span class="string">&#x27;./App&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Home</span> <span class="keyword">from</span> <span class="string">&#x27;./Home&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Menu</span> <span class="keyword">from</span> <span class="string">&#x27;./Menu&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> router <span class="keyword">from</span> <span class="string">&#x27;./router&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> localVue = <span class="title function_">createLocalVue</span>();</span><br><span class="line">localVue.<span class="title function_">use</span>(<span class="title class_">VueRouter</span>);</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;router&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;App router test&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> wrapper = <span class="title function_">mount</span>(<span class="title class_">App</span>, &#123; localVue, router &#125;);</span><br><span class="line">    router.<span class="title function_">push</span>(&#123; <span class="attr">name</span>: <span class="string">&#x27;Menu&#x27;</span> &#125;);</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">find</span>(<span class="title class_">Menu</span>).<span class="title function_">exists</span>()).<span class="title function_">toBe</span>(<span class="literal">true</span>);</span><br><span class="line">    router.<span class="title function_">push</span>(&#123; <span class="attr">name</span>: <span class="string">&#x27;Home&#x27;</span> &#125;);</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">find</span>(<span class="title class_">Home</span>).<span class="title function_">exists</span>()).<span class="title function_">toBe</span>(<span class="literal">true</span>);</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="property">vm</span>.<span class="property">$route</span>.<span class="property">name</span>).<span class="title function_">toBe</span>(<span class="string">&#x27;Home&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ol start="3"><li>伪造 $route 和 $router</li></ol><p>如下，mock所需组件，并添加相关参数，检查最终结果是否符合预期</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// router.test.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; shallowMount, mount, createLocalVue &#125; <span class="keyword">from</span> <span class="string">&#x27;@vue/test-utils&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">VueRouter</span> <span class="keyword">from</span> <span class="string">&#x27;vue-router&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">App</span> <span class="keyword">from</span> <span class="string">&#x27;./App&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Home</span> <span class="keyword">from</span> <span class="string">&#x27;./Home&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Menu</span> <span class="keyword">from</span> <span class="string">&#x27;./Menu&#x27;</span>;</span><br><span class="line"></span><br><span class="line">jest.<span class="title function_">mock</span>(<span class="string">&#x27;./Home&#x27;</span>, <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;Home&#x27;</span>,</span><br><span class="line">  <span class="attr">render</span>: <span class="function"><span class="params">h</span> =&gt;</span> <span class="title function_">h</span>(</span><br><span class="line">    <span class="string">&#x27;div&#x27;</span>,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">class</span>: &#123;</span><br><span class="line">        <span class="attr">show</span>: <span class="literal">true</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">attrs</span>: &#123;</span><br><span class="line">        <span class="attr">id</span>: <span class="string">&#x27;txt&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&#x27;i\&#x27;m render text&#x27;</span></span><br><span class="line">  )</span><br><span class="line">&#125;));</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> routes = [</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;Home&#x27;</span>, <span class="attr">path</span>: <span class="string">&#x27;/Home&#x27;</span>, <span class="attr">component</span>: <span class="title class_">Home</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;Menu&#x27;</span>, <span class="attr">path</span>: <span class="string">&#x27;/Menu&#x27;</span>, <span class="attr">component</span>: <span class="title class_">Menu</span> &#125;</span><br><span class="line">];</span><br><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> <span class="title class_">VueRouter</span>(&#123; routes &#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> localVue = <span class="title function_">createLocalVue</span>();</span><br><span class="line">localVue.<span class="title function_">use</span>(<span class="title class_">VueRouter</span>);</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;router&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;App router test&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 这里需要用mount挂载，所以为了不污染全局的Vue类，使用createLocalVue创建一个新的提供挂载router的类</span></span><br><span class="line">    <span class="keyword">const</span> wrapper = <span class="title function_">mount</span>(<span class="title class_">App</span>, &#123; localVue, router &#125;);</span><br><span class="line">    router.<span class="title function_">push</span>(&#123; <span class="attr">name</span>: <span class="string">&#x27;Menu&#x27;</span> &#125;);</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">find</span>(<span class="title class_">Menu</span>).<span class="title function_">exists</span>()).<span class="title function_">toBe</span>(<span class="literal">true</span>);</span><br><span class="line">    router.<span class="title function_">push</span>(&#123; <span class="attr">name</span>: <span class="string">&#x27;Home&#x27;</span> &#125;);</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">find</span>(<span class="title class_">Home</span>).<span class="title function_">exists</span>()).<span class="title function_">toBe</span>(<span class="literal">true</span>);</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">find</span>(<span class="string">&#x27;#txt&#x27;</span>).<span class="title function_">text</span>()).<span class="title function_">toBe</span>(<span class="string">&#x27;i\&#x27;m render text&#x27;</span>);</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="property">vm</span>.<span class="property">$route</span>.<span class="property">name</span>).<span class="title function_">toBe</span>(<span class="string">&#x27;Home&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;Menu mock router&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;renders a username from query string&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> username = <span class="string">&#x27;alice&#x27;</span>;</span><br><span class="line">    <span class="comment">// shallowMount渲染的是一个替身组件，&lt;router-link&gt;会被忽略，上面的wrapper.find(Menu)也找不到，因为shallowMount的子组件是stub，不存在的。</span></span><br><span class="line">    <span class="keyword">const</span> wrapper = <span class="title function_">shallowMount</span>(<span class="title class_">Menu</span>, &#123;</span><br><span class="line">      <span class="attr">mocks</span>: &#123;</span><br><span class="line">        <span class="attr">$route</span>: &#123;</span><br><span class="line">          <span class="attr">params</span>: &#123; username &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">find</span>(<span class="string">&#x27;.username&#x27;</span>).<span class="title function_">text</span>()).<span class="title function_">toBe</span>(username);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="增加快照"><a href="#增加快照" class="headerlink" title="增加快照"></a>增加快照</h3><p>&emsp;&emsp;当你的组件达到理想状态并测试无误后，可以将当前状态保存为一个快照，这样后面再有改动就可以跟这个快照对比，可以大大提高测试的速度。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 为HTML根元素设置快照</span></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;vue组件测试&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">beforeEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    wrapper = <span class="title function_">mount</span>(hello);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">afterEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    vm &amp;&amp; vm.$destroy();</span><br><span class="line">    wrapper &amp;&amp; wrapper.<span class="title function_">destroy</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;snaps&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="property">element</span>).<span class="title function_">toMatchSnapshot</span>();</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;设置好快照，下次有更改的东西直接会提示出来：<br><img src="https://raw.githubusercontent.com/wz71014q/img/master/jest/snapshot.png" alt="snapshot"></p><p>更新快照可以用：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jset -u</span><br></pre></td></tr></table></figure><h2 id="常用测试项"><a href="#常用测试项" class="headerlink" title="常用测试项"></a>常用测试项</h2><ol><li>检测Dom</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;renders a div&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">expect</span>(wrapper.<span class="title function_">contains</span>(<span class="string">&#x27;div&#x27;</span>)).<span class="title function_">toBe</span>(<span class="literal">true</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;p标签的内容是I\&#x27;m jest demo&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">expect</span>(wrapper.<span class="title function_">find</span>(<span class="string">&#x27;.text&#x27;</span>).<span class="title function_">text</span>()).<span class="title function_">toEqual</span>(<span class="string">&#x27;I\&#x27;m jest demo&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ol start="2"><li>检测类名</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;check classes&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> checkButton = wrapper.<span class="title function_">find</span>(<span class="string">&#x27;.check-box&#x27;</span>);</span><br><span class="line">  <span class="title function_">expect</span>(checkButton.<span class="title function_">classes</span>()).<span class="title function_">toContain</span>(<span class="string">&#x27;is-checked&#x27;</span>);</span><br><span class="line">  <span class="title function_">expect</span>(checkButton.<span class="title function_">classes</span>().<span class="title function_">indexOf</span>(<span class="string">&#x27;checked&#x27;</span>) === -<span class="number">1</span>).<span class="title function_">toBe</span>(<span class="literal">true</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ol start="3"><li>检测样式</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;传递的颜色应该是#fff&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> icon = wrapper.<span class="title function_">find</span>(<span class="string">&#x27;.icon&#x27;</span>);</span><br><span class="line">  <span class="title function_">expect</span>(icon.<span class="property">element</span>.<span class="property">style</span>.<span class="property">color</span>).<span class="title function_">toEqual</span>(<span class="string">&#x27;rgb(255, 255, 255)&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ol start="4"><li>检测方法有没有被调用</li></ol><ul><li>Vue Test Utils</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;click&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> checkButton = wrapper.<span class="title function_">find</span>(<span class="string">&#x27;.check-box&#x27;</span>);</span><br><span class="line">    checkButton.<span class="title function_">trigger</span>(<span class="string">&#x27;click&#x27;</span>);</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">emitted</span>().<span class="property">input</span>.<span class="property">length</span>).<span class="title function_">toBe</span>(<span class="number">1</span>);</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">emitted</span>().<span class="property">input</span>[<span class="number">0</span>]).<span class="title function_">toBeTruthy</span>();</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">emitted</span>().<span class="property">input</span>[<span class="number">0</span>]).<span class="title function_">toEqual</span>([<span class="number">0</span>]);</span><br><span class="line">    <span class="title function_">expect</span>(checkButton.<span class="title function_">classes</span>()).<span class="title function_">toContain</span>(<span class="string">&#x27;is-checked&#x27;</span>);</span><br><span class="line">    checkButton.<span class="title function_">trigger</span>(<span class="string">&#x27;click&#x27;</span>);</span><br><span class="line">    <span class="title function_">expect</span>(wrapper.<span class="title function_">emitted</span>().<span class="property">input</span>.<span class="property">length</span>).<span class="title function_">toBe</span>(<span class="number">2</span>);</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure><ul><li>sinon</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"> <span class="title function_">it</span>(<span class="string">&#x27;triggle&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> shallowWrapper = <span class="title function_">wrapper</span>(<span class="title class_">Header</span>);</span><br><span class="line">    <span class="keyword">const</span> eventSpy = sinon.<span class="title function_">spy</span>(wrapper.<span class="property">vm</span>, <span class="string">&#x27;$emit&#x27;</span>);</span><br><span class="line">    wrapper.<span class="title function_">find</span>(<span class="string">&#x27;.gree-header-title&#x27;</span>).<span class="title function_">trigger</span>(<span class="string">&#x27;click&#x27;</span>);</span><br><span class="line">    <span class="title function_">expect</span>(eventSpy.<span class="title function_">withArgs</span>(<span class="string">&#x27;on-click-title&#x27;</span>).<span class="property">calledOnce</span>).<span class="title function_">toBeTruthy</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ol start="5"><li>检测传入的数据有没有正常接收<br>挂载组件时传入一组虚拟数据，根据传入的内容和得到的结果验证逻辑处理是否正确：</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;The text of check-box should be something, () =&gt; &#123;</span></span><br><span class="line"><span class="string">    const shallowWrapper = shallowMount(box, &#123;</span></span><br><span class="line"><span class="string">      slots: &#123;</span></span><br><span class="line"><span class="string">        default: &#x27;</span>something<span class="string">&#x27;</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string">    &#125;)</span></span><br><span class="line"><span class="string">    expect(shallowWrapper.find(&#x27;</span>.<span class="property">check</span>-box<span class="string">&#x27;).text()).toBe(&#x27;</span>something<span class="string">&#x27;);</span></span><br><span class="line"><span class="string">    shallowWrapper.destroy();</span></span><br><span class="line"><span class="string">  &#125;);</span></span><br></pre></td></tr></table></figure><h2 id="常用API"><a href="#常用API" class="headerlink" title="常用API"></a>常用API</h2><h3 id="jest"><a href="#jest" class="headerlink" title="jest"></a>jest</h3><ul><li>全局API</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 分组</span></span><br><span class="line"><span class="title function_">descrip</span>(<span class="string">&#x27;title&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;test1&#x27;</span>, <span class="function">()=&gt;</span>&#123;</span><br><span class="line">    <span class="comment">// do somthing</span></span><br><span class="line">  &#125;)</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;test2&#x27;</span>, <span class="function">()=&gt;</span>&#123;</span><br><span class="line">    <span class="comment">// do somthing</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="title function_">beforeEach</span>(fn, timeout) <span class="comment">// 在当前作用域下每次执行前运行</span></span><br><span class="line"><span class="title function_">afterEach</span>(fn, timeout) <span class="comment">// 在当前作用域下每次执行后运行</span></span><br><span class="line"><span class="title function_">beforeAll</span>(fn, timeout) <span class="comment">// 全部代码执行前运行</span></span><br><span class="line"><span class="comment">// timeout: ms 终止前等待的时间，默认是5000, 5s到了后即使没有执行完测试用例也会终止</span></span><br><span class="line"><span class="comment">// 使用举例</span></span><br><span class="line"><span class="title function_">beforeAll</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;global - beforeAll&#x27;</span>));</span><br><span class="line"><span class="title function_">afterAll</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;global - afterAll&#x27;</span>));</span><br><span class="line"><span class="title function_">beforeEach</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;global - beforeEach&#x27;</span>));</span><br><span class="line"><span class="title function_">afterEach</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;global - afterEach&#x27;</span>));</span><br><span class="line"><span class="title function_">test</span>(<span class="string">&#x27;&#x27;</span>, <span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;global - test&#x27;</span>));</span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;Scoped / Nested block&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">beforeAll</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Scoped - beforeAll&#x27;</span>));</span><br><span class="line">  <span class="title function_">afterAll</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Scoped - afterAll&#x27;</span>));</span><br><span class="line">  <span class="title function_">beforeEach</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Scoped - beforeEach&#x27;</span>));</span><br><span class="line">  <span class="title function_">afterEach</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Scoped - afterEach&#x27;</span>));</span><br><span class="line">  <span class="title function_">test</span>(<span class="string">&#x27;&#x27;</span>, <span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Scoped - test&#x27;</span>));</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 执行顺序</span></span><br><span class="line"><span class="comment">// global - beforeAll</span></span><br><span class="line"><span class="comment">// global - beforeEach</span></span><br><span class="line"><span class="comment">// global - test</span></span><br><span class="line"><span class="comment">// global - afterEach</span></span><br><span class="line"><span class="comment">// Scoped - beforeAll</span></span><br><span class="line"><span class="comment">// global - beforeEach</span></span><br><span class="line"><span class="comment">// Scoped - beforeEach</span></span><br><span class="line"><span class="comment">// Scoped - test</span></span><br><span class="line"><span class="comment">// Scoped - afterEach</span></span><br><span class="line"><span class="comment">// global - afterEach</span></span><br><span class="line"><span class="comment">// Scoped - afterAll</span></span><br><span class="line"><span class="comment">// global - afterAll</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(name, fn) <span class="comment">// 创建一个测试用例的分组</span></span><br><span class="line"><span class="title function_">test</span>(name, fn, timeout) <span class="comment">// 测试用例</span></span><br><span class="line"><span class="title function_">it</span>(name, fn, timeout) <span class="comment">// 测试用例</span></span><br><span class="line">test.<span class="title function_">each</span>(table)(name, fn, timeout) <span class="comment">// 当一个测试用例需要执行多次，但是每次仅仅是参数不同时用这个</span></span><br><span class="line"><span class="comment">// e.g.</span></span><br><span class="line">test.<span class="title function_">each</span>([[<span class="number">1</span>, <span class="number">1</span>, <span class="number">2</span>], [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>], [<span class="number">2</span>, <span class="number">1</span>, <span class="number">3</span>]])(</span><br><span class="line">  <span class="string">&#x27;.add(%i, %i)&#x27;</span>,</span><br><span class="line">  <span class="function">(<span class="params">a, b, expected</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">expect</span>(a + b).<span class="title function_">toBe</span>(expected);</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><ul><li>断言</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">expect</span>(<span class="title function_">fn</span>()).<span class="title function_">toBe</span>(value) <span class="comment">// 断言fn的执行结果是value</span></span><br><span class="line">expect.<span class="title function_">extend</span>(matchers) <span class="comment">// 自定义断言</span></span><br><span class="line"><span class="comment">// e.g.</span></span><br><span class="line">expect.<span class="title function_">extend</span>(&#123;</span><br><span class="line">  <span class="title function_">matchers</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;numeric ranges&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">expect</span>(<span class="number">100</span>).<span class="title function_">matchers</span>().<span class="title function_">toBe</span>(<span class="literal">true</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title function_">expect</span>().<span class="title function_">toBeTruthy</span>() <span class="comment">// 结果为真</span></span><br><span class="line"><span class="title function_">expect</span>().<span class="title function_">toEqual</span>() <span class="comment">// 递归比较对象实例的所有属性（也称为“深度”相等）。</span></span><br></pre></td></tr></table></figure><ul><li>mock</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">jest.<span class="title function_">fn</span>() <span class="comment">// 提供一个虚拟方法</span></span><br><span class="line"><span class="comment">// e.g.</span></span><br><span class="line"><span class="keyword">const</span> returnsTrue = jest.<span class="title function_">fn</span>(<span class="function">() =&gt;</span> <span class="literal">true</span>);</span><br><span class="line"><span class="title function_">returnsTrue</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">returnsTrue</span>()); <span class="comment">// true;</span></span><br><span class="line"><span class="title function_">expect</span>(returnsTrue).<span class="title function_">toHaveBeenCalled</span>();</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">jest.<span class="title function_">mock</span>(moduleName, factory, options) <span class="comment">// 在需要时模拟一个模块。factory和options是可选的。比如测试路由时可以mock所需的路由模块</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 举例：</span></span><br><span class="line"><span class="comment">// sum.js</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sum</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">88</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = sum;</span><br><span class="line"></span><br><span class="line"><span class="comment">// sum.test.js</span></span><br><span class="line"><span class="keyword">const</span> sum = <span class="built_in">require</span>(<span class="string">&#x27;./sum.js&#x27;</span>);</span><br><span class="line">jest.<span class="title function_">mock</span>(<span class="string">&#x27;./sum.js&#x27;</span>, <span class="function">() =&gt;</span> jest.<span class="title function_">fn</span>(<span class="function">() =&gt;</span> <span class="number">66</span>));</span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;sum&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;sum should return 66&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">sum</span>()); <span class="comment">// 66，这里的sum()是mock的假方法</span></span><br><span class="line">    <span class="title function_">expect</span>(<span class="title function_">sum</span>()).<span class="title function_">toBe</span>(<span class="number">66</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Debug"><a href="#Debug" class="headerlink" title="Debug"></a>Debug</h2><p>&emsp;&emsp;vscode有jest插件，安装后可以快速开启debug。debug环境是在node下的，也可以自己配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0.2.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;configurations&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;node&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vscode-jest-tests&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;request&quot;</span><span class="punctuation">:</span> <span class="string">&quot;launch&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;--runInBand&quot;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;cwd&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;workspaceFolder&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;console&quot;</span><span class="punctuation">:</span> <span class="string">&quot;integratedTerminal&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;internalConsoleOptions&quot;</span><span class="punctuation">:</span> <span class="string">&quot;neverOpen&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;program&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;workspaceFolder&#125;/node_modules/jest/bin/jest&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>配置好后设置断点，按F5即可。</p><p><img src="https://raw.githubusercontent.com/wz71014q/img/master/jest/jestdebug.png" alt="snapshot"></p>]]>
    </content>
    <id>https://github.com/wz71014q/2019/06/05/VUE%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/</id>
    <link href="https://github.com/wz71014q/2019/06/05/VUE%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/"/>
    <published>2019-06-05T12:00:00.000Z</published>
    <summary>
      <![CDATA[<p>&emsp;&emsp;为了提高代码设计水平，测试是必不可少的。<a href="https://jestjs.io/zh-Hans/">jest</a>是facebook出的一个测试框架，里面自带断言库，而且VUE有个<a href="https://vue-test-utils.vuejs.org/zh/">Vue Test Utils</a>提供了官方支持，因此这里使用jest构建VUE单元测试部分。开始前建议先浏览一下官方网站，进行初步了解。</p>
<h2 id="起步"><a href="#起步" class="headerlink" title="起步"></a>起步</h2><h3 id="安装-简易demo"><a href="#安装-简易demo" class="headerlink" title="安装&amp;简易demo"></a>安装&amp;简易demo</h3><p>新建一个文件夹，然后执行：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm init</span><br><span class="line">npm install jest -g</span><br></pre></td></tr></table></figure>

<p>这就安装好了，接下来写一个简单的函数：</p>
<p><img src="https://raw.githubusercontent.com/wz71014q/img/master/jest/jestSimpleDemo.png" alt="jestSimpleDemo"></p>
<p>然后在你的项目文件夹下执行jest, 就会自动搜索所有.test.js和.spec.js文件进行测试。</p>
<h3 id="增加配置"><a href="#增加配置" class="headerlink" title="增加配置"></a>增加配置</h3><p>接着我们改一下目录，增加一个SRC文件夹，里面放着我们的原文件，然后新建test文件夹，将测试文件全部放进去<br><img src="https://raw.githubusercontent.com/wz71014q/img/master/jest/jestES6Demo.png" alt="jestES6Demo"><br>在demo.test.js中添加代码：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// demo.test.js</span></span><br><span class="line"><span class="keyword">import</span> checkNumber <span class="keyword">from</span> <span class="string">&#x27;../src/demo/demo&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;decribe用来生成一个组&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">test</span>(<span class="string">&#x27;checkNumber&#x27;</span>, <span class="function">()=&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">expect</span>(<span class="title function_">checkNumber</span>(<span class="number">2</span>, <span class="number">3</span>)).<span class="title function_">toBe</span>(<span class="number">5</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>]]>
    </summary>
    <title>Vue单元测试踩坑记录</title>
    <updated>2026-05-15T08:53:54.861Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="CSS" scheme="https://github.com/wz71014q/categories/CSS/"/>
    <category term="屏幕适配" scheme="https://github.com/wz71014q/categories/%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D/"/>
    <category term="CSS" scheme="https://github.com/wz71014q/tags/CSS/"/>
    <category term="屏幕适配" scheme="https://github.com/wz71014q/tags/%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D/"/>
    <content>
      <![CDATA[<p>&emsp;&emsp;前端适配是个麻烦事。PC端要适配不同浏览器，移动端又要适配各种屏幕。而这一切的根源，要从「设备像素」和「逻辑像素」这对概念说起。</p><h2 id="基础概念"><a href="#基础概念" class="headerlink" title="基础概念"></a>基础概念</h2><p>&emsp;&emsp;设备像素（Device Pixel）是屏幕的<strong>物理像素</strong>，一块屏幕出厂时就固定的发光点数量。比如 iPhone 8 是 750 × 1334 像素，这是硬件决定的，改不了。</p><p>&emsp;&emsp;逻辑像素（CSS Pixel &#x2F; Logical Pixel）是<strong>我们写代码时用的像素</strong>。CSS 里的 <code>width: 375px</code> 指的就是逻辑像素。</p><p>&emsp;&emsp;为什么会有两套像素系统？因为手机屏幕太小了，如果 1px CSS 对应 1 个物理像素，文字和按钮会小到看不清。所以苹果在 iPhone 4 上提出了 Retina 概念，用一个逻辑像素对应多个物理像素。</p><h2 id="DPR（Device-Pixel-Ratio）"><a href="#DPR（Device-Pixel-Ratio）" class="headerlink" title="DPR（Device Pixel Ratio）"></a>DPR（Device Pixel Ratio）</h2><p>&emsp;&emsp;DPR 就是设备像素与逻辑像素的比值：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DPR = 设备像素 / 逻辑像素</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;常见的 DPR 值：</p><table><thead><tr><th>设备</th><th>逻辑分辨率</th><th>物理分辨率</th><th>DPR</th></tr></thead><tbody><tr><td>iPhone SE &#x2F; 6&#x2F;7&#x2F;8</td><td>375 × 667</td><td>750 × 1334</td><td>2</td></tr><tr><td>iPhone 12 Pro</td><td>390 × 844</td><td>1170 × 2532</td><td>3</td></tr><tr><td>MacBook Pro Retina</td><td>1440 × 900</td><td>2880 × 1800</td><td>2</td></tr><tr><td>普通 1080p 显示器</td><td>1920 × 1080</td><td>1920 × 1080</td><td>1</td></tr></tbody></table><p>&emsp;&emsp;DPR &#x3D; 1 时，一个 CSS 像素就是一个物理像素；DPR &#x3D; 2 时，一个 CSS 像素由 2×2 个物理像素渲染，所以画面更细腻。</p><h2 id="由-DPR-引发的问题"><a href="#由-DPR-引发的问题" class="headerlink" title="由 DPR 引发的问题"></a>由 DPR 引发的问题</h2><h3 id="1-1px-边框变粗"><a href="#1-1px-边框变粗" class="headerlink" title="1. 1px 边框变粗"></a>1. 1px 边框变粗</h3><span id="more"></span><p>&emsp;&emsp;最经典的问题：写 <code>border: 1px solid #ccc</code>，在 DPR &#x3D; 2 的屏幕上，物理上实际渲染了 2px 的宽度，看起来比设计稿粗。解决方案一般用 <code>transform: scale()</code> 来模拟 0.5px：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* DPR = 2 下 0.5px 等效 */</span></span><br><span class="line"><span class="selector-class">.hairline</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.hairline</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">bottom</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">1px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#ccc</span>;</span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">scaleY</span>(<span class="number">0.5</span>);</span><br><span class="line">  <span class="attribute">transform-origin</span>: <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-图片模糊"><a href="#2-图片模糊" class="headerlink" title="2. 图片模糊"></a>2. 图片模糊</h3><p>&emsp;&emsp;一张 100×100px 的图片，在 DPR &#x3D; 2 的屏幕上需要 200×200px 的物理像素来渲染，但实际只有 100×100px 的数据，就只能被拉伸，导致模糊。解决方案是用 <code>srcset</code> 提供不同倍率的图：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;photo-1x.jpg&quot;</span></span></span><br><span class="line"><span class="tag">     <span class="attr">srcset</span>=<span class="string">&quot;photo-2x.jpg 2x, photo-3x.jpg 3x&quot;</span></span></span><br><span class="line"><span class="tag">     <span class="attr">alt</span>=<span class="string">&quot;photo&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>或者用 CSS <code>image-set()</code>：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.avatar</span> &#123;</span><br><span class="line">  <span class="attribute">background-image</span>: <span class="built_in">image-set</span>(</span><br><span class="line">    <span class="built_in">url</span>(<span class="string">&quot;avatar-1x.jpg&quot;</span>) <span class="number">1</span>x,</span><br><span class="line">    <span class="built_in">url</span>(<span class="string">&quot;avatar-2x.jpg&quot;</span>) <span class="number">2</span>x</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-媒体查询"><a href="#3-媒体查询" class="headerlink" title="3. 媒体查询"></a>3. 媒体查询</h3><p>&emsp;&emsp;有时需要针对不同 DPR 做不同布局，可以用 <code>device-pixel-ratio</code>：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* DPR = 2 */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">-webkit-min-device-pixel-ratio</span>: <span class="number">2</span>) &#123;</span><br><span class="line">  <span class="selector-class">.box</span> &#123; <span class="attribute">border</span>: <span class="number">0.5px</span> solid <span class="number">#ccc</span>; &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/* DPR = 3 */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">-webkit-min-device-pixel-ratio</span>: <span class="number">3</span>) &#123;</span><br><span class="line">  <span class="selector-class">.box</span> &#123; <span class="attribute">border</span>: <span class="number">0.333px</span> solid <span class="number">#ccc</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="视口（Viewport）"><a href="#视口（Viewport）" class="headerlink" title="视口（Viewport）"></a>视口（Viewport）</h2><p>&emsp;&emsp;移动端还有一个「视口」概念。在没做适配的页面中，手机浏览器会用一个「虚拟视口」来容纳整个 PC 页面，导致文字看起来很小。解决方案是加 viewport meta 标签：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>&emsp;&emsp;<code>width=device-width</code> 让页面宽度等于设备宽度（逻辑像素），<code>initial-scale=1.0</code> 禁止默认缩放。这样 CSS 中的 100vw 就等于逻辑宽度，页面就正常了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li><strong>设备像素</strong>是屏幕物理发光点，写代码时一般不用管它</li><li><strong>逻辑像素</strong>是我们写 CSS 用的单位，通过 DPR 与物理像素关联</li><li>DPR &gt; 1 导致 1px 边框变粗和图片模糊，需要用 transform + srcset 来适配</li><li>移动端一定要加 viewport meta 标签</li></ul>]]>
    </content>
    <id>https://github.com/wz71014q/2019/06/04/CSS-%E8%AE%BE%E5%A4%87%E5%83%8F%E7%B4%A0%E4%B8%8E%E9%80%BB%E8%BE%91%E5%83%8F%E7%B4%A0/</id>
    <link href="https://github.com/wz71014q/2019/06/04/CSS-%E8%AE%BE%E5%A4%87%E5%83%8F%E7%B4%A0%E4%B8%8E%E9%80%BB%E8%BE%91%E5%83%8F%E7%B4%A0/"/>
    <published>2019-06-04T13:00:00.000Z</published>
    <summary>
      <![CDATA[<p>&emsp;&emsp;前端适配是个麻烦事。PC端要适配不同浏览器，移动端又要适配各种屏幕。而这一切的根源，要从「设备像素」和「逻辑像素」这对概念说起。</p>
<h2 id="基础概念"><a href="#基础概念" class="headerlink" title="基础概念"></a>基础概念</h2><p>&emsp;&emsp;设备像素（Device Pixel）是屏幕的<strong>物理像素</strong>，一块屏幕出厂时就固定的发光点数量。比如 iPhone 8 是 750 × 1334 像素，这是硬件决定的，改不了。</p>
<p>&emsp;&emsp;逻辑像素（CSS Pixel &#x2F; Logical Pixel）是<strong>我们写代码时用的像素</strong>。CSS 里的 <code>width: 375px</code> 指的就是逻辑像素。</p>
<p>&emsp;&emsp;为什么会有两套像素系统？因为手机屏幕太小了，如果 1px CSS 对应 1 个物理像素，文字和按钮会小到看不清。所以苹果在 iPhone 4 上提出了 Retina 概念，用一个逻辑像素对应多个物理像素。</p>
<h2 id="DPR（Device-Pixel-Ratio）"><a href="#DPR（Device-Pixel-Ratio）" class="headerlink" title="DPR（Device Pixel Ratio）"></a>DPR（Device Pixel Ratio）</h2><p>&emsp;&emsp;DPR 就是设备像素与逻辑像素的比值：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DPR = 设备像素 / 逻辑像素</span><br></pre></td></tr></table></figure>

<p>&emsp;&emsp;常见的 DPR 值：</p>
<table>
<thead>
<tr>
<th>设备</th>
<th>逻辑分辨率</th>
<th>物理分辨率</th>
<th>DPR</th>
</tr>
</thead>
<tbody><tr>
<td>iPhone SE &#x2F; 6&#x2F;7&#x2F;8</td>
<td>375 × 667</td>
<td>750 × 1334</td>
<td>2</td>
</tr>
<tr>
<td>iPhone 12 Pro</td>
<td>390 × 844</td>
<td>1170 × 2532</td>
<td>3</td>
</tr>
<tr>
<td>MacBook Pro Retina</td>
<td>1440 × 900</td>
<td>2880 × 1800</td>
<td>2</td>
</tr>
<tr>
<td>普通 1080p 显示器</td>
<td>1920 × 1080</td>
<td>1920 × 1080</td>
<td>1</td>
</tr>
</tbody></table>
<p>&emsp;&emsp;DPR &#x3D; 1 时，一个 CSS 像素就是一个物理像素；DPR &#x3D; 2 时，一个 CSS 像素由 2×2 个物理像素渲染，所以画面更细腻。</p>
<h2 id="由-DPR-引发的问题"><a href="#由-DPR-引发的问题" class="headerlink" title="由 DPR 引发的问题"></a>由 DPR 引发的问题</h2><h3 id="1-1px-边框变粗"><a href="#1-1px-边框变粗" class="headerlink" title="1. 1px 边框变粗"></a>1. 1px 边框变粗</h3>]]>
    </summary>
    <title>CSS-设备像素与逻辑像素</title>
    <updated>2026-05-22T05:08:32.107Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <content>
      <![CDATA[<p>异步编程是JS中必备的一部分。由于JS是单线程，如果全部事件都顺序执行会阻塞进程。所以一些耗时较长的事件采用异步方式进行。这里记录一些《深入浅出Node.js》笔记，有些地方没有碰到过，不理解，后续完善。</p><h2 id="异步编程的一些问题："><a href="#异步编程的一些问题：" class="headerlink" title="异步编程的一些问题："></a>异步编程的一些问题：</h2><ol><li>异常处理<br>一般，使用try&#x2F;catch&#x2F;finally来进行错误处理</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="title class_">JSON</span>.<span class="title function_">parse</span>(json);</span><br><span class="line">&#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(e);</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123; <span class="comment">// 无论是否发生错误，finally都会执行。错误会在最近的catch块中捕获，然后终止</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;finally&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在有一个异步方法：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">async</span> = <span class="keyword">function</span> (<span class="params">callback</span>) &#123;</span><br><span class="line">  process.<span class="title function_">nextTick</span>(callback);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="title function_">async</span>(callback);</span><br><span class="line">&#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(e);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里调用async()方法只能捕获async方法的异常，但是回调callback的异常却无法捕获</p><span id="more"></span><ol start="2"><li>函数嵌套太深</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">connection.<span class="title function_">query</span>(sql, <span class="function">(<span class="params">err, result</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span>(err) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">err</span>(err)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    connection.<span class="title function_">query</span>(sql, <span class="function">(<span class="params">err, result</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span>(err) &#123;</span><br><span class="line">          <span class="variable language_">console</span>.<span class="title function_">err</span>(err)</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          ...</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><ol start="3"><li>阻塞代码</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这段代码虽然可以阻塞进程，但会持续占用CPU进行判断，性能不佳</span></span><br><span class="line"><span class="keyword">const</span> start = <span class="keyword">new</span> <span class="title class_">Date</span>();</span><br><span class="line"><span class="keyword">while</span> (<span class="keyword">new</span> <span class="title class_">Date</span>() - start &lt; <span class="number">1000</span>) &#123;</span><br><span class="line">  ···</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>多线程编程</li><li>异步转同步</li></ol><h2 id="异步编程解决方案"><a href="#异步编程解决方案" class="headerlink" title="异步编程解决方案"></a>异步编程解决方案</h2><h3 id="事件发布-订阅模式"><a href="#事件发布-订阅模式" class="headerlink" title="事件发布&#x2F;订阅模式"></a>事件发布&#x2F;订阅模式</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 监听</span></span><br><span class="line">emitter.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="keyword">function</span>(<span class="params">msg</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(msg);</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 发布</span></span><br><span class="line">emitter.<span class="title function_">emit</span>(<span class="string">&#x27;message&#x27;</span>, <span class="string">&#x27;I\&#x27;m bat man!&#x27;</span>);</span><br></pre></td></tr></table></figure><p>事件发布&#x2F;订阅没有同步、异步的概念，只要一发布、监听者立刻调用。</p><h3 id="Promise-Defeered模式"><a href="#Promise-Defeered模式" class="headerlink" title="Promise&#x2F;Defeered模式"></a>Promise&#x2F;Defeered模式</h3><p>Defeered: 延迟对象，暂不处理。</p><p>Promise 对象用于表示一个异步操作的最终状态（完成或失败），以及该异步操作的结果值。它的特点：</p><blockquote><ol><li>Promise对象代表一个异步操作，只有三种状态：pending（进行中）、fulfilled（已成功）和rejected（已失败）。</li><li>Promise只会从pending转化到fulfilled或者是rejected，不会逆转。转化成功后就不会再变了，会一直保持这个结果，这时就称为 resolved（已定型）。</li></ol></blockquote><h4 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h4><p>创建一个Pormise实例：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">asyncTime</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;promise start&#x27;</span>);</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="title function_">resolve</span>(<span class="string">&#x27;time down&#x27;</span>)</span><br><span class="line">    &#125;, <span class="number">5000</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">asyncTime</span>()</span><br><span class="line"> .<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">   <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line"> &#125;)</span><br><span class="line"> .<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">   <span class="variable language_">console</span>.<span class="title function_">error</span>(error);</span><br><span class="line"> &#125;)</span><br><span class="line"> .<span class="title function_">finally</span>(<span class="function">() =&gt;</span> &#123; <span class="comment">// finally方法的回调函数不接受任何参数</span></span><br><span class="line">   <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;执行完成&#x27;</span>);</span><br><span class="line"> &#125;)</span><br></pre></td></tr></table></figure><p>输出结果：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">promise start</span><br><span class="line"><span class="comment">// 中间间隔5秒</span></span><br><span class="line">time down</span><br></pre></td></tr></table></figure><p>Promise实例化后立即执行，输出”promise start”，5秒后执行成功，输出”time down”。</p><p>有几点需要注意：</p><ol><li>resolve将Promise对象的状态从“未完成”变为“成功”（即从 pending 变为 resolved），在异步操作成功时调用，并将异步操作的结果作为参数传递出去。reject也可以将Promise对象的状态结束，但是是变成“失败”（即从 pending 变为 rejected)。</li><li>then方法的第一个参数是resolved状态的回调函数，第二个参数（可选）是rejected状态的回调函数。一般then只用来接收resolve状态。rejected状态由catch捕获执行。</li><li>resolve()、reject()、then()和cacth()方法的返回值都是新的Promise对象。所以可以在后面接着使用then&#x2F;catch，链式调用。</li><li>立即resolve()的 Promise 对象，是在本轮“事件循环”（event loop）的结束时执行，而不是在下一轮“事件循环”的开始时。</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;three&#x27;</span>);</span><br><span class="line">&#125;, <span class="number">0</span>);</span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">resolve</span>().<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;two&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;one&#x27;</span>);</span><br><span class="line"><span class="comment">// one</span></span><br><span class="line"><span class="comment">// two</span></span><br><span class="line"><span class="comment">// three</span></span><br></pre></td></tr></table></figure><h4 id="链式调用"><a href="#链式调用" class="headerlink" title="链式调用"></a>链式调用</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">doSomething</span>()</span><br><span class="line">.<span class="title function_">then</span>(<span class="function"><span class="params">result</span> =&gt;</span> <span class="title function_">doSomethingElse</span>(value))</span><br><span class="line">.<span class="title function_">then</span>(<span class="function"><span class="params">newResult</span> =&gt;</span> <span class="title function_">doThirdThing</span>(newResult))</span><br><span class="line">.<span class="title function_">then</span>(<span class="function"><span class="params">finalResult</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Got the final result: <span class="subst">$&#123;finalResult&#125;</span>`</span>))</span><br><span class="line">.<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123;<span class="variable language_">console</span>.<span class="title function_">error</span>(err)&#125;);</span><br></pre></td></tr></table></figure><p>通常，一遇到异常抛出，promise链就会停下来，直接调用链式中的catch处理程序。</p><h4 id="单独使用Promise-resolve-和Promise-reject"><a href="#单独使用Promise-resolve-和Promise-reject" class="headerlink" title="单独使用Promise.resolve()和Promise.reject()"></a>单独使用Promise.resolve()和Promise.reject()</h4><p>Promise.resolve() 和 Promise.reject() 是手动创建一个已经resolve或者reject的promise快捷方法。以Promise.resolve()为例</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="string">&#x27;foo&#x27;</span>)</span><br><span class="line"><span class="comment">// 等价于</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">resolve</span>(<span class="string">&#x27;foo&#x27;</span>);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><ol><li>如果Promise.resolve()参数是一个Promise对象，则不做任何修改，直接返回；</li><li>如果Promise.resolve()参数是一个不具有then方法的对象或根本就不是对象的参数，返回一个resolve状态的Promise对象</li><li>如果Promise.resolve()不带参数，返回一个resolve状态的Promise对象</li><li>Promise.reject()返回reject状态的Promise对象</li></ol><h4 id="Primse-all-和Promise-race"><a href="#Primse-all-和Promise-race" class="headerlink" title="Primse.all()和Promise.race()"></a>Primse.all()和Promise.race()</h4><p>Promise.all()方法用于将多个 Promise 实例，包装成一个新的 Promise 实例。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> p = <span class="title class_">Promise</span>.<span class="title function_">all</span>([p1, p2, p3]);</span><br></pre></td></tr></table></figure><blockquote><p>上面代码中，Promise.all方法接受一个数组作为参数，p1、p2、p3都是 Promise 实例，如果不是，就会先调用Promise.resolve方法，将参数转为 Promise 实&gt;例。p的状态由p1、p2、p3决定，分成两种情况：</p><ol><li>只有p1、p2、p3的状态都变成fulfilled，p的状态才会变成fulfilled，此时p1、p2、p3的返回值组成一个数组，传递给p的回调函数。</li><li>只要p1、p2、p3之中有一个被rejected，p的状态就变成rejected，此时第一个被reject的实例的返回值，会传递给p的回调函数。</li></ol></blockquote><p>Promise.race()方法同样是将多个 Promise 实例，包装成一个新的 Promise 实例。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> p = <span class="title class_">Promise</span>.<span class="title function_">race</span>([p1, p2, p3]);</span><br></pre></td></tr></table></figure><blockquote><p>上面代码中，只要p1、p2、p3之中有一个实例率先改变状态，p的状态就跟着改变。那个率先改变的 Promise 实例的返回值，就传递给p的回调函数。</p></blockquote><h4 id="async函数"><a href="#async函数" class="headerlink" title="async函数"></a>async函数</h4><p>async函数跟Promise可以搭配使用。async函数有返回值时会返回一个Promise，后面可以跟then方法。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 基本用法，start → timer middle → (timer then) → end</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;start&#x27;</span>);</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">timer</span>();</span><br><span class="line">    <span class="comment">// await timer().then(() =&gt; &#123;</span></span><br><span class="line">    <span class="comment">//   console.log(&#x27;timer then&#x27;);</span></span><br><span class="line">    <span class="comment">// &#125;);</span></span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;end&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">timer</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;timer middle&#x27;</span>);</span><br><span class="line">      <span class="title function_">resolve</span>();</span><br><span class="line">    &#125;, <span class="number">3000</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">test</span>();</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 错误示范，新建Promise会立即运行，而且没有返回一个Promise对象，await后面要么跟一个Promise对象(或有then方法的对象)要么跟个常量，</span></span><br><span class="line"><span class="comment">// 这里timer()先执行，后面再执行test()时会报错：timer is not a function。</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;start&#x27;</span>);</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">timer</span>();</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;end&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> timer = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;timer start&#x27;</span>);</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;middle&#x27;</span>);</span><br><span class="line">    <span class="title function_">resolve</span>();</span><br><span class="line">  &#125;, <span class="number">3000</span>);</span><br><span class="line">&#125;)</span><br><span class="line"><span class="title function_">test</span>();</span><br></pre></td></tr></table></figure><h3 id="异步的并发限制和超时控制"><a href="#异步的并发限制和超时控制" class="headerlink" title="异步的并发限制和超时控制"></a>异步的并发限制和超时控制</h3><p>异步解决方案成熟的第三方库有async、Step等。</p><ul><li>Node中的异步调用有时需要控制并发数量，防止底层系统的性能出问题，一种思路是创建一个队列，每个异步调用顺序存入。设定最大并发数，如果当前活跃的异步调用数量小于最大并发数，直接取出执行，如果大于最大数量，则暂存在队列中，顺序取出调用。</li><li>超时控制可以给异步调用设置一个时间阈值，如果异步调用没有在规定时间内完成，则提示超时。</li></ul><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>《深入浅出Node.js》<br><a href="http://es6.ruanyifeng.com/#docs/promise">ECMAScript6入门——Promise对象</a></p>]]>
    </content>
    <id>https://github.com/wz71014q/2019/05/23/js%E5%9F%BA%E7%A1%80%E4%B9%8B%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <link href="https://github.com/wz71014q/2019/05/23/js%E5%9F%BA%E7%A1%80%E4%B9%8B%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <published>2019-05-23T12:00:00.000Z</published>
    <summary>
      <![CDATA[<p>异步编程是JS中必备的一部分。由于JS是单线程，如果全部事件都顺序执行会阻塞进程。所以一些耗时较长的事件采用异步方式进行。这里记录一些《深入浅出Node.js》笔记，有些地方没有碰到过，不理解，后续完善。</p>
<h2 id="异步编程的一些问题："><a href="#异步编程的一些问题：" class="headerlink" title="异步编程的一些问题："></a>异步编程的一些问题：</h2><ol>
<li>异常处理<br>一般，使用try&#x2F;catch&#x2F;finally来进行错误处理</li>
</ol>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="title class_">JSON</span>.<span class="title function_">parse</span>(json);</span><br><span class="line">&#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(e);</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123; <span class="comment">// 无论是否发生错误，finally都会执行。错误会在最近的catch块中捕获，然后终止</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;finally&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>现在有一个异步方法：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">async</span> = <span class="keyword">function</span> (<span class="params">callback</span>) &#123;</span><br><span class="line">  process.<span class="title function_">nextTick</span>(callback);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="title function_">async</span>(callback);</span><br><span class="line">&#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(e);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里调用async()方法只能捕获async方法的异常，但是回调callback的异常却无法捕获</p>]]>
    </summary>
    <title>js基础之异步编程解决方案</title>
    <updated>2026-05-15T08:53:54.865Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="个人笔记" scheme="https://github.com/wz71014q/categories/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <category term="个人笔记" scheme="https://github.com/wz71014q/tags/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<p>多线程语言容易发生的一个问题是线程之间状态不同步。比如有两个线程A和B，A线程要对一个资源进行删除操作，B线程要对一个资源进行修改操作，这时候两个线程容易发生冲突。JS是单线程语言，貌似不会发送这种情况，但是真的如此吗？请看下面这个情况，这是在处理公司业务时碰到的，具体代码无法透漏：<br><img src="https://raw.githubusercontent.com/wz71014q/img/master/lock/lock.png" alt="lock"><br>本地状态需要跟服务器状态同步，并且采取轮询策略。同时本地又是可以一直操作的，所以如果网络延迟就会产生图中的情况，本地数据会一直跳变。<br>这时可以借用多线程之间互斥锁的思想，将过程简化如下：<br><img src="https://raw.githubusercontent.com/wz71014q/img/master/lock/simplify.png" alt="lock"><br>简化之后，就发现本地操作和服务端操作两个线程要对UI这个资源同时进行操作，所以需要加个互斥锁，本地操作时禁止服务端进行操作，本地操作完成并且收到服务端的回调才解锁。</p><p>总结：多线程事件如框架组件内部数据更新、git协作开发等合作事件，最重要的是找出共享资源，理清楚操作方和他们之间的关系，再进行工作协调，保证每次只有一方在操作共享资源，保证所有状态同步。</p>]]>
    </content>
    <id>https://github.com/wz71014q/2019/05/23/%E4%BA%92%E6%96%A5%E9%94%81/</id>
    <link href="https://github.com/wz71014q/2019/05/23/%E4%BA%92%E6%96%A5%E9%94%81/"/>
    <published>2019-05-23T12:00:00.000Z</published>
    <summary>
      <![CDATA[<p>多线程语言容易发生的一个问题是线程之间状态不同步。比如有两个线程A和B，A线程要对一个资源进行删除操作，B线程要对一个资源进行修改操作，这时候两个线程容易发生冲突。JS是单线程语言，貌似不会发送这种情况，但是真的如此吗？请看下面这个情况，这是在处理公司业务时碰到的，具体代码]]>
    </summary>
    <title>互斥锁</title>
    <updated>2026-05-15T08:53:54.866Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="个人笔记" scheme="https://github.com/wz71014q/categories/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    <category term="算法" scheme="https://github.com/wz71014q/categories/%E7%AE%97%E6%B3%95/"/>
    <category term="二叉树" scheme="https://github.com/wz71014q/categories/%E4%BA%8C%E5%8F%89%E6%A0%91/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <category term="个人笔记" scheme="https://github.com/wz71014q/tags/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    <category term="算法" scheme="https://github.com/wz71014q/tags/%E7%AE%97%E6%B3%95/"/>
    <category term="二叉树" scheme="https://github.com/wz71014q/tags/%E4%BA%8C%E5%8F%89%E6%A0%91/"/>
    <content>
      <![CDATA[<p>二叉树是最常见的数据结构之一，很多复杂问题（二叉搜索树、堆、Trie）都是在二叉树基础上扩展的。本文总结二叉树的核心操作和常见题型。</p><h2 id="二叉树的遍历"><a href="#二叉树的遍历" class="headerlink" title="二叉树的遍历"></a>二叉树的遍历</h2><p>&emsp;&emsp;遍历是二叉树最基本的操作，分为深度优先（DFS）和广度优先（BFS）两大类。</p><h3 id="前序遍历（Preorder）：根-→-左-→-右"><a href="#前序遍历（Preorder）：根-→-左-→-右" class="headerlink" title="前序遍历（Preorder）：根 → 左 → 右"></a>前序遍历（Preorder）：根 → 左 → 右</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">preorder</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span> [];</span><br><span class="line">  <span class="keyword">return</span> [root.<span class="property">val</span>, ...<span class="title function_">preorder</span>(root.<span class="property">left</span>), ...<span class="title function_">preorder</span>(root.<span class="property">right</span>)];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="中序遍历（Inorder）：左-→-根-→-右"><a href="#中序遍历（Inorder）：左-→-根-→-右" class="headerlink" title="中序遍历（Inorder）：左 → 根 → 右"></a>中序遍历（Inorder）：左 → 根 → 右</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">inorder</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span> [];</span><br><span class="line">  <span class="keyword">return</span> [...<span class="title function_">inorder</span>(root.<span class="property">left</span>), root.<span class="property">val</span>, ...<span class="title function_">inorder</span>(root.<span class="property">right</span>)];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><span id="more"></span><p>&emsp;&emsp;中序遍历二叉搜索树（BST）会得到一个有序序列，这是一个很重要的性质。</p><h3 id="后序遍历（Postorder）：左-→-右-→-根"><a href="#后序遍历（Postorder）：左-→-右-→-根" class="headerlink" title="后序遍历（Postorder）：左 → 右 → 根"></a>后序遍历（Postorder）：左 → 右 → 根</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">postorder</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span> [];</span><br><span class="line">  <span class="keyword">return</span> [...<span class="title function_">postorder</span>(root.<span class="property">left</span>), ...<span class="title function_">postorder</span>(root.<span class="property">right</span>), root.<span class="property">val</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="层序遍历（Level-Order）：逐层从左到右"><a href="#层序遍历（Level-Order）：逐层从左到右" class="headerlink" title="层序遍历（Level Order）：逐层从左到右"></a>层序遍历（Level Order）：逐层从左到右</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">levelOrder</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span> [];</span><br><span class="line">  <span class="keyword">const</span> result = [];</span><br><span class="line">  <span class="keyword">const</span> queue = [root];</span><br><span class="line">  <span class="keyword">while</span> (queue.<span class="property">length</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> len = queue.<span class="property">length</span>;</span><br><span class="line">    <span class="keyword">const</span> level = [];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; len; i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> node = queue.<span class="title function_">shift</span>();</span><br><span class="line">      level.<span class="title function_">push</span>(node.<span class="property">val</span>);</span><br><span class="line">      <span class="keyword">if</span> (node.<span class="property">left</span>) queue.<span class="title function_">push</span>(node.<span class="property">left</span>);</span><br><span class="line">      <span class="keyword">if</span> (node.<span class="property">right</span>) queue.<span class="title function_">push</span>(node.<span class="property">right</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    result.<span class="title function_">push</span>(level);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="DFS（Depth-First-Search）"><a href="#DFS（Depth-First-Search）" class="headerlink" title="DFS（Depth-First Search）"></a>DFS（Depth-First Search）</h2><p>&emsp;&emsp;DFS 指的是「一路走到头再回头」的搜索方式，二叉树的前序、中序、后序遍历都是 DFS 的具体形态。区别在于访问根节点的时机。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DFS 通用模板（递归）</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">dfs</span>(<span class="params">node</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!node) <span class="keyword">return</span>;</span><br><span class="line">  <span class="comment">// 前序位置</span></span><br><span class="line">  <span class="title function_">dfs</span>(node.<span class="property">left</span>);</span><br><span class="line">  <span class="comment">// 中序位置</span></span><br><span class="line">  <span class="title function_">dfs</span>(node.<span class="property">right</span>);</span><br><span class="line">  <span class="comment">// 后序位置</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;DFS 也可以写成非递归（用栈模拟），核心思想是手动维护一个栈来模拟系统调用栈：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 前序非递归：根 → 左 → 右</span></span><br><span class="line"><span class="comment">// 每弹出一个节点就访问，然后先压右子节点再压左子节点</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">preorderIterative</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span> [];</span><br><span class="line">  <span class="keyword">const</span> stack = [root];</span><br><span class="line">  <span class="keyword">const</span> result = [];</span><br><span class="line">  <span class="keyword">while</span> (stack.<span class="property">length</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> node = stack.<span class="title function_">pop</span>();</span><br><span class="line">    result.<span class="title function_">push</span>(node.<span class="property">val</span>);</span><br><span class="line">    <span class="keyword">if</span> (node.<span class="property">right</span>) stack.<span class="title function_">push</span>(node.<span class="property">right</span>);</span><br><span class="line">    <span class="keyword">if</span> (node.<span class="property">left</span>) stack.<span class="title function_">push</span>(node.<span class="property">left</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 中序非递归：左 → 根 → 右</span></span><br><span class="line"><span class="comment">// 先一路把左子节点压栈，到底后弹出访问，再处理右子树</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">inorderIterative</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> stack = [];</span><br><span class="line">  <span class="keyword">const</span> result = [];</span><br><span class="line">  <span class="keyword">let</span> node = root;</span><br><span class="line">  <span class="keyword">while</span> (stack.<span class="property">length</span> || node) &#123;</span><br><span class="line">    <span class="comment">// 一路向左压栈</span></span><br><span class="line">    <span class="keyword">while</span> (node) &#123;</span><br><span class="line">      stack.<span class="title function_">push</span>(node);</span><br><span class="line">      node = node.<span class="property">left</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 弹出访问</span></span><br><span class="line">    node = stack.<span class="title function_">pop</span>();</span><br><span class="line">    result.<span class="title function_">push</span>(node.<span class="property">val</span>);</span><br><span class="line">    <span class="comment">// 转向右子树</span></span><br><span class="line">    node = node.<span class="property">right</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 后序非递归：左 → 右 → 根</span></span><br><span class="line"><span class="comment">// 技巧：按 根 → 右 → 左 遍历后反转结果 = 左 → 右 → 根</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">postorderIterative</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span> [];</span><br><span class="line">  <span class="keyword">const</span> stack = [root];</span><br><span class="line">  <span class="keyword">const</span> result = [];</span><br><span class="line">  <span class="keyword">while</span> (stack.<span class="property">length</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> node = stack.<span class="title function_">pop</span>();</span><br><span class="line">    result.<span class="title function_">push</span>(node.<span class="property">val</span>);</span><br><span class="line">    <span class="keyword">if</span> (node.<span class="property">left</span>) stack.<span class="title function_">push</span>(node.<span class="property">left</span>);</span><br><span class="line">    <span class="keyword">if</span> (node.<span class="property">right</span>) stack.<span class="title function_">push</span>(node.<span class="property">right</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 反转得到「左 → 右 → 根」</span></span><br><span class="line">  <span class="keyword">return</span> result.<span class="title function_">reverse</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;三种非递归遍历总结：</p><table><thead><tr><th>遍历方式</th><th>栈用法</th><th>核心思路</th></tr></thead><tbody><tr><td>前序</td><td>根压栈 → 弹出访问 → 右左压栈</td><td>右先入栈，左后出</td></tr><tr><td>中序</td><td>一路左压栈 → 弹出 → 转向右</td><td>用 <code>while</code> 走到底再回头</td></tr><tr><td>后序</td><td>根压栈 → 弹出记录 → 左右压栈 → 反转</td><td>前序变体 + reverse</td></tr></tbody></table><h2 id="BFS（Breadth-First-Search）"><a href="#BFS（Breadth-First-Search）" class="headerlink" title="BFS（Breadth-First Search）"></a>BFS（Breadth-First Search）</h2><p>&emsp;&emsp;BFS 是「一层一层扫」的搜索方式，对应二叉树的层序遍历。BFS 通常用队列实现。</p><p>&emsp;&emsp;BFS 有两个常见变体：</p><ul><li><strong>逐层输出</strong>：上面 <code>levelOrder</code> 的例子，每一层一个数组</li><li><strong>求最短路径</strong>：BFS 首次到达目标节点时走过的路径一定最短</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// BFS 求二叉树最小深度</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">minDepth</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">const</span> queue = [[root, <span class="number">1</span>]];</span><br><span class="line">  <span class="keyword">while</span> (queue.<span class="property">length</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> [node, depth] = queue.<span class="title function_">shift</span>();</span><br><span class="line">    <span class="keyword">if</span> (!node.<span class="property">left</span> &amp;&amp; !node.<span class="property">right</span>) <span class="keyword">return</span> depth;</span><br><span class="line">    <span class="keyword">if</span> (node.<span class="property">left</span>) queue.<span class="title function_">push</span>([node.<span class="property">left</span>, depth + <span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">if</span> (node.<span class="property">right</span>) queue.<span class="title function_">push</span>([node.<span class="property">right</span>, depth + <span class="number">1</span>]);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="序列化与反序列化"><a href="#序列化与反序列化" class="headerlink" title="序列化与反序列化"></a>序列化与反序列化</h2><p>&emsp;&emsp;序列化是把二叉树转换成字符串（或数组），反序列化是把字符串还原回二叉树。主要用于数据传输和缓存。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 前序序列化</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">serialize</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span> <span class="string">&#x27;#&#x27;</span>;</span><br><span class="line">  <span class="keyword">return</span> root.<span class="property">val</span> + <span class="string">&#x27;,&#x27;</span> + <span class="title function_">serialize</span>(root.<span class="property">left</span>) + <span class="string">&#x27;,&#x27;</span> + <span class="title function_">serialize</span>(root.<span class="property">right</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 前序反序列化</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">deserialize</span>(<span class="params">str</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> list = str.<span class="title function_">split</span>(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">build</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> val = list.<span class="title function_">shift</span>();</span><br><span class="line">    <span class="keyword">if</span> (val === <span class="string">&#x27;#&#x27;</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">const</span> node = <span class="keyword">new</span> <span class="title class_">TreeNode</span>(<span class="title class_">Number</span>(val));</span><br><span class="line">    node.<span class="property">left</span> = <span class="title function_">build</span>();</span><br><span class="line">    node.<span class="property">right</span> = <span class="title function_">build</span>();</span><br><span class="line">    <span class="keyword">return</span> node;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">build</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;也可以用层序遍历做序列化，结果更直观：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 层序序列化</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">serializeLevel</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span> <span class="string">&#x27;[]&#x27;</span>;</span><br><span class="line">  <span class="keyword">const</span> queue = [root];</span><br><span class="line">  <span class="keyword">const</span> result = [];</span><br><span class="line">  <span class="keyword">while</span> (queue.<span class="property">length</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> node = queue.<span class="title function_">shift</span>();</span><br><span class="line">    <span class="keyword">if</span> (node) &#123;</span><br><span class="line">      result.<span class="title function_">push</span>(node.<span class="property">val</span>);</span><br><span class="line">      queue.<span class="title function_">push</span>(node.<span class="property">left</span>);</span><br><span class="line">      queue.<span class="title function_">push</span>(node.<span class="property">right</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      result.<span class="title function_">push</span>(<span class="string">&#x27;#&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 去掉尾部多余的 &#x27;#&#x27;</span></span><br><span class="line">  <span class="keyword">while</span> (result[result.<span class="property">length</span> - <span class="number">1</span>] === <span class="string">&#x27;#&#x27;</span>) result.<span class="title function_">pop</span>();</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(result);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="二叉树与链表的关系"><a href="#二叉树与链表的关系" class="headerlink" title="二叉树与链表的关系"></a>二叉树与链表的关系</h2><p>&emsp;&emsp;二叉树和链表有很深的联系：</p><ol><li><strong>单链表可以看作只有右子树的二叉树</strong>——每个节点只有一个 next 指针，相当于二叉树节点只有 right child</li><li><strong>二叉搜索树中序遍历得到有序链表</strong>——这常被用来「展平」二叉搜索树</li><li><strong>链表 + 随机指针就是二叉树</strong>——每个节点有两个额外指针（left&#x2F;right），就是二叉树结构</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 二叉树展平成链表（LeetCode 114）</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">flatten</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span>;</span><br><span class="line">  <span class="title function_">flatten</span>(root.<span class="property">left</span>);</span><br><span class="line">  <span class="title function_">flatten</span>(root.<span class="property">right</span>);</span><br><span class="line">  <span class="comment">// 后序位置</span></span><br><span class="line">  <span class="keyword">const</span> right = root.<span class="property">right</span>;</span><br><span class="line">  root.<span class="property">right</span> = root.<span class="property">left</span>;</span><br><span class="line">  root.<span class="property">left</span> = <span class="literal">null</span>;</span><br><span class="line">  <span class="keyword">let</span> node = root;</span><br><span class="line">  <span class="keyword">while</span> (node.<span class="property">right</span>) node = node.<span class="property">right</span>;</span><br><span class="line">  node.<span class="property">right</span> = right;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;这种联系在题目中经常出现，本质上都是<strong>把树结构转换成线性结构</strong>来方便处理。</p>]]>
    </content>
    <id>https://github.com/wz71014q/2019/05/23/%E7%AE%97%E6%B3%95-%E4%BA%8C%E5%8F%89%E6%A0%91%E6%80%BB%E7%BB%93/</id>
    <link href="https://github.com/wz71014q/2019/05/23/%E7%AE%97%E6%B3%95-%E4%BA%8C%E5%8F%89%E6%A0%91%E6%80%BB%E7%BB%93/"/>
    <published>2019-05-23T12:00:00.000Z</published>
    <summary>
      <![CDATA[<p>二叉树是最常见的数据结构之一，很多复杂问题（二叉搜索树、堆、Trie）都是在二叉树基础上扩展的。本文总结二叉树的核心操作和常见题型。</p>
<h2 id="二叉树的遍历"><a href="#二叉树的遍历" class="headerlink" title="二叉树的遍历"></a>二叉树的遍历</h2><p>&emsp;&emsp;遍历是二叉树最基本的操作，分为深度优先（DFS）和广度优先（BFS）两大类。</p>
<h3 id="前序遍历（Preorder）：根-→-左-→-右"><a href="#前序遍历（Preorder）：根-→-左-→-右" class="headerlink" title="前序遍历（Preorder）：根 → 左 → 右"></a>前序遍历（Preorder）：根 → 左 → 右</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">preorder</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span> [];</span><br><span class="line">  <span class="keyword">return</span> [root.<span class="property">val</span>, ...<span class="title function_">preorder</span>(root.<span class="property">left</span>), ...<span class="title function_">preorder</span>(root.<span class="property">right</span>)];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="中序遍历（Inorder）：左-→-根-→-右"><a href="#中序遍历（Inorder）：左-→-根-→-右" class="headerlink" title="中序遍历（Inorder）：左 → 根 → 右"></a>中序遍历（Inorder）：左 → 根 → 右</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">inorder</span>(<span class="params">root</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span> [];</span><br><span class="line">  <span class="keyword">return</span> [...<span class="title function_">inorder</span>(root.<span class="property">left</span>), root.<span class="property">val</span>, ...<span class="title function_">inorder</span>(root.<span class="property">right</span>)];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </summary>
    <title>算法——二叉树总结</title>
    <updated>2026-05-22T05:11:46.962Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="个人笔记" scheme="https://github.com/wz71014q/categories/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    <category term="算法" scheme="https://github.com/wz71014q/categories/%E7%AE%97%E6%B3%95/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <category term="个人笔记" scheme="https://github.com/wz71014q/tags/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    <category term="算法" scheme="https://github.com/wz71014q/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<p>动态规划（Dynamic Programming，DP）与递归常常可以一起使用。核心思想是：<strong>将大问题拆成小问题，先解决小问题，再根据小问题的解推导出大问题的解</strong>。关键是要找到状态定义和状态转移方程。</p><h2 id="递归"><a href="#递归" class="headerlink" title="递归"></a>递归</h2><p>&emsp;&emsp;递归是 DP 的基础——一个问题可以拆成若干个结构相同的子问题。比如爬楼梯：到第 n 级台阶，要么从 n-1 跨一步上来，要么从 n-2 跨两步上来。所以：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">f(n) = f(n-1) + f(n-2)</span><br></pre></td></tr></table></figure><span id="more"></span><p>&emsp;&emsp;用递归直接写：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">climb</span>(<span class="params">n</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (n &lt;= <span class="number">2</span>) <span class="keyword">return</span> n;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">climb</span>(n - <span class="number">1</span>) + <span class="title function_">climb</span>(n - <span class="number">2</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;但有大量重复计算——<code>climb(5)</code> 会计算 <code>climb(3)</code> 两次。DP 就是来消除这些重复的。</p><h2 id="初始状态（Base-Case）"><a href="#初始状态（Base-Case）" class="headerlink" title="初始状态（Base Case）"></a>初始状态（Base Case）</h2><p>&emsp;&emsp;递归需要终止条件，DP 需要初始状态。找初始状态就是问：<strong>问题最小到什么程度可以直接得出答案？</strong></p><ul><li>爬楼梯：<code>f(1) = 1</code>，<code>f(2) = 2</code></li><li>最大子序和：<code>dp[0] = nums[0]</code>，第一个元素的最大子序和就是它自己</li><li>斐波那契：<code>fib(0) = 0</code>，<code>fib(1) = 1</code></li></ul><h2 id="状态转移方程"><a href="#状态转移方程" class="headerlink" title="状态转移方程"></a>状态转移方程</h2><p>&emsp;&emsp;这是 DP 的核心——<strong>当前状态怎么从前面的状态推导过来</strong>。写好方程，代码就是照着翻译。</p><table><thead><tr><th>问题</th><th>状态定义</th><th>转移方程</th></tr></thead><tbody><tr><td>爬楼梯</td><td><code>dp[i]</code> 表示到第 i 阶的方法数</td><td><code>dp[i] = dp[i-1] + dp[i-2]</code></td></tr><tr><td>最大子序和</td><td><code>dp[i]</code> 表示以 i 结尾的最大子序和</td><td><code>dp[i] = max(nums[i], dp[i-1] + nums[i])</code></td></tr><tr><td>斐波那契</td><td><code>dp[i]</code> 表示第 i 个斐波那契数</td><td><code>dp[i] = dp[i-1] + dp[i-2]</code></td></tr></tbody></table><h3 id="爬楼梯完整实现"><a href="#爬楼梯完整实现" class="headerlink" title="爬楼梯完整实现"></a>爬楼梯完整实现</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">climbStairs</span>(<span class="params">n</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (n &lt;= <span class="number">2</span>) <span class="keyword">return</span> n;</span><br><span class="line">  <span class="keyword">const</span> dp = [<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>]; <span class="comment">// dp[1] = 1, dp[2] = 2</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">3</span>; i &lt;= n; i++) &#123;</span><br><span class="line">    dp[i] = dp[i - <span class="number">1</span>] + dp[i - <span class="number">2</span>];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> dp[n];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="最大子序和完整实现"><a href="#最大子序和完整实现" class="headerlink" title="最大子序和完整实现"></a>最大子序和完整实现</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">maxSubArray</span>(<span class="params">nums</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!nums.<span class="property">length</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">const</span> dp = [nums[<span class="number">0</span>]];</span><br><span class="line">  <span class="keyword">let</span> max = dp[<span class="number">0</span>];</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>; i &lt; nums.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    dp[i] = <span class="title class_">Math</span>.<span class="title function_">max</span>(nums[i], dp[i - <span class="number">1</span>] + nums[i]);</span><br><span class="line">    max = <span class="title class_">Math</span>.<span class="title function_">max</span>(max, dp[i]);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> max;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="状态压缩（空间优化）"><a href="#状态压缩（空间优化）" class="headerlink" title="状态压缩（空间优化）"></a>状态压缩（空间优化）</h2><p>&emsp;&emsp;很多 DP 问题中，当前状态只依赖前几个状态，不需要保留整个 <code>dp</code> 数组，用几个变量滚动更新即可。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 爬楼梯空间优化：O(n) → O(1)</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">climbStairs</span>(<span class="params">n</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (n &lt;= <span class="number">2</span>) <span class="keyword">return</span> n;</span><br><span class="line">  <span class="keyword">let</span> prev = <span class="number">1</span>, curr = <span class="number">2</span>;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">3</span>; i &lt;= n; i++) &#123;</span><br><span class="line">    [prev, curr] = [curr, prev + curr];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> curr;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 最大子序和空间优化</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">maxSubArray</span>(<span class="params">nums</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!nums.<span class="property">length</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">let</span> prev = nums[<span class="number">0</span>], max = prev;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>; i &lt; nums.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    prev = <span class="title class_">Math</span>.<span class="title function_">max</span>(nums[i], prev + nums[i]);</span><br><span class="line">    max = <span class="title class_">Math</span>.<span class="title function_">max</span>(max, prev);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> max;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最优解"><a href="#最优解" class="headerlink" title="最优解"></a>最优解</h2><p>&emsp;&emsp;DP 求的是<strong>最优子结构</strong>——如果子问题是最优的，那么组合出来的父问题也是最优的。区分 DP 和贪心：</p><ul><li><strong>贪心</strong>：每一步选当前最优，不考虑未来</li><li><strong>DP</strong>：枚举所有可能的状态，通过状态转移方程找到全局最优</li></ul><p>&emsp;&emsp;比如爬楼梯求的是「方法数」，不是最优值，严格来说属于计数型 DP。求最优值的典型问题是最大子序和。</p><h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><h3 id="爬楼梯"><a href="#爬楼梯" class="headerlink" title="爬楼梯"></a>爬楼梯</h3><blockquote><p>假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶？</p></blockquote><p><strong>思路</strong>：到第 n 阶 &#x3D; 到第 n-1 阶再跨 1 步 + 到第 n-2 阶再跨 2 步，即斐波那契数列。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">climbStairs</span>(<span class="params">n</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (n &lt;= <span class="number">2</span>) <span class="keyword">return</span> n;</span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">1</span>, b = <span class="number">2</span>;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">3</span>; i &lt;= n; i++) &#123;</span><br><span class="line">    [a, b] = [b, a + b];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="最大子序和"><a href="#最大子序和" class="headerlink" title="最大子序和"></a>最大子序和</h3><blockquote><p>给定一个整数数组 nums ，找到一个具有最大和的连续子数组（子数组最少包含一个元素），返回其最大和。</p></blockquote><p><strong>思路</strong>：对于每个位置 i，要么自己单独成子数组，要么加入前面的子数组。取较大的那个。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">maxSubArray</span>(<span class="params">nums</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!nums.<span class="property">length</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">let</span> prev = nums[<span class="number">0</span>];</span><br><span class="line">  <span class="keyword">let</span> max = prev;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>; i &lt; nums.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    prev = <span class="title class_">Math</span>.<span class="title function_">max</span>(nums[i], prev + nums[i]);</span><br><span class="line">    max = <span class="title class_">Math</span>.<span class="title function_">max</span>(max, prev);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> max;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://github.com/wz71014q/2019/05/23/%E7%AE%97%E6%B3%95-%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/</id>
    <link href="https://github.com/wz71014q/2019/05/23/%E7%AE%97%E6%B3%95-%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
    <published>2019-05-23T12:00:00.000Z</published>
    <summary>
      <![CDATA[<p>动态规划（Dynamic Programming，DP）与递归常常可以一起使用。核心思想是：<strong>将大问题拆成小问题，先解决小问题，再根据小问题的解推导出大问题的解</strong>。关键是要找到状态定义和状态转移方程。</p>
<h2 id="递归"><a href="#递归" class="headerlink" title="递归"></a>递归</h2><p>&emsp;&emsp;递归是 DP 的基础——一个问题可以拆成若干个结构相同的子问题。比如爬楼梯：到第 n 级台阶，要么从 n-1 跨一步上来，要么从 n-2 跨两步上来。所以：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">f(n) = f(n-1) + f(n-2)</span><br></pre></td></tr></table></figure>]]>
    </summary>
    <title>算法——动态规划</title>
    <updated>2026-05-22T05:08:43.380Z</updated>
  </entry>
  <entry>
    <author>
      <name>Qiang</name>
    </author>
    <category term="JS" scheme="https://github.com/wz71014q/categories/JS/"/>
    <category term="JS" scheme="https://github.com/wz71014q/tags/JS/"/>
    <content>
      <![CDATA[<p>《JS高级程序设计》中说JS的继承有原型链、构造函数、组合式、原型式、寄生式、寄生组合式继承，加上ES6的class继承，一共有7种方式。前6种最关键的是搞清楚构造函数、原型对象、原型链的关系，其他模式都是这些的组合。</p><h2 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h2><p>构造函数也是函数，跟普通函数的没什么区别，但是可以用new()方法创建实例</p><p>e.g.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Animal</span>(<span class="params">name, legCount</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">legCount</span> = legCount;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> cat = <span class="keyword">new</span> <span class="title class_">Animal</span>(<span class="string">&#x27;Tom&#x27;</span>, <span class="number">4</span>);</span><br></pre></td></tr></table></figure><h2 id="原型对象"><a href="#原型对象" class="headerlink" title="原型对象"></a>原型对象</h2><p>每个函数都有一个prototype属性（对象没有这个属性），指向一个原型对象，即Animal.prototype( 这玩意是个对象，名字就叫Animal.prototye )</p><p>Animal.prototype默认有个constructor属性，这个对象的constructor属性指向该对象对应的构造函数，即Animal()。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Animal</span>.<span class="property"><span class="keyword">prototype</span></span>);</span><br><span class="line"><span class="comment">// 结果：</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">constructor</span>: ƒ <span class="title class_">Animal</span>(name ,leg),</span><br><span class="line">  <span class="attr">__proto__</span>: <span class="title class_">Object</span>  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><span id="more"></span><p>注意：Animal()函数内部定义了name、age属性，但是Animal.prototype对象是没有这些属性的，只有默认的constructor属性，除非在原型上定义其他属性: </p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Animal</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">speak</span> = <span class="string">&#x27;miao&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Animal</span>.<span class="property"><span class="keyword">prototype</span></span>);</span><br><span class="line"><span class="comment">// 结果：</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">constructor</span>: ƒ <span class="title class_">Animal</span>(name ,leg),</span><br><span class="line">  <span class="attr">speak</span>: <span class="string">&#x27;miao&#x27;</span>,</span><br><span class="line">  <span class="attr">__proto__</span>: <span class="title class_">Object</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这时创建一个Animal实例：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> dog = <span class="keyword">new</span> <span class="title class_">Animal</span>(<span class="string">&#x27;light&#x27;</span>, <span class="number">4</span>);</span><br></pre></td></tr></table></figure><p>dog对象有一个[[prototype]]属性，指向该实例对象的原型对象，[[prototype]]可以用__proto__访问，即dog.__proto__ &#x3D; Animal.prototype</p><h2 id="class"><a href="#class" class="headerlink" title="class"></a>class</h2><p>没有class之前，生成实例都是用构造函数, es6中引入了class概念，可以像C++等直接用class关键字来定义类<br>e.g.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Animal</span> &#123;</span><br><span class="line">  species = <span class="string">&#x27;bird&#x27;</span>; <span class="comment">// 这跟定义在this上(写在constructor内)是一样的效果</span></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = name; <span class="comment">//  类内部的this指向该类的实例</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">Height</span> = <span class="number">100</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">speak</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">name</span> + <span class="string">&#x27;, speak&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">static</span> <span class="title function_">voice</span>(<span class="params"></span>) &#123; <span class="comment">// Animal类自身的静态方法</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;this is my voice&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">get</span> <span class="title function_">height</span>() &#123; <span class="comment">// getter方法</span></span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">Height</span> * <span class="number">3</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">set</span> <span class="title function_">height</span>(<span class="params">val</span>) &#123; <span class="comment">// setter方法</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">Height</span> += val;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> cat = <span class="keyword">new</span> <span class="title class_">Animal</span>(<span class="string">&#x27;Tom&#x27;</span>, <span class="number">18</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(cat, cat.<span class="title function_">speak</span>(), <span class="title class_">Animal</span>.<span class="title function_">voice</span>());</span><br><span class="line"><span class="comment">// cat是: </span></span><br><span class="line"><span class="title class_">Animal</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;Tom&#x27;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">18</span>,</span><br><span class="line">  <span class="title class_">Height</span>: <span class="number">100</span></span><br><span class="line">&#125;</span><br><span class="line">cat.<span class="property">__proto__</span>= &#123;</span><br><span class="line">  <span class="attr">constructor</span>:  f <span class="title class_">Aniaml</span>(name, age),</span><br><span class="line">  <span class="attr">speak</span>: f <span class="title function_">speak</span>(<span class="params"></span>) &#123;&#125;,</span><br><span class="line">  <span class="attr">height</span>: <span class="number">300</span>,</span><br><span class="line">  get <span class="attr">height</span>: <span class="title function_">f</span>(<span class="params"></span>) &#123;&#125;,</span><br><span class="line">  set <span class="attr">height</span>: <span class="title function_">f</span>(<span class="params">val</span>) &#123;&#125;,</span><br><span class="line">  <span class="attr">__proto__</span>: <span class="title class_">Object</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="构造函数式继承"><a href="#构造函数式继承" class="headerlink" title="构造函数式继承"></a>构造函数式继承</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">hand</span> = <span class="string">&#x27;hand&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Child</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">  <span class="title class_">Person</span>.<span class="title function_">call</span>(<span class="variable language_">this</span>, name, age);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> tom = <span class="keyword">new</span> <span class="title class_">Child</span>(<span class="string">&#x27;Tom&#x27;</span>, <span class="number">10</span>);</span><br><span class="line"><span class="comment">// tom是Child的实例，但是“借用了”Person的构造函数，生成的实例就包含了Person的属性</span></span><br><span class="line">&#123;</span><br><span class="line">  name = <span class="string">&#x27;Tom&#x27;</span>;</span><br><span class="line">  age = <span class="number">10</span>;</span><br><span class="line">  hand = <span class="string">&#x27;hand&#x27;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="原型链式继承"><a href="#原型链式继承" class="headerlink" title="原型链式继承"></a>原型链式继承</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Animal</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">action</span> = <span class="string">&#x27;move&#x27;</span>;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">speak</span> = <span class="string">&#x27;speak&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Animal</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">hasLeg</span> = <span class="string">&#x27;true&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Cat</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = <span class="string">&#x27;cat&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Cat</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="keyword">new</span> <span class="title class_">Animal</span>();</span><br><span class="line"><span class="title class_">Cat</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">height</span> = <span class="number">50</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> tom = <span class="keyword">new</span> <span class="title class_">Cat</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// tom</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;cat&#x27;</span>,</span><br><span class="line">  <span class="attr">__proto__</span>: <span class="title class_">Animal</span> <span class="comment">// 这里的Animal不是Animal构造函数，而是Cat的原型对象。因为Cat.prototype = new Animal();</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// tom.__proto__指向tom的原型对象，就是Cat.prototype</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">action</span>: <span class="string">&quot;move&quot;</span>,</span><br><span class="line">  <span class="attr">height</span>: <span class="number">50</span>,</span><br><span class="line">  <span class="attr">speak</span>: <span class="string">&quot;speak&quot;</span>,</span><br><span class="line">  <span class="attr">__proto__</span>: <span class="title class_">Object</span> <span class="comment">// 这里的Object是Animal.prototype</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// tom.__proto__.__proto__指向tom原型对象的原型对象，就是Animal.prototype</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">hasleg</span>: <span class="string">&#x27;true&#x27;</span>,</span><br><span class="line">  <span class="attr">constructor</span>: ƒ <span class="title class_">Animal</span>(),</span><br><span class="line">  <span class="attr">__proto__</span>: <span class="title class_">Object</span> <span class="comment">// 这里的Object是原型Object</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="class继承"><a href="#class继承" class="headerlink" title="class继承"></a>class继承</h2><p>class的继承有专门的语法</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Animal</span> &#123;</span><br><span class="line">  speak = <span class="string">&#x27;bar&#x27;</span>;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = <span class="string">&#x27;Animal&#x27;</span>; <span class="comment">//  类内部的this指向该类的实例</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">age</span> = <span class="number">15</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">Height</span> = <span class="number">100</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Dog</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Animal</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">...args</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(...args); <span class="comment">// super(...args)相当于Animal.prototype.constructor.call(this, ...args)</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">speak</span> = <span class="string">&#x27;bar&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">dogSpeak</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">super</span>.<span class="title function_">speak</span>(); <span class="comment">// 调用父类的speak()方法，相当于dogSpeak() = Animal.prototype.speak()，并且内部的this指向当前调用的实例</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(dog);</span><br><span class="line"><span class="comment">// Dog &#123;speak: &quot;bar&quot;, name: &quot;Animal&quot;, age: 15, Height: 100&#125;</span></span><br><span class="line"><span class="comment">//    Height: 100</span></span><br><span class="line"><span class="comment">//    age: 15</span></span><br><span class="line"><span class="comment">//    name: &quot;Animal&quot;</span></span><br><span class="line"><span class="comment">//    speak: &quot;bar&quot;</span></span><br><span class="line"><span class="comment">//    __proto__: Animal</span></span><br></pre></td></tr></table></figure><p>class的继承是将父类实例对象的属性和方法，加到this上面（所以必须先调用super方法），然后再用子类的构造函数修改this。也就是只有先调用super，才能够使用this.<br>注意：super<br>        当方法super()使用时，会继承父类的构造函数，只能在子类的构造函数中使用；<br>        当对象super.XXX使用时，指向父类的原型对象Animal.prototype;<br>        如果super作为对象，用在静态方法之中，这时super将指向父类;<br>        在子类的静态方法中通过super调用父类的方法时，方法内部的this指向当前的子类，而不是子类的实例</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="http://es6.ruanyifeng.com/">《ECMAScript 6入门》– 阮一峰</a></p>]]>
    </content>
    <id>https://github.com/wz71014q/2019/05/09/js%E5%9F%BA%E7%A1%80%E4%B9%8B%E7%BB%A7%E6%89%BF/</id>
    <link href="https://github.com/wz71014q/2019/05/09/js%E5%9F%BA%E7%A1%80%E4%B9%8B%E7%BB%A7%E6%89%BF/"/>
    <published>2019-05-09T12:00:00.000Z</published>
    <summary>
      <![CDATA[<p>《JS高级程序设计》中说JS的继承有原型链、构造函数、组合式、原型式、寄生式、寄生组合式继承，加上ES6的class继承，一共有7种方式。前6种最关键的是搞清楚构造函数、原型对象、原型链的关系，其他模式都是这些的组合。</p>
<h2 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h2><p>构造函数也是函数，跟普通函数的没什么区别，但是可以用new()方法创建实例</p>
<p>e.g.</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Animal</span>(<span class="params">name, legCount</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">legCount</span> = legCount;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> cat = <span class="keyword">new</span> <span class="title class_">Animal</span>(<span class="string">&#x27;Tom&#x27;</span>, <span class="number">4</span>);</span><br></pre></td></tr></table></figure>

<h2 id="原型对象"><a href="#原型对象" class="headerlink" title="原型对象"></a>原型对象</h2><p>每个函数都有一个prototype属性（对象没有这个属性），指向一个原型对象，即Animal.prototype( 这玩意是个对象，名字就叫Animal.prototye )</p>
<p>Animal.prototype默认有个constructor属性，这个对象的constructor属性指向该对象对应的构造函数，即Animal()。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Animal</span>.<span class="property"><span class="keyword">prototype</span></span>);</span><br><span class="line"><span class="comment">// 结果：</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">constructor</span>: ƒ <span class="title class_">Animal</span>(name ,leg),</span><br><span class="line">  <span class="attr">__proto__</span>: <span class="title class_">Object</span>  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </summary>
    <title>js基础之继承</title>
    <updated>2026-05-15T08:53:54.866Z</updated>
  </entry>
</feed>
