<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/feed.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Yukiの小窝</title><description>生活或许有不如意，但温柔与善意能治愈一切。&lt;br/&gt;愿你被世界温柔以待，也能做温暖别人的小太阳。&lt;br/&gt;——《生如冬花的你》</description><link>https://yuki-bloom.vercel.app</link><language>ja</language><item><title>第 5 期：工作三个月，离职休息与秋招准备</title><link>https://yuki-bloom.vercel.app/ja/post/weekly-2026-06-07</link><guid isPermaLink="false">ja:weekly-2026-06-07</guid><description>本周状态低迷决定离职，分享三个月实习的感悟与接下来的秋招、旅游计划。</description><pubDate>Sun, 07 Jun 2026 04:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;情绪与状态：疲惫与决定&lt;a href=&quot;#情绪与状态疲惫与决定&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这周开始感觉就没有什么精神，什么也不想做，感觉是累到了，而且北京不知道为什么天气好干。&lt;/p&gt;
&lt;p&gt;下定决心离职好好休息了。不知不觉也已经实习三个月了，这段时间主要都在做业务方面的需求，感觉也好无聊，学习不到新的东西。而且刚入职第一个月就把我调整到这个组，做的还是之前组的内容，好边缘，感觉迟早要完。之前部门一下从 8 个人裁掉 3 个，太恐怖了。&lt;/p&gt;
&lt;p&gt;不过自我感知，真不知道什么用户在 ks 买东西…可能层级不一样吧。很难想象。&lt;/p&gt;
&lt;h2&gt;接下来的计划：休息、旅游与秋招&lt;a href=&quot;#接下来的计划休息旅游与秋招&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;下周离职回去好好休息，然后准备秋招 + 出去旅游玩 w。打工赚的钱终于可以让自己好好放松了。&lt;/p&gt;
&lt;p&gt;接下来的重点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;旅游放松&lt;/strong&gt;：用自己赚的钱好好放松一下。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完善项目&lt;/strong&gt;：回去完善自己的新项目。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新旧项目&lt;/strong&gt;：update 自己的老项目。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;面试与八股复习&lt;a href=&quot;#面试与八股复习&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;本周还面试了两场，自己八股都快忘完了。不过面试官都问八股，感觉这个组也不怎么样。看着别人都是全程问 AI 之类的，怎么到我这里就全是八股轰炸。&lt;/p&gt;
&lt;p&gt;不过自己算法能力还是不太行，还是需要加强。其他的计划就是再复习一下八股。&lt;/p&gt;
&lt;p&gt;离职跑路 w&lt;/p&gt;</content:encoded><category>category:周刊</category><category>tag:实习日记</category><category>tag:离职</category><category>tag:秋招</category><category>tag:面试</category></item><item><title>携程集团面经</title><link>https://yuki-bloom.vercel.app/ja/post/xie-cheng-ji-tuan-mian-jing-2</link><guid isPermaLink="false">ja:xie-cheng-ji-tuan-mian-jing-2</guid><description>携程前端面经。重点复盘：React Hooks 与 forwardRef 场景、CJS 与 ESM 的变量导出机制（值拷贝与动态引用）、Flex 布局核心（flex:1 vs auto）、CSS Animation 完全指南。</description><pubDate>Thu, 04 Jun 2026 06:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;携程集团&lt;a href=&quot;#携程集团&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;面试时间&lt;a href=&quot;#面试时间&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;2026_0604-14:00&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;面试内容&lt;a href=&quot;#面试内容&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;介绍你的项目，包括使用方和使用场景是什么？&lt;/li&gt;
&lt;li&gt;是否仍在快手实习？&lt;/li&gt;
&lt;li&gt;询问跨域 iframe 实时预览功能。&lt;/li&gt;
&lt;li&gt;解释一下 React Hooks&lt;/li&gt;
&lt;li&gt;是否了解 React 的 forwardRef？&lt;/li&gt;
&lt;li&gt;事件循环&lt;/li&gt;
&lt;li&gt;事件循环输出题&lt;/li&gt;
&lt;li&gt;解释 HTTP 缓存机制&lt;/li&gt;
&lt;li&gt;协商缓存 ETag 和 Last-Modified 的区别。&lt;/li&gt;
&lt;li&gt;请说明 CommonJS 和 ES6 Module 在变量作用域上的主要区别。&lt;/li&gt;
&lt;li&gt;请解释 Flex 布局的基本概念&lt;/li&gt;
&lt;li&gt;说明 flex: 1 是哪些属性的简写？&lt;/li&gt;
&lt;li&gt;flex: auto 是哪些属性的简写？&lt;/li&gt;
&lt;li&gt;是否了解 openSpec&lt;/li&gt;
&lt;li&gt;页面性能优化&lt;/li&gt;
&lt;li&gt;怎么实现图片懒加载&lt;/li&gt;
&lt;li&gt;css 动画&lt;/li&gt;
&lt;li&gt;animation 的具体使用&lt;/li&gt;
&lt;li&gt;手撕：leetcode &lt;a href=&quot;https://leetcode.cn/problems/longest-consecutive-sequence/description/&quot;&gt;128. 最长连续序列&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI 相关内容的讨论&lt;/li&gt;
&lt;li&gt;业务方面（旅游门票）&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;面试复盘总结（核心问题精讲）&lt;a href=&quot;#面试复盘总结核心问题精讲&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1) React Hooks 与 &lt;code&gt;forwardRef&lt;/code&gt;（Q4, Q5）&lt;a href=&quot;#1-react-hooks-与-forwardrefq4-q5&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4&gt;解释 React Hooks&lt;a href=&quot;#解释-react-hooks&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Hooks 出现的核心目的是解决&lt;strong&gt;函数组件没有状态（State）和生命周期&lt;/strong&gt;的问题。
它让函数组件能够“钩入” React 的状态管理和副作用处理机制（如 &lt;code&gt;useState&lt;/code&gt;、&lt;code&gt;useEffect&lt;/code&gt;），同时解决了类组件（Class Component）中 &lt;code&gt;this&lt;/code&gt; 指向混乱、生命周期逻辑割裂（同一个业务逻辑被拆分到 &lt;code&gt;DidMount&lt;/code&gt; 和 &lt;code&gt;DidUpdate&lt;/code&gt; 中）以及逻辑复用困难（只能靠高阶组件 HOC 或 Render Props 导致嵌套地狱）的问题。&lt;/p&gt;
&lt;h4&gt;为什么需要 &lt;code&gt;forwardRef&lt;/code&gt;？&lt;a href=&quot;#为什么需要-forwardref&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;痛点&lt;/strong&gt;：在 React 中，&lt;code&gt;ref&lt;/code&gt; 不能作为普通的 &lt;code&gt;props&lt;/code&gt; 往下传。如果你给一个自定义函数组件传 &lt;code&gt;ref&lt;/code&gt;，React 会直接报错，因为函数组件没有实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用与代码演示&lt;/strong&gt;：&lt;code&gt;forwardRef&lt;/code&gt; 用来包裹函数组件，把父组件传进来的 &lt;code&gt;ref&lt;/code&gt; 拦截下来，传给内部节点。最佳实践是配合 &lt;code&gt;useImperativeHandle&lt;/code&gt;，只暴露特定方法给父组件，而不是暴露整个 DOM（避免破坏封装）。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { forwardRef, useImperativeHandle, useRef } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 子组件：被 forwardRef 包裹，拦截 ref 并自定义暴露的方法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; MyInput&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; forwardRef&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; inputRef&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useRef&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 只暴露 focus 和 clear 方法给父组件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  useImperativeHandle&lt;/span&gt;&lt;span&gt;(ref, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    focus&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      inputRef.current.&lt;/span&gt;&lt;span&gt;focus&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    clear&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      inputRef.current.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; ref&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{inputRef} &lt;/span&gt;&lt;span&gt;placeholder&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;请输入...&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 父组件：可以直接调用子组件暴露的方法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Parent&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; myRef&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useRef&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;MyInput&lt;/span&gt;&lt;span&gt; ref&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{myRef} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; myRef.current.&lt;/span&gt;&lt;span&gt;focus&lt;/span&gt;&lt;span&gt;()}&amp;gt;聚焦&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; myRef.current.&lt;/span&gt;&lt;span&gt;clear&lt;/span&gt;&lt;span&gt;()}&amp;gt;清空&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2) CommonJS 与 ES6 Module 的变量导出差异（Q10）&lt;a href=&quot;#2-commonjs-与-es6-module-的变量导出差异q10&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是一道极为经典的模块化底层题：“假如模块里导出了一个数字 5，过一会儿模块内改成了 6，外部拿到的分别是多少？”&lt;/p&gt;
&lt;h4&gt;CommonJS (CJS) —— 值的浅拷贝&lt;a href=&quot;#commonjs-cjs--值的浅拷贝&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;module.exports&lt;/code&gt; 导出的是一个对象的&lt;strong&gt;浅拷贝&lt;/strong&gt;（类似按值传递）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// a.js (CommonJS)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; num &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setTimeout&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  num &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 6&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 1秒后内部改为 6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exports&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { num };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// b.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;./a.js&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(a.num); &lt;/span&gt;&lt;span&gt;// 输出 5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setTimeout&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(a.num); &lt;/span&gt;&lt;span&gt;// 2秒后再打印，依然是 5。因为 require 时复制了那一瞬间的值&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;2000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ES6 Module (ESM) —— 动态只读引用（Live Bindings）&lt;a href=&quot;#es6-module-esm--动态只读引用live-bindings&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;export&lt;/code&gt; 导出的是一个&lt;strong&gt;动态映射的内存地址引用&lt;/strong&gt;。外部拿到的值会随内部变化而动态变化，且该引用在外部是&lt;strong&gt;只读&lt;/strong&gt;的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// a.mjs (ES6 Module)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; num &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setTimeout&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  num &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 6&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 1秒后内部改为 6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// b.mjs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { num } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;./a.mjs&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(num); &lt;/span&gt;&lt;span&gt;// 输出 5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setTimeout&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(num); &lt;/span&gt;&lt;span&gt;// 2秒后再打印，输出 6！动态绑定了 a.mjs 内存&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // num = 7; // 如果强行在外部修改，会抛错 TypeError: Assignment to constant variable&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;2000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3) Flex 布局：&lt;code&gt;flex: 1&lt;/code&gt; vs &lt;code&gt;flex: auto&lt;/code&gt;（Q11, Q12, Q13）&lt;a href=&quot;#3-flex-布局flex-1-vs-flex-autoq11-q12-q13&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4&gt;Flex 基本概念&lt;a href=&quot;#flex-基本概念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Flexbox（弹性盒子）是一维布局模型，专门用于处理空间分布和对齐。它由**主轴（Main Axis）&lt;strong&gt;和&lt;/strong&gt;交叉轴（Cross Axis）**构成，能自适应父容器的尺寸变化，解决传统浮动（float）布局中垂直居中困难、等高列难实现等问题。&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;flex&lt;/code&gt; 简写属性的秘密&lt;a href=&quot;#flex-简写属性的秘密&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;flex&lt;/code&gt; 其实是三个属性的简写组合：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;flex-grow&lt;/code&gt;: 放大比例（默认 0，不放大）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flex-shrink&lt;/code&gt;: 缩小比例（默认 1，空间不足时会缩小）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flex-basis&lt;/code&gt;: 分配多余空间之前，元素的基础大小（默认 auto，依据内容大小）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;拆解代码演示：&lt;code&gt;flex: 1&lt;/code&gt; vs &lt;code&gt;flex: auto&lt;/code&gt;&lt;a href=&quot;#拆解代码演示flex-1-vs-flex-auto&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;假设我们有两个容器，分别放了不同长度的内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;box a&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;短&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;box b&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;我是一段非常非常长的文本内容&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .parent&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flex&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    border&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; solid&lt;/span&gt;&lt;span&gt; #ccc&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .box&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    border&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; solid&lt;/span&gt;&lt;span&gt; red&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 场景1：绝对平分（无视内容） */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .b&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    flex&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 结果：A 和 B 各占 300px，一模一样宽。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   原理：等同于 flex-basis: 0%，大家起点都是 0，直接把 600px 一分为二。*/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 场景2：相对平分（尊重内容大小） */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .b&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    flex&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 结果：B 会比 A 宽得多。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   原理：等同于 flex-basis: auto。系统先算出 A 和 B 文本原本的宽度，&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   然后从 600px 中减去这两个原始宽度，剩下的空白区域再平分给 A 和 B。*/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4) CSS 动画与 &lt;code&gt;animation&lt;/code&gt; 属性详解（Q17, Q18）&lt;a href=&quot;#4-css-动画与-animation-属性详解q17-q18&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在 CSS 中实现动画通常分两步：&lt;strong&gt;定义关键帧&lt;/strong&gt; 和 &lt;strong&gt;调用动画属性&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;第一步：定义 &lt;code&gt;@keyframes&lt;/code&gt;&lt;a href=&quot;#第一步定义-keyframes&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;指定动画在不同百分比时间节点（0% 到 100%，或 from 到 to）的 CSS 状态。&lt;/p&gt;
&lt;h4&gt;第二步：使用 &lt;code&gt;animation&lt;/code&gt; 简写属性&lt;a href=&quot;#第二步使用-animation-简写属性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;animation&lt;/code&gt; 也是一个超级简写属性，面试时能把下面 8 个子属性说清楚，绝对加分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* 顺序通常为：name | duration | timing-function | delay | iteration-count | direction | fill-mode | play-state */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;animation: slideIn 2s &lt;/span&gt;&lt;span&gt;ease-in-out&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;.5s&lt;/span&gt;&lt;span&gt; infinite alternate forwards running;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;拆解详解：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;animation-name&lt;/code&gt;&lt;/strong&gt;：绑定的关键帧名称（如 &lt;code&gt;slideIn&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;animation-duration&lt;/code&gt;&lt;/strong&gt;：持续时间（如 &lt;code&gt;2s&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;animation-timing-function&lt;/code&gt;&lt;/strong&gt;：缓动曲线，决定速度节奏。常用 &lt;code&gt;linear&lt;/code&gt; (匀速), &lt;code&gt;ease-in&lt;/code&gt; (渐显加速), &lt;code&gt;cubic-bezier&lt;/code&gt; (自定义贝塞尔曲线)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;animation-delay&lt;/code&gt;&lt;/strong&gt;：动画延迟多久开始（如 &lt;code&gt;0.5s&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;animation-iteration-count&lt;/code&gt;&lt;/strong&gt;：播放次数，常用 &lt;code&gt;infinite&lt;/code&gt;（无限循环）或具体数字。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;animation-direction&lt;/code&gt;&lt;/strong&gt;：播放方向。
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;normal&lt;/code&gt;（正向）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reverse&lt;/code&gt;（反向播放）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;alternate&lt;/code&gt;（交替播放，比如去程正放，回程倒放，常用于呼吸灯/摇摆效果）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;animation-fill-mode&lt;/code&gt;&lt;/strong&gt;（&lt;strong&gt;非常重要，面试常考&lt;/strong&gt;）：动画结束（或等待期）的状态。
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;forwards&lt;/code&gt;：动画结束后，元素&lt;strong&gt;停留在最后一帧&lt;/strong&gt;的状态。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backwards&lt;/code&gt;：动画延迟等待时，元素提前应用第一帧的状态。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;both&lt;/code&gt;：同时应用上述两者。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;animation-play-state&lt;/code&gt;&lt;/strong&gt;：动画的运行状态。可以通过 JS 控制它为 &lt;code&gt;paused&lt;/code&gt; 或 &lt;code&gt;running&lt;/code&gt; 来实现悬停暂停动画的效果。&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>category:面经</category><category>tag:携程集团</category><category>tag:面经</category><category>tag:React</category><category>tag:CSS</category></item><item><title>知乎一面面经</title><link>https://yuki-bloom.vercel.app/ja/post/zhi-hu-mian-jing</link><guid isPermaLink="false">ja:zhi-hu-mian-jing</guid><description>知乎前端一面面经。重点复盘：低代码表单联动、Intersection Observer 提前加载（正负 margin 的误区）、弱网快速滑动图片优化、箭头函数底层特性以及前端安全与 React 转义机制。</description><pubDate>Wed, 03 Jun 2026 09:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;知乎一面&lt;a href=&quot;#知乎一面&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;面试时间&lt;a href=&quot;#面试时间&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;2026_0603-17:00&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;面试题目&lt;a href=&quot;#面试题目&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;为什么打算离开快手？&lt;/li&gt;
&lt;li&gt;能实习多久？&lt;/li&gt;
&lt;li&gt;你在项目中主要负责什么？&lt;/li&gt;
&lt;li&gt;SSR 与 CSR 的区别及应用场景&lt;/li&gt;
&lt;li&gt;SSE 流式返回机制&lt;/li&gt;
&lt;li&gt;图表生成失败怎么考虑？&lt;/li&gt;
&lt;li&gt;低代码动态表单怎么联动？&lt;/li&gt;
&lt;li&gt;怎么做的提前加载内容？&lt;/li&gt;
&lt;li&gt;弱网情况下用户快速滑动图片列表怎么能够好的让用户访问？&lt;/li&gt;
&lt;li&gt;MCP Server 怎么理解？&lt;/li&gt;
&lt;li&gt;解释事件循环&lt;/li&gt;
&lt;li&gt;事件循环输出题&lt;/li&gt;
&lt;li&gt;var/let/const 区别&lt;/li&gt;
&lt;li&gt;箭头函数特性（this 指向、不能作为构造函数，没有 prototype）&lt;/li&gt;
&lt;li&gt;H5 页面顶部固定搜索框 + 下方自适应列表的布局方案&lt;/li&gt;
&lt;li&gt;虚拟 DOM 作用及 key 的作用（为什么不能用 index）&lt;/li&gt;
&lt;li&gt;全局状态管理使用场景&lt;/li&gt;
&lt;li&gt;跨域问题&lt;/li&gt;
&lt;li&gt;前端安全问题（恶意脚本注入、转译库）&lt;/li&gt;
&lt;li&gt;React 原生支持转义吗？&lt;/li&gt;
&lt;li&gt;AI 上下文治理（污染、回退、Sub Agent 隔离、压缩、skill）&lt;/li&gt;
&lt;li&gt;反问&lt;/li&gt;
&lt;li&gt;对我的建议&lt;/li&gt;
&lt;li&gt;是否转正及工作内容范围（C 端/M 端、全栈）&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;面试复盘（核心问题精讲）&lt;a href=&quot;#面试复盘核心问题精讲&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1) 低代码动态表单怎么联动？（Q7）&lt;a href=&quot;#1-低代码动态表单怎么联动q7&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;低代码表单的核心是 &lt;strong&gt;“数据驱动视图”&lt;/strong&gt;，联动的本质是 &lt;strong&gt;依赖收集与副作用（Side Effects）派发&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如：选择了“省份 A”，需要发请求拉取并回填“城市列表 B”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;完整解法（分层架构设计）：&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;第一层：联动规则的 Schema 定义&lt;a href=&quot;#第一层联动规则的-schema-定义&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;配置表单时，我们不能直接写 &lt;code&gt;watch&lt;/code&gt; 或 &lt;code&gt;useEffect&lt;/code&gt;，只能在 JSON Schema 中通过特定的关键字（如 &lt;code&gt;dependencies&lt;/code&gt; / &lt;code&gt;x-reactions&lt;/code&gt;）来描述“谁依赖了谁”、“满足条件后干什么”。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;场景：选择“部门(dept)”后，自动拉取该部门的“员工列表(users)”并回填下拉框。&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;dept&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;部门&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;users&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;员工&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;x-reactions&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;dependencies&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;dept&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // fulfill 定义条件满足后执行的动作&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;fulfill&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 1. 同步联动：改变自身 UI 状态（比如显示出来）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;state&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;visible&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;{{ !!$deps[0] }}&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 2. 异步联动：触发一个预定义的请求动作，并把数据回填到 dataSource&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;run&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;fetchUsersByDept($deps[0])&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;第二层：状态管理树（Form Store）&lt;a href=&quot;#第二层状态管理树form-store&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;低代码引擎在运行时，必须有一个统一的 Store（通常基于 &lt;code&gt;Proxy&lt;/code&gt;、&lt;code&gt;MobX&lt;/code&gt; 等响应式库）。这个 Store 里不仅存用户填的 &lt;code&gt;values&lt;/code&gt;，还要存每个组件内部的元数据：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Values&lt;/code&gt;: &lt;code&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Field State&lt;/code&gt;: &lt;code&gt;visible&lt;/code&gt;, &lt;code&gt;disabled&lt;/code&gt;, &lt;code&gt;loading&lt;/code&gt;（组件自带状态）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Component Props&lt;/code&gt;: &lt;code&gt;dataSource&lt;/code&gt;（下拉框的备选项数据）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;第三层：副作用派发与异步回填执行流（核心）&lt;a href=&quot;#第三层副作用派发与异步回填执行流核心&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;当用户在界面上切换了“部门”时，整个执行流如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;值变化拦截&lt;/strong&gt;：用户切到“前端组”，触发 &lt;code&gt;Store.setValues(&apos;dept&apos;, &apos;前端组&apos;)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;触发依赖链&lt;/strong&gt;：引擎内部的依赖收集器（Dependency Graph）发现 &lt;code&gt;users&lt;/code&gt; 字段订阅了 &lt;code&gt;dept&lt;/code&gt;，于是拿到 &lt;code&gt;users&lt;/code&gt; 对应的 &lt;code&gt;x-reactions&lt;/code&gt; 规则。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;沙箱求值&lt;/strong&gt;：取出 &lt;code&gt;&quot;{{ !!$deps[0] }}&quot;&lt;/code&gt;，在安全的 &lt;code&gt;new Function&lt;/code&gt; 沙箱中执行，得到 &lt;code&gt;visible: true&lt;/code&gt;。同时发现有 &lt;code&gt;run: &quot;fetchUsersByDept&quot;&lt;/code&gt; 这个异步动作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异步请求与回填&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;引擎立刻把 &lt;code&gt;users&lt;/code&gt; 字段的状态设为 &lt;code&gt;loading: true&lt;/code&gt;（此时界面上下拉框会转圈）。&lt;/li&gt;
&lt;li&gt;引擎调用注册在全局的 &lt;code&gt;fetchUsersByDept(&apos;前端组&apos;)&lt;/code&gt; 接口拉数据。&lt;/li&gt;
&lt;li&gt;接口返回 &lt;code&gt;[{ label: &apos;张三&apos;, value: &apos;1&apos; }]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;引擎派发更新：&lt;code&gt;Store.setFieldState(&apos;users&apos;, { loading: false, dataSource: 拿到数据 })&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;视图更新&lt;/strong&gt;：底层组件（如下拉框）因为绑定了 Store 的 &lt;code&gt;dataSource&lt;/code&gt;，自动重新渲染，数据回填完成。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;第四层：防死循环（DAG 拓扑排序）&lt;a href=&quot;#第四层防死循环dag-拓扑排序&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;如果业务配置非常复杂：A 变化 -&amp;gt; 拉取 B -&amp;gt; B 回填后触发 C -&amp;gt; C 又修改了 A。这会导致死循环和不断发请求。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;防御机制：&lt;/strong&gt;
引擎在解析 JSON 初始化表单时，会先构建一张&lt;strong&gt;有向无环图（DAG）&lt;/strong&gt;。如果发现依赖成环（A-&amp;gt;B-&amp;gt;C-&amp;gt;A），初始化阶段直接抛错，拒绝渲染，从而从根源切断死循环问题。&lt;/p&gt;
&lt;h3&gt;2) 提前加载内容与 Intersection Observer 的 margin（Q8）&lt;a href=&quot;#2-提前加载内容与-intersection-observer-的-marginq8&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;【纠点预警】&lt;/strong&gt;：面试中如果提到“设置成负的来提前加载”，这里其实是个常见误区！
&lt;strong&gt;提前加载需要把 &lt;code&gt;rootMargin&lt;/code&gt; 设置为“正值”（Positive）。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理解析：&lt;/strong&gt;
&lt;code&gt;Intersection Observer&lt;/code&gt; 默认在元素的边界刚好碰到视口（Viewport）时触发。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;正值（如 &lt;code&gt;rootMargin: &quot;0px 0px 500px 0px&quot;&lt;/code&gt;）&lt;/strong&gt;：相当于把视口的判定范围&lt;strong&gt;向下扩大了 500px&lt;/strong&gt;。元素还在屏幕下方 500px 时，观察器就认为它“进入”了，从而触发回调。&lt;strong&gt;这才是做“提前加载”（预加载）的正确姿势。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;负值（如 &lt;code&gt;rootMargin: &quot;-50px 0px&quot;&lt;/code&gt;）&lt;/strong&gt;：相当于把视口向内收缩了 50px。元素必须完全进入屏幕并且再往上走 50px 才算“可见”。这通常用于**“精准曝光埋点”**（防止用户飞速滑过也算曝光）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;补充：其他提前加载手段&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;link rel=&quot;prefetch&quot;&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;link rel=&quot;preload&quot;&amp;gt;&lt;/code&gt;：在浏览器空闲时提前下载下一页资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hover 预加载&lt;/strong&gt;：当用户鼠标悬停在链接上时（大约有 200-300ms 的犹豫时间），提前发请求拉取数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3) 弱网 + 快速滑动长列表的图片优化（Q9）&lt;a href=&quot;#3-弱网--快速滑动长列表的图片优化q9&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;弱网加极速滑动，最大的问题是：&lt;strong&gt;滑过的废弃图片塞满了浏览器的并发请求队列（Chrome 默认最多 6 个并发），导致当前停下来真正该看的图片一直处于 Pending 状态。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;完整的解决方案（可层层递进向面试官展示）：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;取消废弃请求（最核心）&lt;/strong&gt;：配合 &lt;code&gt;Intersection Observer&lt;/code&gt;，如果元素很快滑出可视区（或扩大后的可视区），立即在 &lt;code&gt;unobserve&lt;/code&gt; 的同时中断请求。如果用 fetch，可以用 &lt;code&gt;AbortController&lt;/code&gt;；如果是 img 标签，可以将 &lt;code&gt;src&lt;/code&gt; 置空（&lt;code&gt;img.src = &apos;&apos;&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;防抖/延迟加载判断&lt;/strong&gt;：不要一进入可视区就立马加载。定一个 100ms 的延迟，如果 100ms 内元素又滑出去了，就不发请求。（过滤掉飞速滑动途经的图片）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渐进式占位（骨架/低清）&lt;/strong&gt;：请求真正大图前，先显示极小体积的缩略图（LQIP）、或者用 &lt;code&gt;Blurhash&lt;/code&gt;（用一串几十字节的字符串渲染出色彩模糊的占位），给用户心理预期。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;图片格式降级&lt;/strong&gt;：通过 CDN 参数，弱网下自动请求压缩率更高的 WebP/AVIF 格式，或者降低 &lt;code&gt;quality&lt;/code&gt; 参数。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4) 箭头函数特性底层剖析（Q14）&lt;a href=&quot;#4-箭头函数特性底层剖析q14&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;回答箭头函数不要只背诵，要告诉面试官“为什么”。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;没有自身的 &lt;code&gt;this&lt;/code&gt;（词法作用域绑定）&lt;/strong&gt;：
普通函数的 &lt;code&gt;this&lt;/code&gt; 是在&lt;strong&gt;调用时&lt;/strong&gt;决定的（谁调用就指向谁）。而箭头函数的 &lt;code&gt;this&lt;/code&gt; 是在&lt;strong&gt;定义时&lt;/strong&gt;根据外层上下文（Lexical Context）决定的。因为它自身压根没有 &lt;code&gt;this&lt;/code&gt;，所以你用 &lt;code&gt;.bind()&lt;/code&gt;、&lt;code&gt;.call()&lt;/code&gt;、&lt;code&gt;.apply()&lt;/code&gt; 强行改也没用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不能作为构造函数（不能 &lt;code&gt;new&lt;/code&gt;）&lt;/strong&gt;：
因为 JS 规范里，箭头函数并没有内部方法 &lt;code&gt;[[Construct]]&lt;/code&gt;。当你对它使用 &lt;code&gt;new&lt;/code&gt; 关键字时，JS 引擎去寻找 &lt;code&gt;[[Construct]]&lt;/code&gt; 找不到，直接抛出 &lt;code&gt;TypeError&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有 &lt;code&gt;prototype&lt;/code&gt; 属性&lt;/strong&gt;：
既然不能被 &lt;code&gt;new&lt;/code&gt; 实例化，那它就不需要原型链去给实例继承属性，所以引擎在创建箭头函数时直接省去了 &lt;code&gt;prototype&lt;/code&gt; 属性（节约内存）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有 &lt;code&gt;arguments&lt;/code&gt; 对象&lt;/strong&gt;：
不能用 &lt;code&gt;arguments&lt;/code&gt; 获取参数，现代 JS 推荐统一使用 &lt;code&gt;...args&lt;/code&gt;（剩余参数）来替代。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5) 前端安全与 React 转义机制（Q19 &amp;amp; Q20）&lt;a href=&quot;#5-前端安全与-react-转义机制q19--q20&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;React 原生支持转义吗？（Q20）&lt;/strong&gt;
&lt;strong&gt;答案是：原生支持，且非常安全。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;机制&lt;/strong&gt;：在 React 中，当你写 &lt;code&gt;&amp;lt;div&amp;gt;{userInput}&amp;lt;/div&amp;gt;&lt;/code&gt; 时，React 的渲染引擎底层调用的是 &lt;code&gt;textContent&lt;/code&gt; 而不是 &lt;code&gt;innerHTML&lt;/code&gt;。所有传入的大括号 &lt;code&gt;{}&lt;/code&gt; 变量，在渲染到 DOM 之前都会被强制转换为字符串。哪怕你传入的是 &lt;code&gt;&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&lt;/code&gt;，页面上也只会原样显示这串字符，而不会去执行它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特例&lt;/strong&gt;：React 唯一的后门是 &lt;code&gt;dangerouslySetInnerHTML={{ __html: userInput }}&lt;/code&gt;。这个属性名字故意取得这么长、这么吓人，就是为了提醒开发者“这很危险”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;前端安全问题（恶意脚本注入、转译库）（Q19）&lt;/strong&gt;
这就是经典的 &lt;strong&gt;XSS（跨站脚本攻击）&lt;/strong&gt;。
如果在 React 里非要渲染富文本（比如从后端拿到了 Markdown 转换后的 HTML，必须用 &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;），就必须经过转译（Sanitize）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;防范手段：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;转译库（Sanitizer）&lt;/strong&gt;：千万不要自己写正则替换，非常容易被绕过。业界标准是使用 &lt;strong&gt;DOMPurify&lt;/strong&gt;。把脏字符串扔进去 &lt;code&gt;DOMPurify.sanitize(dirtyHtml)&lt;/code&gt;，它会基于一个严苛的白名单，把里面夹带的 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 标签、&lt;code&gt;javascript:void(0)&lt;/code&gt; 链接、&lt;code&gt;onerror&lt;/code&gt; 属性全部剔除，返回干净的 HTML。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSP（内容安全策略）&lt;/strong&gt;：在 HTTP 响应头加上 &lt;code&gt;Content-Security-Policy: default-src &apos;self&apos;&lt;/code&gt;，从源头告诉浏览器：只能执行自己域名下的脚本，就算是漏网之鱼被注入了第三方脚本也执行不了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cookie 安全&lt;/strong&gt;：把鉴权 Token 存到 Cookie 时，必须加上 &lt;code&gt;HttpOnly&lt;/code&gt; 属性。这样就算 XSS 攻击成功，黑客的 JS 代码也读不到你的 Cookie（无法发回给黑客服务器）。&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>category:面经</category><category>tag:知乎</category><category>tag:面经</category><category>tag:低代码</category><category>tag:性能优化</category><category>tag:React</category></item><item><title>劳作与远行交叠，夜修与心愿同起</title><link>https://yuki-bloom.vercel.app/ja/post/weekly-2026-05-31</link><guid isPermaLink="false">ja:weekly-2026-05-31</guid><description>两周合刊，记录混研低代码与线上修复的压力、求职迷茫与武汉北京小旅行，以及设计模式学习与依赖注入的收获。</description><pubDate>Sun, 31 May 2026 13:30:00 GMT</pubDate><content:encoded>&lt;p&gt;上周忘记写了，这次把 &lt;strong&gt;5.18 ~ 5.31&lt;/strong&gt; 合在一起记一下。&lt;/p&gt;
&lt;p&gt;这两周整体节奏很像“混研 + 低代码 + 旧项目”三件套：白天写业务，晚上提测修 bug，偶尔被线上问题捅一刀 (T_T)。&lt;/p&gt;
&lt;h2&gt;工作：混研 + 低代码的日常磨耗&lt;a href=&quot;#工作混研--低代码的日常磨耗&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1) 你根本不知道页面是源码还是低代码&lt;a href=&quot;#1-你根本不知道页面是源码还是低代码&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;同一套页面，&lt;strong&gt;有的在源码里，有的在 schema 里&lt;/strong&gt;。想改一个小问题，先得用 search 找关键词——在 schema 里的就是低代码，不在的就是源码。&lt;/p&gt;
&lt;p&gt;低代码改起来比源码更麻烦：字段多、联动杂、发布链路还不透明。改完才发现“发布的不是我这套”，一度迷路。最后还是问 onCall + mentor 才把链条梳清楚，准备写成文档给后人 (´-ω-`)。&lt;/p&gt;
&lt;p&gt;混研这块更痛苦：你根本不知道页面是源码还是低代码，只能靠 search 关键词，看落在 schema 里的就是低代码，其余才是源码。对外是同一页面，对内是两套世界，改起来像在拆盲盒。&lt;/p&gt;
&lt;p&gt;维护这个 M 端一度像坐牢，但这两周慢慢摸清了发布链路和字段套路，现在算是“能游刃有余地坐牢”了（至少不会每天都懵）。&lt;/p&gt;
&lt;h3&gt;2) 老项目线上问题 + 三平台兼容&lt;a href=&quot;#2-老项目线上问题--三平台兼容&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这周还出了两个线上问题，&lt;strong&gt;全是五年前的项目&lt;/strong&gt;。产品文档写的是“只改一个平台”，结果上线后发现要兼容三个平台……&lt;/p&gt;
&lt;p&gt;更难受的是逻辑还要求“新逻辑不兼容老逻辑”，结果一上线就报错，只能临时补兼容。到最后我都开始害怕上线了。&lt;/p&gt;
&lt;h3&gt;3) 业务逻辑循环：提测 → 修 bug → 再提测&lt;a href=&quot;#3-业务逻辑循环提测--修-bug--再提测&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;前人离职的代码问题真的多：需求里没有的按钮还被加上了、低代码页面结构像谜题。周一晚上提测，周二修 bug，周三再提测……循环跑了好几次。&lt;/p&gt;
&lt;p&gt;不过好消息是：这周上线终于平稳了，&lt;strong&gt;线上 bug 也修干净&lt;/strong&gt;，稍微松了一口气。&lt;/p&gt;
&lt;p&gt;周四周五留出了一点时间，可以慢慢补学习和复盘，不再是“上班打仗、下班救火”的节奏。&lt;/p&gt;
&lt;h2&gt;求职：投递不顺 &amp;amp; 一点点希望&lt;a href=&quot;#求职投递不顺--一点点希望&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;投了一周几乎没怎么约到面试，太难了 (´•̥̥̥ω•̥̥̥`)。两周只约到一个携程的面试，希望能过 (ง •_•)ง。&lt;/p&gt;
&lt;p&gt;还以为会被百度面试官捞，结果感觉像“大家都说你优秀，横向没问题”，然后就没有然后了（养蛊感拉满，欺骗感情呜呜）。&lt;/p&gt;
&lt;p&gt;更扎心的是：暑期 3 月就投了，连笔试都没发。秋招到底会怎样，还挺迷茫的。现在这边一直做业务迭代，学不到更多系统性的东西，&lt;strong&gt;很想遇到一个能带我走更远的 mentor&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但也舍不得工资（现实就是这样），所以只能继续努力投 + 继续做任务两头跑。希望能遇到一个靠谱 mentor，哪怕不教我，至少给我指个路。&lt;/p&gt;
&lt;h2&gt;学习：设计模式 &amp;amp; 依赖注入的惊喜&lt;a href=&quot;#学习设计模式--依赖注入的惊喜&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这周在学习设计模式，尤其是 &lt;strong&gt;依赖注入&lt;/strong&gt;，感觉特别顺手：不用自己管理依赖，需要什么就拿什么。&lt;/p&gt;
&lt;p&gt;我也把笔记整理了一下，链接放这：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/note/creational-patterns-front-end&quot;&gt;设计模式：创建型模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/note/structural-patterns-front-end&quot;&gt;设计模式：结构型模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/note/behavioral-patterns-front-end&quot;&gt;设计模式：行为型模式&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;生活：武汉三日游 + 北京小打卡&lt;a href=&quot;#生活武汉三日游--北京小打卡&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;武汉三日游（周五晚 ~ 周一）&lt;a href=&quot;#武汉三日游周五晚--周一&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;请了一天假去武汉，天气真的是 &lt;strong&gt;大蒸炉&lt;/strong&gt;：又热又闷还下雨 (；´д｀)ゞ。不过吃的还不错，烤鱼很香（忘记吃火锅了，下次补上）。&lt;/p&gt;
&lt;h3&gt;北京周末小逛&lt;a href=&quot;#北京周末小逛&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这周末去国贸转了一下，来北京半年了还没怎么逛。打卡了 &lt;strong&gt;offer 大楼&lt;/strong&gt;（许愿 offer (☆_☆)），还有大裤衩和中央广播电视中心。&lt;/p&gt;
&lt;p&gt;南锣鼓巷像个小吃街，胡同文化是不是就是夜市小吃街（？）。还打卡了 &lt;strong&gt;黄仁勋同款蜜桃四季春&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;最后去了什刹海和北海公园，北京的“海”确实挺好看。&lt;/p&gt;
&lt;h2&gt;碎碎念&lt;a href=&quot;#碎碎念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;他们怎么周五都团建……我们组呢？为什么只有闷头干活 (눈_눈)。&lt;/p&gt;
&lt;p&gt;总之，这两周虽然累，但感觉自己对“混研 + 低代码 + 发布链路”这条线越来越熟了。希望携程面试能过，换个环境好好准备秋招。&lt;/p&gt;
&lt;h2&gt;一点思考&lt;a href=&quot;#一点思考&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这两周的感受挺现实：业务需求不会因为你迷茫就暂停，线上问题也不会因为你累就不来。但好的一面是，每一次“被迫修复”和“被迫适配”，都在逼着我把能力拼成闭环——从定位问题、理解链路、改完上线，再到写文档沉淀。&lt;/p&gt;
&lt;p&gt;当情绪很乱的时候，我会提醒自己：&lt;strong&gt;先把能掌控的事情做好&lt;/strong&gt;。能做的，就是把当下的事情做到更扎实，把每一次坑踩成一条清晰的路。路会慢，但路是往前的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“路漫漫其修远兮，吾将上下而求索。”——屈原&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;下周继续努力，愿 offer 来得更猛一点&lt;/strong&gt; (ง •_•)ง&lt;/p&gt;</content:encoded><category>category:周刊</category><category>tag:周刊</category><category>tag:职场</category><category>tag:低代码</category><category>tag:旅行</category><category>tag:面试</category></item><item><title>设计模式：创建型模式</title><link>https://yuki-bloom.vercel.app/ja/post/creational-patterns-front-end</link><guid isPermaLink="false">ja:creational-patterns-front-end</guid><description>从前端视角重新梳理创建型模式，补全工厂、单例、依赖注入、原型、建造者和对象池的实战代码、耦合对照与 Vue provide/inject 示例。</description><pubDate>Sun, 31 May 2026 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;创建型模式到底在解决什么？&lt;a href=&quot;#创建型模式到底在解决什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;创建型模式的核心，不是“发明几个高大上的名词”，而是把对象创建这件事从业务代码里收回来，交给一个更稳定、更统一的地方处理。&lt;/p&gt;
&lt;p&gt;如果你到处都是 &lt;code&gt;new Xxx()&lt;/code&gt;，对象的创建方式、参数变化、依赖顺序一改，整个项目都会跟着震动。创建型模式本质上就是在降低这种“创建方式变化”带来的耦合。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一句话总结&lt;/strong&gt; &lt;/p&gt;&lt;div&gt;&lt;/div&gt;“怎么创建对象”这件事，和“怎么使用对象”分开。&lt;p&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;工厂模式（Factory Pattern）&lt;a href=&quot;#工厂模式factory-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：根据数据动态渲染不同卡片&lt;a href=&quot;#前端真实场景根据数据动态渲染不同卡片&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在富文本编辑器、低代码平台、CMS 页面里，经常会遇到这种场景：后端返回一个 type，你要根据 type 渲染不同组件。&lt;/p&gt;
&lt;p&gt;比如后端接口真实返回的一份页面区块数据可能是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;block_9527&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;video&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;properties&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;src&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://example.com/movie.mp4&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;autoplay&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;controls&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;layout&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;width&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;100%&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;marginTop&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;20px&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;面对这种数据，我们很多人的第一反应就是在业务层直接写一长串的 &lt;code&gt;if / else&lt;/code&gt; 或者 &lt;code&gt;switch&lt;/code&gt;，即：把“判断类型、收集参数、实例化核心类”这三件事揉在一起。&lt;/p&gt;
&lt;p&gt;一段不假思索的、高度耦合的渲染逻辑大概率会长这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 假设这些是我们底层依赖的第三方或自己写的复杂组件类&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { VideoPlayer, ImageViewer, TextRender } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;@/libs/ui-core&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; renderBlock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;blockData&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  //  耦合发生地：需要关心要 new 谁，还要关心每个类初始化需要怎么塞参数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (blockData.type &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;video&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; player&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; VideoPlayer&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      url: blockData.properties.src,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      auto: blockData.properties.autoplay,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      showControls: blockData.properties.controls,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    player.&lt;/span&gt;&lt;span&gt;setStyle&lt;/span&gt;&lt;span&gt;(blockData.layout);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; player.&lt;/span&gt;&lt;span&gt;mount&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (blockData.type &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;image&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; viewer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; ImageViewer&lt;/span&gt;&lt;span&gt;(blockData.properties.src);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    viewer.&lt;/span&gt;&lt;span&gt;setStyle&lt;/span&gt;&lt;span&gt;(blockData.layout);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; viewer.&lt;/span&gt;&lt;span&gt;mount&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (blockData.type &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;text&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; textNode&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; TextRender&lt;/span&gt;&lt;span&gt;(blockData.properties.content);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    textNode.&lt;/span&gt;&lt;span&gt;setStyle&lt;/span&gt;&lt;span&gt;(blockData.layout);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; textNode.&lt;/span&gt;&lt;span&gt;mount&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;未知的区块类型&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的问题很快就暴露出来了：&lt;code&gt;renderBlock&lt;/code&gt; 这个普通的业务视图函数，背负了太多。它既负责“判断结构”，又负责“去调底层的各种类”，还负责“拼装特定的参数和样式”。一旦新增一个 &lt;code&gt;quote&lt;/code&gt; 引用类型，或者大版本更新 &lt;code&gt;VideoPlayer&lt;/code&gt; 的构造函数的入参变了，这个函数就得跟着大改。&lt;/p&gt;
&lt;h3&gt;如果没有工厂，灾难还会蔓延&lt;a href=&quot;#如果没有工厂灾难还会蔓延&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;如果上面这段逻辑只在一个地方写还能忍，但往往这些底层实体类需要在多个位置被实例化。比如 Toolbar、快捷键、右键菜单里都可能需要凭空生成一个视频块。&lt;/p&gt;
&lt;p&gt;没有工厂时，业务组件会自己决定“创建谁、怎么创建、参数怎么传”，于是每个文件里都开始写同样的实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Toolbar.vue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; block&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; blockType &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;video&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ?&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; VideoPlayer&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;/* 传一堆参 */&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    :&lt;/span&gt;&lt;span&gt; blockType &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;image&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ?&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; ImageViewer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/* 传参数 */&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      :&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; TextRender&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/* 传参数 */&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Shortcut.vue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; block&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; blockType &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;video&quot;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; VideoPlayer&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; /* ... */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// RightMenu.vue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; block&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; blockType &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;video&quot;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; VideoPlayer&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; /* ... */&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会带来两个绝望的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;创建逻辑散落到处都是&lt;/strong&gt;。以后 &lt;code&gt;VideoPlayer&lt;/code&gt; 构造函数多一个必填参数，你要全局搜索这十几个文件，挨个 &lt;code&gt;new VideoPlayer(...)&lt;/code&gt; 去改，直接崩溃。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;业务组件被创建细节绑死&lt;/strong&gt;。组件本来只该关心“我要一个卡片”，结果被迫知道了“底层的某个卡片到底是怎么造出来的和需要什么格式的参数”。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;更前端一点的写法：工厂模式解耦&lt;a href=&quot;#更前端一点的写法工厂模式解耦&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;工厂模式的核心，就是建一个专门用来“制造对象”的工厂。业务层只管提交“加工单”，工厂负责查参数、分配给具体类。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { VideoPlayer, ImageViewer, TextRender } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;@/libs/ui-core&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//  创建一个专门的制造工厂&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; BlockFactory&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  static&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;blockData&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; instance;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    switch&lt;/span&gt;&lt;span&gt; (blockData.type) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      case&lt;/span&gt;&lt;span&gt; &quot;video&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        instance &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; VideoPlayer&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          url: blockData.properties.src,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          auto: blockData.properties.autoplay,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          showControls: blockData.properties.controls,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      case&lt;/span&gt;&lt;span&gt; &quot;image&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        instance &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; ImageViewer&lt;/span&gt;&lt;span&gt;(blockData.properties.src);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      case&lt;/span&gt;&lt;span&gt; &quot;text&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        instance &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; TextRender&lt;/span&gt;&lt;span&gt;(blockData.properties.content);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      default&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`不支持的类型: ${&lt;/span&gt;&lt;span&gt;blockData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 统一处理公共逻辑，比如样式挂载&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (blockData.layout &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; instance.setStyle) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      instance.&lt;/span&gt;&lt;span&gt;setStyle&lt;/span&gt;&lt;span&gt;(blockData.layout);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; instance;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调用方的世界彻底清爽了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// renderBlock.ts 或者是 Vue组件内部&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { BlockFactory } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;./BlockFactory&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; renderBlock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;blockData&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 业务层再也不用关心到底 new 了什么、传了什么细节散参&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; block&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; BlockFactory.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(blockData);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; block.&lt;/span&gt;&lt;span&gt;mount&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样后续如果新增 &lt;code&gt;QuoteCard&lt;/code&gt;，你&lt;strong&gt;只需改且只改 &lt;code&gt;BlockFactory&lt;/code&gt; 一个文件&lt;/strong&gt;即可，其它业务层逻辑纹丝不动。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;单例模式（Singleton Pattern）&lt;a href=&quot;#单例模式singleton-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：全局唯一的 WebSocket 消息通知&lt;a href=&quot;#前端真实场景全局唯一的-websocket-消息通知&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;假设我们在做一个有很多页面的管理系统，几乎每个页面（顶部的未读消息红点、侧边栏的审批通知、甚至弹窗里）都需要实时接收后端的 WebSocket 消息。&lt;/p&gt;
&lt;h3&gt;第一反应：哪里需要，就在哪里连&lt;a href=&quot;#第一反应哪里需要就在哪里连&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;最直白的写法就是，我们在 &lt;code&gt;Header.vue&lt;/code&gt; 和 &lt;code&gt;Sidebar.vue&lt;/code&gt; 里各自去建立连接：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Header.vue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ws&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WebSocket&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;wss://api.example.com/notifications&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ws.&lt;/span&gt;&lt;span&gt;onmessage&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;更新顶部红点&quot;&lt;/span&gt;&lt;span&gt;, msg);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Sidebar.vue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ws&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WebSocket&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;wss://api.example.com/notifications&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ws.&lt;/span&gt;&lt;span&gt;onmessage&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;更新侧边栏待办&quot;&lt;/span&gt;&lt;span&gt;, msg);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：服务器爆炸与状态非同步&lt;a href=&quot;#灾难服务器爆炸与状态非同步&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;如果真的这么写，会带来非常恐怖的灾难：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;资源极度浪费&lt;/strong&gt;：用户打开一个页面，前端悄悄建立了 5 个 WebSocket 连接，后端连接数直接原地爆炸。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据不同步&lt;/strong&gt;：不同组件各自维系一个连接，由于网络延迟，顶部已经显示有新消息，侧边栏还是旧的。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：单例模式&lt;a href=&quot;#解决思路单例模式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;单例的核心就是 &lt;strong&gt;“计划生育”，保证一个类在整个运行周期里，绝对只有唯一的一个实例&lt;/strong&gt;。如果是第一次要，我就建给你；如果已经建过了，我就把之前那个给你。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;经典的类实现方法（通过类的静态属性拦截）：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; WsManager&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 1. 静态属性存放唯一实例&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  private&lt;/span&gt;&lt;span&gt; static&lt;/span&gt;&lt;span&gt; instance&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; WsManager&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  public&lt;/span&gt;&lt;span&gt; ws&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; WebSocket&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 2. 构造函数私有化，防死外部 new WsManager()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  private&lt;/span&gt;&lt;span&gt; constructor&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.ws &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WebSocket&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;wss://api.example.com/notifications&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 3. 唯一的获取入口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  public&lt;/span&gt;&lt;span&gt; static&lt;/span&gt;&lt;span&gt; getInstance&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;WsManager.instance) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      WsManager.instance &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WsManager&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; WsManager.instance;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 后续在任何地方获取，拿到的都是同一个连接内存地址&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ws1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; WsManager.&lt;/span&gt;&lt;span&gt;getInstance&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ws2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; WsManager.&lt;/span&gt;&lt;span&gt;getInstance&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(ws1 &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; ws2); &lt;/span&gt;&lt;span&gt;// true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;更现代的前端做法（ES Module 就是天然的单例）：&lt;/strong&gt;
如今在前端，打包工具自带模块缓存，我们根本不需要写那么绕的类代码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// wsManager.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 文件只会被解析执行一次，天然保证单例&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; globalWs&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WebSocket&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;wss://api.example.com/notifications&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到处 &lt;code&gt;import { globalWs } from &apos;./wsManager&apos;&lt;/code&gt; 即可。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;依赖注入（Dependency Injection, DI）&lt;a href=&quot;#依赖注入dependency-injection-di&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：深层级组件的上下文传递&lt;a href=&quot;#前端真实场景深层级组件的上下文传递&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;假设你的整个系统支持“暗黑/明亮”主题切换，并且内部有一套极端复杂的多级表单。
结构是这样的：&lt;code&gt;App -&amp;gt; Layout -&amp;gt; Page -&amp;gt; Table -&amp;gt; Form -&amp;gt; Button&lt;/code&gt;。
最底层的 &lt;code&gt;Button&lt;/code&gt; 按钮需要知道当前的 &lt;code&gt;theme&lt;/code&gt; 来决定自己的背景色。&lt;/p&gt;
&lt;h3&gt;第一反应：Props 一路向下钻（Props Drilling）&lt;a href=&quot;#第一反应props-一路向下钻props-drilling&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;最朴素的想法：既然数据在最顶层的 &lt;code&gt;App.vue&lt;/code&gt;，我就一层层用 Props 传下去。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!-- App.vue --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Layout&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;theme&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;currentTheme&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- Layout.vue --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Page&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;theme&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;props.theme&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- Page.vue --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;theme&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;props.theme&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- ... 一直传到 Button.vue --&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：无辜的中间层被绑架&lt;a href=&quot;#灾难无辜的中间层被绑架&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这种写法叫 props drilling（属性钻取）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;中间组件被污染&lt;/strong&gt;：&lt;code&gt;Page&lt;/code&gt; 和 &lt;code&gt;Table&lt;/code&gt; 从头到尾都没用到 &lt;code&gt;theme&lt;/code&gt;，但它们被迫在代码里声明并传递这个变量。代码又臭又长。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高度耦合，难以复用&lt;/strong&gt;：如果你想把 &lt;code&gt;Table&lt;/code&gt; 组件移植到另一个没有 &lt;code&gt;theme&lt;/code&gt; 设定的项目里，它竟然会报错说少传了参数。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：依赖注入（DI）&lt;a href=&quot;#解决思路依赖注入di&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;依赖注入的精髓是：&lt;strong&gt;最底层不用去问上层要，也不用自己想办法新建；顶层直接把东西“注入”到一个公共的空气通道里，底层直接“伸手拿”。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 Vue 3 里，这就是最经典的 &lt;code&gt;provide / inject&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;顶层提供依赖（Provide）：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!-- App.vue (提供方) --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; setup&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { provide, ref } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;vue&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; currentTheme&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ref&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;dark&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 把依赖注入到这棵组件树的虚拟空间中&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;provide&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;app-theme&quot;&lt;/span&gt;&lt;span&gt;, currentTheme);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;底层无脑获取（Inject）：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!-- Button.vue (任意深的子组件) --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; setup&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { inject } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;vue&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 绕过所有中间层，直接伸手拿&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; theme&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; inject&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;app-theme&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;light&quot;&lt;/span&gt;&lt;span&gt; /* 默认值 */&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; :class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;`btn-${theme}`&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;提交&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;中间的 &lt;code&gt;Layout&lt;/code&gt;, &lt;code&gt;Page&lt;/code&gt;, &lt;code&gt;Table&lt;/code&gt; 再也不需要碰 &lt;code&gt;theme&lt;/code&gt; 这个雷区了，这也就是所谓**“控制反转（IoC）”**在组件层的最佳体现。&lt;/p&gt;
&lt;h3&gt;进阶前端场景：逻辑类的连环依赖（A 依赖 B，B 依赖 C）&lt;a href=&quot;#进阶前端场景逻辑类的连环依赖a-依赖-bb-依赖-c&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在前端的复杂单页应用中，我们的网络请求模块（Service / API 抽象层）往往存在这种连环依赖：我们要写一个 &lt;code&gt;UserService&lt;/code&gt; 请求用户数据；发请求前拿 Token 需要经过 &lt;code&gt;AuthService&lt;/code&gt; 鉴权；而 &lt;code&gt;AuthService&lt;/code&gt; 又必须依赖 &lt;code&gt;StorageService&lt;/code&gt; 去读取底层的 &lt;code&gt;localStorage&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果按照传统直觉去写（强耦合的层层 import 和 new）：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// StorageService.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; StorageService&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 读写底层缓存 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// AuthService.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { StorageService } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;./StorageService&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; AuthService&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  storage&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; StorageService&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //  灾难1：强行在此处实例化了底层的具体实现&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.storage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; StorageService&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// UserService.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { AuthService } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;./AuthService&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; UserService&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  auth&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; AuthService&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //  灾难2：继续俄罗斯套娃&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.auth &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; AuthService&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(注：上面这种写法，导致 &lt;code&gt;UserService&lt;/code&gt;、&lt;code&gt;AuthService&lt;/code&gt; 和 &lt;code&gt;StorageService&lt;/code&gt; 三者这辈子彻底焊死了。这会带来无尽的折磨，比如你要给 &lt;code&gt;UserService&lt;/code&gt; 写隔离单元测试时，它会一路顺藤摸瓜去读真实的缓存 API，直接引发环境报错；一旦 &lt;code&gt;StorageService&lt;/code&gt; 构造函数需要加一个配置参数，你要去所有 &lt;code&gt;new StorageService&lt;/code&gt; 的上游文件里挨个改代码！)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;终极解决思路：IoC 容器统一做这层“中间商”&lt;a href=&quot;#终极解决思路ioc-容器统一做这层中间商&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我们在 Angular、InversifyJS（或后端的 NestJS）中见到的 DI，正是通过&lt;strong&gt;IoC 容器&lt;/strong&gt;来彻底接管这一切。
它的精髓是：&lt;strong&gt;你不准在 A 里面再去 &lt;code&gt;import B&lt;/code&gt; 然后 &lt;code&gt;new B()&lt;/code&gt; 了。需要依赖的类都在头上打个标签（&lt;code&gt;@Injectable&lt;/code&gt;），大家只需要通过构造函数大喊“我要什么包”，剩下的一切统统交给“容器管家”去自动管理。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Injectable, Container } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;di-library&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Injectable&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; StorageService&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Injectable&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; AuthService&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 告诉管家：我需要一个 StorageService 实例，不用我自己 new，请派发给我&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; storage&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; StorageService&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Injectable&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; UserService&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 告诉管家：我需要一个 AuthService 实例&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; auth&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; AuthService&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//  见证奇迹的时刻&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; container&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 业务端只管“伸手要”，拿到的就是配置好一切的完整对象：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; userService&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; container.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(UserService);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个过程中，&lt;strong&gt;容器（Container）在底层偷偷帮你完成了一张依赖拓扑图&lt;/strong&gt;：
它看到你想要 &lt;code&gt;UserService&lt;/code&gt;，但发现缺 &lt;code&gt;AuthService&lt;/code&gt;，再去查发现还缺 &lt;code&gt;StorageService&lt;/code&gt;。于是管家默默地帮你执行： &lt;code&gt;new StorageService()&lt;/code&gt; -&amp;gt; 把缓存实例塞给 &lt;code&gt;new AuthService(...)&lt;/code&gt; -&amp;gt; 最后把 auth 实例塞给 &lt;code&gt;new UserService(...)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这就是 DI 最爽的地方：你只需要拿，不需要管。&lt;/strong&gt; 至于 C 怎么实例化的、B 的参数怎么来的？全部由统一的容器从上帝视角帮你构建好，我们再也不用手动去层层依赖、层层瞎改代码了。&lt;/p&gt;
&lt;h3&gt;附录：这个“管家”底层凭什么这么神奇？（极简版容器实现）&lt;a href=&quot;#附录这个管家底层凭什么这么神奇极简版容器实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;其实 IoC 容器没有魔法，它的核心本质就是一个&lt;strong&gt;注册表（Map）&lt;strong&gt;再加上&lt;/strong&gt;递归实例化&lt;/strong&gt;。借此机会，我们可以用十几行代码自己手写一个极简版的 Container，一看就懂：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 核心：一个存放所有信息的大字典&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  private&lt;/span&gt;&lt;span&gt; registry&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Map&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 1. 登记办事处：把类和它需要的依赖存进来&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ClassRef&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.registry.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(name, { ClassRef, dependencies });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 2. 核心大招：顺藤摸瓜，递归提车&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; target&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.registry.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(name);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;target) &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;} 还未注册`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 递归去拿所有的依赖（如果依赖还有依赖，就继续向下挖）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; resolvedDeps&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; target.dependencies.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;depName&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(depName),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 把查到的所有依赖实例，当成参数原封不动地丢进构造函数！&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; target.&lt;/span&gt;&lt;span&gt;ClassRef&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;resolvedDeps);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// ============ 测试一下 ============&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; container&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 提前告诉管家：他们分别是谁，依赖什么？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;container.&lt;/span&gt;&lt;span&gt;register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Storage&quot;&lt;/span&gt;&lt;span&gt;, StorageService);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;container.&lt;/span&gt;&lt;span&gt;register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Auth&quot;&lt;/span&gt;&lt;span&gt;, AuthService, [&lt;/span&gt;&lt;span&gt;&quot;Storage&quot;&lt;/span&gt;&lt;span&gt;]); &lt;/span&gt;&lt;span&gt;// Auth需要Storage&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;container.&lt;/span&gt;&lt;span&gt;register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;User&quot;&lt;/span&gt;&lt;span&gt;, UserService, [&lt;/span&gt;&lt;span&gt;&quot;Auth&quot;&lt;/span&gt;&lt;span&gt;]); &lt;/span&gt;&lt;span&gt;// User需要Auth&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 业务端发话了：管家，把你弄好的 User 给我！&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; user&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; container.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;User&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 管家收到：发车！内部自动执行了 get(&apos;Auth&apos;) -&amp;gt; get(&apos;Storage&apos;) 帮你 new 到底！&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(注：真实的 InversifyJS 或 Angular 容器比这更强大，它们能缓存实例（即单例模式），而且借助 TypeScript 的 &lt;code&gt;@Injectable()&lt;/code&gt; 装饰器和 &lt;code&gt;reflect-metadata&lt;/code&gt;，它内部能自动读取到你的构造函数里写了什么类型，你甚至连 &lt;code&gt;[&apos;Storage&apos;]&lt;/code&gt; 这种依赖名字都不需要手写了！)&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;原型模式（Prototype Pattern）&lt;a href=&quot;#原型模式prototype-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：满地都是的臃肿图表配置&lt;a href=&quot;#前端真实场景满地都是的臃肿图表配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我们在做后台大屏时，往往需要用到 ECharts。一个 ECharts 的 &lt;code&gt;option&lt;/code&gt; 配置对象通常长达上百行，包括标题、图例、网格、提示框、X 轴、Y 轴等等。
现在一屏里要画 5 个图表（4 个折线图，1 个柱状图），长得几乎一模一样，只有数据不同。&lt;/p&gt;
&lt;h3&gt;第一反应：疯狂复制粘贴字典对象&lt;a href=&quot;#第一反应疯狂复制粘贴字典对象&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 图表A&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; chartAOption&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title: { text: &lt;/span&gt;&lt;span&gt;&quot;销量&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  tooltip: { trigger: &lt;/span&gt;&lt;span&gt;&quot;axis&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  grid: { left: &lt;/span&gt;&lt;span&gt;&quot;3%&quot;&lt;/span&gt;&lt;span&gt;, right: &lt;/span&gt;&lt;span&gt;&quot;4%&quot;&lt;/span&gt;&lt;span&gt;, bottom: &lt;/span&gt;&lt;span&gt;&quot;3%&quot;&lt;/span&gt;&lt;span&gt;, containLabel: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  xAxis: { type: &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  yAxis: { type: &lt;/span&gt;&lt;span&gt;&quot;value&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  series: [{ type: &lt;/span&gt;&lt;span&gt;&quot;line&quot;&lt;/span&gt;&lt;span&gt;, data: [&lt;/span&gt;&lt;span&gt;120&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;150&lt;/span&gt;&lt;span&gt;] }],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 图表B（直接把上面那一大坨复制过来改改数据）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; chartBOption&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title: { text: &lt;/span&gt;&lt;span&gt;&quot;访客&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  tooltip: { trigger: &lt;/span&gt;&lt;span&gt;&quot;axis&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  grid: { left: &lt;/span&gt;&lt;span&gt;&quot;3%&quot;&lt;/span&gt;&lt;span&gt;, right: &lt;/span&gt;&lt;span&gt;&quot;4%&quot;&lt;/span&gt;&lt;span&gt;, bottom: &lt;/span&gt;&lt;span&gt;&quot;3%&quot;&lt;/span&gt;&lt;span&gt;, containLabel: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  xAxis: { type: &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  yAxis: { type: &lt;/span&gt;&lt;span&gt;&quot;value&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  series: [{ type: &lt;/span&gt;&lt;span&gt;&quot;line&quot;&lt;/span&gt;&lt;span&gt;, data: [&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;900&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;700&lt;/span&gt;&lt;span&gt;] }],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：一处 UI 改动，处处漏改&lt;a href=&quot;#灾难一处-ui-改动处处漏改&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;代码极度膨胀&lt;/strong&gt;：5 个图表，500 行代码，翻都翻不到底。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;维护噩梦&lt;/strong&gt;：产品经理说“把图表边距 bottom 从 3% 统一改成 5%”。你得在成堆的代码里挨个 Ctrl+F 去找 &lt;code&gt;grid&lt;/code&gt; 属性。万一漏改了哪怕一个，界面就出现了不对齐。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：原型模式&lt;a href=&quot;#解决思路原型模式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;原型模式的本质：&lt;strong&gt;先捏一个尽量完美的“泥人（模板）”，后面全靠这个泥人“克隆（拷贝）”，只对克隆体做微调。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过 &lt;code&gt;Object.create&lt;/code&gt; 或 &lt;code&gt;structuredClone&lt;/code&gt;（深拷贝）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//  先定一个“原生泥人”（Base 原型）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; baseChartContext&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  tooltip: { trigger: &lt;/span&gt;&lt;span&gt;&quot;axis&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  grid: { left: &lt;/span&gt;&lt;span&gt;&quot;5%&quot;&lt;/span&gt;&lt;span&gt;, right: &lt;/span&gt;&lt;span&gt;&quot;4%&quot;&lt;/span&gt;&lt;span&gt;, bottom: &lt;/span&gt;&lt;span&gt;&quot;5%&quot;&lt;/span&gt;&lt;span&gt;, containLabel: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  xAxis: { type: &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  yAxis: { type: &lt;/span&gt;&lt;span&gt;&quot;value&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//  接下来全靠拷贝微调&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; chartAOption&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; structuredClone&lt;/span&gt;&lt;span&gt;(baseChartContext);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;chartAOption.title &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { text: &lt;/span&gt;&lt;span&gt;&quot;销量&quot;&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;chartAOption.series &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [{ type: &lt;/span&gt;&lt;span&gt;&quot;line&quot;&lt;/span&gt;&lt;span&gt;, data: [&lt;/span&gt;&lt;span&gt;120&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;150&lt;/span&gt;&lt;span&gt;] }];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; chartBOption&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; structuredClone&lt;/span&gt;&lt;span&gt;(baseChartContext);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;chartBOption.title &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { text: &lt;/span&gt;&lt;span&gt;&quot;访客&quot;&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;chartBOption.series &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [{ type: &lt;/span&gt;&lt;span&gt;&quot;line&quot;&lt;/span&gt;&lt;span&gt;, data: [&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;900&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;700&lt;/span&gt;&lt;span&gt;] }];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;边距怎么改？只改 &lt;code&gt;baseChartContext&lt;/code&gt; 那一个地方，瞬间生效全场。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;建造者模式（Builder Pattern）&lt;a href=&quot;#建造者模式builder-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：复杂的弹窗参数 / 动态查询 API&lt;a href=&quot;#前端真实场景复杂的弹窗参数--动态查询-api&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;写中后台列表页的请求时，我们需要处理一系列复杂的查询条件（关键词、页码、条数、状态枚举、时间范围、排序字段）。&lt;/p&gt;
&lt;h3&gt;第一反应：塞出一个无敌长的参数列表&lt;a href=&quot;#第一反应塞出一个无敌长的参数列表&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; fetchTableData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  keyword&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  page&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  status&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  startDate&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  endDate&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  sortBy&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 一大坨拼 url 的逻辑&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//  业务里调用时，场面极其惨烈：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fetchTableData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;apple&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;price&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者稍微好一点，封装成一个大 Object：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;fetchTableData&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  keyword: &lt;/span&gt;&lt;span&gt;&quot;apple&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  page: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  sortBy: &lt;/span&gt;&lt;span&gt;&quot;price&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但这还是没解决组装条件时，大量的判空逻辑散布在业务组件中的问题。&lt;/p&gt;
&lt;h3&gt;灾难：极差的可读性与扩展逻辑的散落&lt;a href=&quot;#灾难极差的可读性与扩展逻辑的散落&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一旦我们需要根据用户点击不同的选项，渐进式地往里加参数（先设了时间，再设了状态，最后才点击发送），大参数对象会被传来传去，谁都可以在中途直接 &lt;code&gt;params.status = 1&lt;/code&gt; 把它修改掉。哪天查不出数据了，你完全不知道参数是在哪一行被破坏的。&lt;/p&gt;
&lt;h3&gt;解决思路：建造者模式&lt;a href=&quot;#解决思路建造者模式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;建造者模式的思想是：&lt;strong&gt;把一个复杂对象的创建过程“碎片化、流水线化”，通过链式调用，一步一步地把它“建造”出来，最后再出厂（build）。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//  创建一个查询条件的“施工队”&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; QueryBuilder&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  private&lt;/span&gt;&lt;span&gt; params&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { page: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, size: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 各种 set 方法，总是 return this 以实现链式调用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setKeyword&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;word&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (word) &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.params.keyword &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; word;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setPaging&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.params.page &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; page;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.params.size &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; size;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setDateRange&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (start &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; end) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.params.startDate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; start;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.params.endDate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; end;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  //  最后一道工序：出厂&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  build&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.params;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有了它，业务代码的可读性直线上升：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; finalParams&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; QueryBuilder&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;setKeyword&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;apple&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;setPaging&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;setDateRange&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;2026-05-01&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;2026-05-31&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;axios.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/api/goods&quot;&lt;/span&gt;&lt;span&gt;, { params: finalParams });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不光优雅，还在内部消灭了烦穿人的 &lt;code&gt;if (xxx !== undefined)&lt;/code&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;对象池模式（Object Pool）&lt;a href=&quot;#对象池模式object-pool&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：满屏的春节红包雨 / Canvas 子弹效果&lt;a href=&quot;#前端真实场景满屏的春节红包雨--canvas-子弹效果&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在做双十一大促的“金币雨”特效，或者 Canvas 网页游戏里的机枪扫射时，屏幕上短时间内会出现成百上千个不断下落又消失的元素。&lt;/p&gt;
&lt;h3&gt;第一反应：疯狂创建与销毁 DOM / 对象&lt;a href=&quot;#第一反应疯狂创建与销毁-dom--对象&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 每 10 毫秒就 new 一枚新金币&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setInterval&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; coin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; CoinNode&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  document.body.&lt;/span&gt;&lt;span&gt;appendChild&lt;/span&gt;&lt;span&gt;(coin.el);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 下落 3 秒后销毁&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setTimeout&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    coin.el.&lt;/span&gt;&lt;span&gt;remove&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    coin.&lt;/span&gt;&lt;span&gt;destroy&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：GC（垃圾回收）榨干了浏览器性能&lt;a href=&quot;#灾难gc垃圾回收榨干了浏览器性能&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在 V8 引擎（浏览器底层）里，创建对象要申请内存，销毁对象需要等待 Garbage Collection 去清理。
如果你每秒创建 100 个金币，同时又有一堆金币销毁，&lt;strong&gt;GC 就会疯狂启动&lt;/strong&gt;。GC 启动时会阻塞主线程（Stop-the-world），进而导致你的动画出现肉眼可见的“卡顿、掉帧”。&lt;/p&gt;
&lt;h3&gt;解决思路：对象池模式&lt;a href=&quot;#解决思路对象池模式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;对象池不算是经典 GoF 的 23 种设计模式，却是前端渲染（不仅是 DOM，包括 WebGL）优化的神兵利器。
核心思想：&lt;strong&gt;像图书馆借书一样。提前造好一堆对象放在池子里。要用就“借”，用完“隐藏并还”回池子，绝不销毁。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; CoinPool&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  private&lt;/span&gt;&lt;span&gt; pool&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; CoinNode&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 1. 初始化时，直接造 50 个隐藏的硬币塞进池子&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; size; i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; coin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; CoinNode&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      coin.&lt;/span&gt;&lt;span&gt;hide&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.pool.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(coin);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 2. 借出：不 new，直接从池子里捞一个闲置的拿去复用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  acquire&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; CoinNode&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 找到还在池子里的硬币推出&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; coin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.pool.&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (coin) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; coin;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 实在不够了再补 new&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; CoinNode&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 3. 归还：隐藏，坐标归零，塞回池子接着等下一波&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  release&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;coin&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; CoinNode&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    coin.&lt;/span&gt;&lt;span&gt;hide&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    coin.&lt;/span&gt;&lt;span&gt;resetPosition&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.pool.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(coin);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;业务端逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; pool&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; CoinPool&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 先备好 100 发子弹&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setInterval&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; coin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; pool.&lt;/span&gt;&lt;span&gt;acquire&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// 借用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  coin.&lt;/span&gt;&lt;span&gt;startFalling&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setTimeout&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    pool.&lt;/span&gt;&lt;span&gt;release&lt;/span&gt;&lt;span&gt;(coin); &lt;/span&gt;&lt;span&gt;// 落下去了？回收！下一波接着用它。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过这种方式，动画全程没有任何一个对象被销毁，GC 彻底处于休眠状态，帧率稳如老狗。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结：创建型模式到底在帮你什么？&lt;a href=&quot;#总结创建型模式到底在帮你什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;把这几种模式放在一起看，它们其实都在做一件事：&lt;strong&gt;削弱“创建细节”对业务代码的污染。&lt;/strong&gt;&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模式&lt;/th&gt;&lt;th&gt;重点&lt;/th&gt;&lt;th&gt;解决什么耦合&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;工厂模式&lt;/td&gt;&lt;td&gt;统一创建入口&lt;/td&gt;&lt;td&gt;业务代码判断繁琐、不再到处 &lt;code&gt;new&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;单例模式&lt;/td&gt;&lt;td&gt;全局唯一实例&lt;/td&gt;&lt;td&gt;避免各种重复创建连累性能与状态不同步&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;依赖注入&lt;/td&gt;&lt;td&gt;外部提供依赖&lt;/td&gt;&lt;td&gt;对具体实现的硬绑定，组件树中间层惨遭连累&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;原型模式&lt;/td&gt;&lt;td&gt;基于模板复制&lt;/td&gt;&lt;td&gt;一大坨 JSON 对象重复写，改一次漏五次&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;建造者模式&lt;/td&gt;&lt;td&gt;分步骤打磨&lt;/td&gt;&lt;td&gt;发请求、构造复杂对象时可恶的大参数列表和判空&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;对象池模式&lt;/td&gt;&lt;td&gt;借出与回收&lt;/td&gt;&lt;td&gt;高频创建销毁导致的 GC 掉帧与页面卡死&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;最后用一句前端味更重的话收尾&lt;a href=&quot;#最后用一句前端味更重的话收尾&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;如果一个组件一旦写进去，就必须知道“底层对象怎么造、深渊依赖怎么来、几百行配置怎么拼、渲染实例怎么销毁”，那这个组件就已经过度耦合了。&lt;/p&gt;
&lt;p&gt;创建型模式要做的，就是把这些脏活从你的 Vue/React 业务代码里拿走。
&lt;strong&gt;把“该谁管”划清楚，让组件专心画 UI，让创建的烂摊子交给专门的人去处理。&lt;/strong&gt;&lt;/p&gt;</content:encoded><category>category:笔记</category><category>tag:设计模式</category><category>tag:前端</category><category>tag:创建型模式</category><category>tag:Vue</category><category>tag:TypeScript</category></item><item><title>设计模式：行为型模式</title><link>https://yuki-bloom.vercel.app/ja/post/behavioral-patterns-front-end</link><guid isPermaLink="false">ja:behavioral-patterns-front-end</guid><description>从前端协作与状态流转视角拆解行为型模式，涵盖发布订阅、策略、状态、责任链、命令、模板方法、迭代器、备忘录、中介者、访问者、解释器的实战写法与耦合对照。</description><pubDate>Sun, 31 May 2026 11:50:35 GMT</pubDate><content:encoded>&lt;h2&gt;行为型模式到底在解决什么？&lt;a href=&quot;#行为型模式到底在解决什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;行为型模式关注的不是“对象怎么来”，而是“对象怎么协作、怎么流转、怎么把行为拆开”。前端常见的状态切换、事件联动、流程编排，往往都藏着行为型模式的影子。&lt;/p&gt;
&lt;p&gt;它的本质是：&lt;strong&gt;把变化的行为从业务代码里抽出来&lt;/strong&gt;，让流程不再被一堆 &lt;code&gt;if / else&lt;/code&gt; 驱动，而是被更清晰、更可替换的规则驱动。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一句话总结&lt;/strong&gt; &lt;/p&gt;&lt;div&gt;&lt;/div&gt;“谁来驱动流程 / 谁来响应变化”这件事，从业务里抽出来。&lt;p&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;发布订阅模式（Publish-Subscribe / Observer Pattern）&lt;a href=&quot;#发布订阅模式publish-subscribe--observer-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：跨组件事件通知（Vue2 $bus → Vue3 单向数据流）&lt;a href=&quot;#前端真实场景跨组件事件通知vue2-bus--vue3-单向数据流&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;从 Vue2 的 &lt;code&gt;$bus&lt;/code&gt; 到 Vue3 的 &lt;code&gt;emit&lt;/code&gt;，发布订阅几乎是前端最熟的模式。Vue3 不再推荐 &lt;code&gt;$bus&lt;/code&gt;，是因为它强化了单向数据流：&lt;strong&gt;数据向下、事件向上&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;第一反应：上一个全局事件总线&lt;a href=&quot;#第一反应上一个全局事件总线&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Vue2 时代常见写法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; bus&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Vue&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// A.vue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;bus.&lt;/span&gt;&lt;span&gt;$emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;user:login&quot;&lt;/span&gt;&lt;span&gt;, user);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// B.vue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;bus.&lt;/span&gt;&lt;span&gt;$on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;user:login&quot;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  userStore.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(u);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：事件飞来飞去，谁也不负责&lt;a href=&quot;#灾难事件飞来飞去谁也不负责&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;事件名冲突&lt;/strong&gt;：全局事件越来越多，改一个名字会牵一堆文件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流向不可追踪&lt;/strong&gt;：谁 emit，谁监听，谁清理，完全失控。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监听泄漏&lt;/strong&gt;：组件卸载后忘记 off，事件继续触发。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：手撕发布订阅 + 限定边界&lt;a href=&quot;#解决思路手撕发布订阅--限定边界&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Handler&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; PubSub&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // eventName -&amp;gt; handlers&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  private&lt;/span&gt;&lt;span&gt; events&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Set&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Handler&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;handler&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Handler&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 订阅：返回取消函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.events[event]) &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.events[event] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Set&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.events[event].&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(handler);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;off&lt;/span&gt;&lt;span&gt;(event, handler);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  once&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;handler&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Handler&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 只触发一次&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; off&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(event, (&lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      off&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      handler&lt;/span&gt;&lt;span&gt;(payload);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; off;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  off&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;handler&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Handler&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 取消订阅&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.events[event]?.&lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt;(handler);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 发布事件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.events[event]?.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;handler&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;span&gt;(payload));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 只在“认证模块”内部共享，而不是全局总线&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; authBus&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; PubSub&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; onLogin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Handler&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; authBus.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;login&quot;&lt;/span&gt;&lt;span&gt;, fn);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; emitLogin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  authBus.&lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;login&quot;&lt;/span&gt;&lt;span&gt;, user);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;发布订阅可以用，但必须有边界&lt;/strong&gt;。组件内优先 &lt;code&gt;props + emit&lt;/code&gt;，跨模块再用模块级事件中心或状态库。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;策略模式（Strategy Pattern）&lt;a href=&quot;#策略模式strategy-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：不同会员等级的定价策略&lt;a href=&quot;#前端真实场景不同会员等级的定价策略&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;会员、渠道、节日折扣一多，价格计算就会变成一坨 &lt;code&gt;if/else&lt;/code&gt;。这类“规则变化频繁”的场景，就是策略模式的主场。&lt;/p&gt;
&lt;h3&gt;第一反应：条件分支写到天荒地老&lt;a href=&quot;#第一反应条件分支写到天荒地老&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; calcPrice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;plan&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;base&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (plan &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;free&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; base;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (plan &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;vip&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; base &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 0.9&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (plan &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;svip&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; base &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 0.8&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; base;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：新增一个策略等于改核心逻辑&lt;a href=&quot;#灾难新增一个策略等于改核心逻辑&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;分支越来越长&lt;/strong&gt;：新增“学生价 / 员工价”就得改核心函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测试负担飙升&lt;/strong&gt;：每次新增都要回归所有分支。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：策略对象化，Context 执行策略&lt;a href=&quot;#解决思路策略对象化context-执行策略&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; PricingContext&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  coupon&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; PricingStrategy&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 每个策略只实现自己的算法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  algorithm&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;base&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PricingContext&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; FreeStrategy&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; PricingStrategy&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  algorithm&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;base&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PricingContext&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 免费会员不打折，保留优惠券&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; base &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; (ctx.coupon &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; VipStrategy&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; PricingStrategy&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  algorithm&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;base&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PricingContext&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // VIP 9 折&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; base &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 0.9&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; (ctx.coupon &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; SvipStrategy&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; PricingStrategy&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  algorithm&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;base&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PricingContext&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // SVIP 8 折&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; base &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 0.8&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; (ctx.coupon &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; PricingContextExecutor&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // Context 持有策略，可动态替换&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; strategy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PricingStrategy&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setStrategy&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;strategy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PricingStrategy&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.strategy &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; strategy;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  executeStrategy&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;base&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PricingContext&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 对外统一入口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.strategy.&lt;/span&gt;&lt;span&gt;algorithm&lt;/span&gt;&lt;span&gt;(base, ctx);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; PricingContextExecutor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; VipStrategy&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; price&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; context.&lt;/span&gt;&lt;span&gt;executeStrategy&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;, { coupon: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;策略模式把“变化点”集中到策略里，Context 只负责执行。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;状态模式（State Pattern）&lt;a href=&quot;#状态模式state-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：支付按钮的状态流转&lt;a href=&quot;#前端真实场景支付按钮的状态流转&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一个“立即支付”按钮背后有明确状态：&lt;code&gt;idle → paying → success / failed&lt;/code&gt;。没有状态机，按钮逻辑很快就变成“全靠人脑记忆”。&lt;/p&gt;
&lt;h3&gt;第一反应：用多个布尔值硬撑&lt;a href=&quot;#第一反应用多个布尔值硬撑&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  isPaying: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  isSuccess: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  isFailed: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：非法组合 + 分支爆炸&lt;a href=&quot;#灾难非法组合--分支爆炸&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态互相打架&lt;/strong&gt;：&lt;code&gt;isPaying&lt;/code&gt; 和 &lt;code&gt;isSuccess&lt;/code&gt; 可能同时为 true。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;按钮文案失控&lt;/strong&gt;：有人忘了更新 &lt;code&gt;disabled&lt;/code&gt;，导致重复下单。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：用状态机约束“能做什么”&lt;a href=&quot;#解决思路用状态机约束能做什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; PayState&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;idle&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;paying&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;success&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;failed&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; PayAction&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;submit&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;resolve&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;reject&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;reset&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 状态转移表：只允许合法流转&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; transitions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;PayState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Partial&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;PayAction&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;PayState&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  idle: { submit: &lt;/span&gt;&lt;span&gt;&quot;paying&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  paying: { resolve: &lt;/span&gt;&lt;span&gt;&quot;success&quot;&lt;/span&gt;&lt;span&gt;, reject: &lt;/span&gt;&lt;span&gt;&quot;failed&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  success: { reset: &lt;/span&gt;&lt;span&gt;&quot;idle&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  failed: { reset: &lt;/span&gt;&lt;span&gt;&quot;idle&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// UI 映射表：状态决定渲染&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; viewState&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;PayState&lt;/span&gt;&lt;span&gt;, { &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;disabled&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt; }&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  idle: { text: &lt;/span&gt;&lt;span&gt;&quot;立即支付&quot;&lt;/span&gt;&lt;span&gt;, disabled: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  paying: { text: &lt;/span&gt;&lt;span&gt;&quot;支付中...&quot;&lt;/span&gt;&lt;span&gt;, disabled: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  success: { text: &lt;/span&gt;&lt;span&gt;&quot;支付成功&quot;&lt;/span&gt;&lt;span&gt;, disabled: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  failed: { text: &lt;/span&gt;&lt;span&gt;&quot;支付失败，重试&quot;&lt;/span&gt;&lt;span&gt;, disabled: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PayState&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;idle&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; dispatch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PayAction&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 通过状态转移表推进&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; transitions[state][action] &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; state;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 状态变化驱动 UI&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  render&lt;/span&gt;&lt;span&gt;(viewState[state]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;状态机让“什么时候能做什么”变成规则，而不是临时约定。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;责任链模式（Chain of Responsibility Pattern）&lt;a href=&quot;#责任链模式chain-of-responsibility-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：请求链路的多级处理&lt;a href=&quot;#前端真实场景请求链路的多级处理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;请求出去之前要鉴权、签名、缓存命中、重试，甚至还要打点上报。每一步都可能“截断流程”。&lt;/p&gt;
&lt;h3&gt;第一反应：把所有判断塞进一个函数&lt;a href=&quot;#第一反应把所有判断塞进一个函数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handleRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;ctx.token) &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;unauthorized&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (ctx.isBlocked) &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;blocked&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (ctx.useCache) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; ctx.cache;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(ctx.url);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：一条链条绑死所有规则&lt;a href=&quot;#灾难一条链条绑死所有规则&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;强耦合&lt;/strong&gt;：规则必须按固定顺序执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不可复用&lt;/strong&gt;：不同模块想改顺序或省略某一步都要改源码。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：把规则拆成可插拔的链&lt;a href=&quot;#解决思路把规则拆成可插拔的链&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Context&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  req&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Request&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  token&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  response&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Middleware&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;&amp;gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; compose&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;middlewares&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Middleware&lt;/span&gt;&lt;span&gt;[]) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Context&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; dispatch&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // next 只能被调用一次&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (i &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; index) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reject&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;next called twice&quot;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      index &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; i;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; fn&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; middlewares[i];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;fn) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 把控制权交给下一个 handler&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; fn&lt;/span&gt;&lt;span&gt;(ctx, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; dispatch&lt;/span&gt;&lt;span&gt;(i &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; dispatch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; withAuth&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Middleware&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 鉴权拦截&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;ctx.token) &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;unauthorized&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 统一塞 token&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ctx.req.headers.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`Bearer ${&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;token&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; next&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; withCache&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Middleware&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 命中缓存则直接返回&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; cached&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; readCache&lt;/span&gt;&lt;span&gt;(ctx.req);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (cached) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ctx.response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cached;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; next&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 回写缓存&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (ctx.response) &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; writeCache&lt;/span&gt;&lt;span&gt;(ctx.req, ctx.response);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; withRetry&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Middleware&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 重试三次&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; next&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (err) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 最后一次才抛出&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (i &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; err;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; fetcher&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Middleware&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 真正发请求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ctx.response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(ctx.req);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; chain&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; compose&lt;/span&gt;&lt;span&gt;([withAuth, withCache, withRetry, fetcher]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; chain&lt;/span&gt;&lt;span&gt;({ req: &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Request&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/api/order&quot;&lt;/span&gt;&lt;span&gt;), token: &lt;/span&gt;&lt;span&gt;&quot;t1&quot;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个规则独立、顺序可换，这就是责任链的价值。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;命令模式（Command Pattern）&lt;a href=&quot;#命令模式command-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：画布编辑器的撤销/重做&lt;a href=&quot;#前端真实场景画布编辑器的撤销重做&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;拖拽、对齐、改名、删除……这些操作都需要“可撤销”。如果直接改数据，历史就很难还原。&lt;/p&gt;
&lt;h3&gt;第一反应：直接改模型&lt;a href=&quot;#第一反应直接改模型&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;layers[index].name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Banner&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：历史被打碎，撤销无从下手&lt;a href=&quot;#灾难历史被打碎撤销无从下手&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;操作不可逆&lt;/strong&gt;：改了就改了，回不去。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重做体系缺失&lt;/strong&gt;：撤销后再操作，历史乱成一团。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：把操作封装为命令&lt;a href=&quot;#解决思路把操作封装为命令&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; Command&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 执行与撤销&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  execute&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  undo&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; CommandManager&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 维护撤销/重做栈&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  private&lt;/span&gt;&lt;span&gt; undoStack&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Command&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  private&lt;/span&gt;&lt;span&gt; redoStack&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Command&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cmd&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Command&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 执行后写入撤销栈&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    cmd.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.undoStack.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(cmd);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 新操作会清空重做栈&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.redoStack &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  undo&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; cmd&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.undoStack.&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;cmd) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    cmd.&lt;/span&gt;&lt;span&gt;undo&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.redoStack.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(cmd);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  redo&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; cmd&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.redoStack.&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;cmd) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    cmd.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.undoStack.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(cmd);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MoveLayerCommand&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; Command&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    private&lt;/span&gt;&lt;span&gt; layer&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    private&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    private&lt;/span&gt;&lt;span&gt; to&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  execute&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 执行移动&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.layer.x &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.to.x;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.layer.y &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.to.y;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  undo&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 撤销移动&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.layer.x &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.from.x;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.layer.y &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.from.y;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; manager&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; CommandManager&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;manager.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; MoveLayerCommand&lt;/span&gt;&lt;span&gt;(layer, { x: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, y: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; }, { x: &lt;/span&gt;&lt;span&gt;120&lt;/span&gt;&lt;span&gt;, y: &lt;/span&gt;&lt;span&gt;40&lt;/span&gt;&lt;span&gt; }));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;manager.&lt;/span&gt;&lt;span&gt;undo&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;命令模式让“操作”变成对象，撤销/重做变成基础能力。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;模板方法模式（Template Method Pattern）&lt;a href=&quot;#模板方法模式template-method-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：列表页加载流程高度一致&lt;a href=&quot;#前端真实场景列表页加载流程高度一致&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;列表页几乎都在做同一件事：loading → 拉数据 → 归一化 → 渲染 → 错误处理 → 打点。&lt;/p&gt;
&lt;h3&gt;第一反应：每个页面都写一套流程&lt;a href=&quot;#第一反应每个页面都写一套流程&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; raw&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetchList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; normalize&lt;/span&gt;&lt;span&gt;(raw);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setList&lt;/span&gt;&lt;span&gt;(data);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  report&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;list_loaded&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (err) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  toast&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;加载失败&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：重复 + 不一致&lt;a href=&quot;#灾难重复--不一致&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;逻辑重复&lt;/strong&gt;：每个页面一套“loading + error”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;细节不一致&lt;/strong&gt;：有的页面没打点，有的页面没兜底。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：固定骨架，差异留给子类&lt;a href=&quot;#解决思路固定骨架差异留给子类&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;abstract&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; BaseListLoader&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  async&lt;/span&gt;&lt;span&gt; load&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 模板方法：固定流程骨架&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;before&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; raw&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;normalize&lt;/span&gt;&lt;span&gt;(raw);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;after&lt;/span&gt;&lt;span&gt;(data);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; data;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (err) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 统一错误处理入口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onError&lt;/span&gt;&lt;span&gt;(err);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      throw&lt;/span&gt;&lt;span&gt; err;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 统一收尾入口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; before&lt;/span&gt;&lt;span&gt;() {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; after&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_data&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;[]) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; onError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_err&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; unknown&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; finally&lt;/span&gt;&lt;span&gt;() {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; abstract&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; abstract&lt;/span&gt;&lt;span&gt; normalize&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; UserListLoader&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; BaseListLoader&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; before&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 钩子：loading&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 钩子：拉数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; http.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/api/users&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; normalize&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 钩子：格式归一化&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; raw.data;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; after&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 钩子：打点&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    report&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;user_list_loaded&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; onError&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 钩子：错误提示&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    toast&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;加载失败&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  protected&lt;/span&gt;&lt;span&gt; finally&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 钩子：收尾&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;骨架流程稳定，差异逻辑只需要覆盖少量钩子。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;迭代器模式（Iterator Pattern）&lt;a href=&quot;#迭代器模式iterator-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：统一遍历路由树与权限树&lt;a href=&quot;#前端真实场景统一遍历路由树与权限树&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;路由树、菜单树、权限树本质都是树。遍历逻辑散落在业务里，会导致“每个模块写一套递归”。&lt;/p&gt;
&lt;h3&gt;第一反应：每次都手写递归&lt;a href=&quot;#第一反应每次都手写递归&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; walk&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  node.children?.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(walk);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  doSomething&lt;/span&gt;&lt;span&gt;(node);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：遍历规则不统一&lt;a href=&quot;#灾难遍历规则不统一&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先序/后序各写一套&lt;/strong&gt;：不同模块遍历顺序不一致。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;难以复用&lt;/strong&gt;：过滤、聚合、统计都要重复造轮子。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：把遍历变成可迭代协议&lt;a href=&quot;#解决思路把遍历变成可迭代协议&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; RouteNode&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  path&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  meta&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;hidden&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  children&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; RouteNode&lt;/span&gt;&lt;span&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function*&lt;/span&gt;&lt;span&gt; traverse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;nodes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; RouteNode&lt;/span&gt;&lt;span&gt;[])&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Generator&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;RouteNode&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 先序遍历&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; node&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; nodes) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    yield&lt;/span&gt;&lt;span&gt; node;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (node.children) &lt;/span&gt;&lt;span&gt;yield*&lt;/span&gt;&lt;span&gt; traverse&lt;/span&gt;&lt;span&gt;(node.children);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; RouteTree&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; Iterable&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;RouteNode&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; roots&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; RouteNode&lt;/span&gt;&lt;span&gt;[]) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [Symbol.iterator]() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 让树可迭代&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; traverse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.roots);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; tree&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; RouteTree&lt;/span&gt;&lt;span&gt;(routes);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; visiblePaths&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; node&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; tree) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 业务侧只关心“拿到节点”&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;node.meta?.hidden) visiblePaths.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(node.path);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;迭代器让遍历方式可复用，业务只管消费节点。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;备忘录模式（Memento Pattern）&lt;a href=&quot;#备忘录模式memento-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：复杂表单的“撤销修改/恢复草稿”&lt;a href=&quot;#前端真实场景复杂表单的撤销修改恢复草稿&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;表单字段多、逻辑多时，经常要“回到上一步”或者“恢复到上次保存”。如果直接拿着业务对象硬改，就很难回退。&lt;/p&gt;
&lt;h3&gt;第一反应：每次改动都深拷贝一份&lt;a href=&quot;#第一反应每次改动都深拷贝一份&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;history.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(formState)));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：内存暴涨 + 回退难以控制&lt;a href=&quot;#灾难内存暴涨--回退难以控制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;全量拷贝成本高&lt;/strong&gt;：字段一多，拷贝与存储都顶不住。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回退粒度混乱&lt;/strong&gt;：什么时候保存、保存几份，全靠人记。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：把“状态快照”抽成备忘录&lt;a href=&quot;#解决思路把状态快照抽成备忘录&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Draft&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  content&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  tags&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; DraftMemento&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 快照对象（只读）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; readonly&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Draft&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; DraftOriginator&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Draft&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Draft&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 修改业务状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; next;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  createMemento&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 生成快照&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; DraftMemento&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.state, tags: [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.state.tags] });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  restore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;memento&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; DraftMemento&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 恢复快照&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;memento.state, tags: [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;memento.state.tags] };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  getState&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 对外读取当前状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.state;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; originator&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; DraftOriginator&lt;/span&gt;&lt;span&gt;({ title: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;, content: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;, tags: [] });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// caretaker：维护快照历史&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; history&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; DraftMemento&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;history.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(originator.&lt;/span&gt;&lt;span&gt;createMemento&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;originator.&lt;/span&gt;&lt;span&gt;setState&lt;/span&gt;&lt;span&gt;({ title: &lt;/span&gt;&lt;span&gt;&quot;草稿 A&quot;&lt;/span&gt;&lt;span&gt;, content: &lt;/span&gt;&lt;span&gt;&quot;内容&quot;&lt;/span&gt;&lt;span&gt;, tags: [&lt;/span&gt;&lt;span&gt;&quot;vue&quot;&lt;/span&gt;&lt;span&gt;] });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; last&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; history.&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (last) originator.&lt;/span&gt;&lt;span&gt;restore&lt;/span&gt;&lt;span&gt;(last);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;备忘录把“保存/恢复”变成显式动作，撤销逻辑就有了抓手。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;中介者模式（Mediator Pattern）&lt;a href=&quot;#中介者模式mediator-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：筛选面板的联动逻辑&lt;a href=&quot;#前端真实场景筛选面板的联动逻辑&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;品牌、价格区间、库存状态、促销标签之间经常互相影响。组件之间互相调用，会变成“你改我、我改他”的耦合地狱。&lt;/p&gt;
&lt;h3&gt;第一反应：组件彼此直接引用&lt;a href=&quot;#第一反应组件彼此直接引用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;brandSelect.&lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; priceSlider.&lt;/span&gt;&lt;span&gt;setRange&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;]));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;priceSlider.&lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; tagPanel.&lt;/span&gt;&lt;span&gt;disable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;promo&quot;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：联动规则散落，改一处崩一片&lt;a href=&quot;#灾难联动规则散落改一处崩一片&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;双向依赖&lt;/strong&gt;：A 调 B，B 又调 A。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逻辑难维护&lt;/strong&gt;：联动规则散落在各个组件里。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：用中介者统一协调&lt;a href=&quot;#解决思路用中介者统一协调&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; MediatorEvent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;brand:change&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;price:change&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;] }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;reset&quot;&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; FilterMediator&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 中介者统一协调组件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  private&lt;/span&gt;&lt;span&gt; components&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Set&lt;/span&gt;&lt;span&gt;&amp;lt;{ &lt;/span&gt;&lt;span&gt;reset&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; }&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;component&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;reset&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.components.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(component);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  notify&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sender&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; unknown&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; MediatorEvent&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 根据事件协调组件行为&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (event.type &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;reset&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.components.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; c.&lt;/span&gt;&lt;span&gt;reset&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (event.type &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;brand:change&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 规则集中在中介者&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (event.value &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;官方自营&quot;&lt;/span&gt;&lt;span&gt;) priceSlider.&lt;/span&gt;&lt;span&gt;setRange&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; mediator&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; FilterMediator&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; brandSelect&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  reset&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setBrand&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 变化只通知中介者&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  change&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; mediator.&lt;/span&gt;&lt;span&gt;notify&lt;/span&gt;&lt;span&gt;(brandSelect, { type: &lt;/span&gt;&lt;span&gt;&quot;brand:change&quot;&lt;/span&gt;&lt;span&gt;, value }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; priceSlider&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  reset&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setPrice&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;999&lt;/span&gt;&lt;span&gt;]),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 暴露被中介者调用的 API&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setRange&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setPrice&lt;/span&gt;&lt;span&gt;(range),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mediator.&lt;/span&gt;&lt;span&gt;register&lt;/span&gt;&lt;span&gt;(brandSelect);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mediator.&lt;/span&gt;&lt;span&gt;register&lt;/span&gt;&lt;span&gt;(priceSlider);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;中介者把“组件之间的对话”集中到一个地方，耦合大幅降低。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;访问者模式（Visitor Pattern）&lt;a href=&quot;#访问者模式visitor-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：组件树的“统计/导出/渲染”多种操作&lt;a href=&quot;#前端真实场景组件树的统计导出渲染多种操作&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;设计器里的节点既要渲染，也要导出 JSON，还要统计图片数量。如果把逻辑都塞进节点类，节点会爆炸。&lt;/p&gt;
&lt;h3&gt;第一反应：在每个节点里塞一堆方法&lt;a href=&quot;#第一反应在每个节点里塞一堆方法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Node&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  render&lt;/span&gt;&lt;span&gt;() {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  exportJson&lt;/span&gt;&lt;span&gt;() {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  countImages&lt;/span&gt;&lt;span&gt;() {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：节点越来越臃肿&lt;a href=&quot;#灾难节点越来越臃肿&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;新增行为必须改所有节点&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逻辑难复用&lt;/strong&gt;：统计/导出/渲染互相污染。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：访问者把“行为”独立出来&lt;a href=&quot;#解决思路访问者把行为独立出来&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; Element&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 接受访问者&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  accept&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;visitor&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Visitor&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; TextElement&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; Element&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; text&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  accept&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;visitor&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Visitor&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 双分派：把自己交给访问者&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    visitor.&lt;/span&gt;&lt;span&gt;visitText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; ImageElement&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; Element&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  accept&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;visitor&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Visitor&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    visitor.&lt;/span&gt;&lt;span&gt;visitImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; Visitor&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 不同节点的访问入口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  visitText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; TextElement&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  visitImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ImageElement&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; ExportVisitor&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; Visitor&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 导出结果&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  public&lt;/span&gt;&lt;span&gt; output&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  visitText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; TextElement&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 文本节点导出&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.output.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({ type: &lt;/span&gt;&lt;span&gt;&quot;text&quot;&lt;/span&gt;&lt;span&gt;, value: el.text });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  visitImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ImageElement&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 图片节点导出&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.output.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({ type: &lt;/span&gt;&lt;span&gt;&quot;image&quot;&lt;/span&gt;&lt;span&gt;, src: el.src });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; elements&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Element&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; TextElement&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; ImageElement&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/logo.png&quot;&lt;/span&gt;&lt;span&gt;)];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; visitor&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; ExportVisitor&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 遍历节点，交给访问者处理&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;elements.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; el.&lt;/span&gt;&lt;span&gt;accept&lt;/span&gt;&lt;span&gt;(visitor));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;访问者让“行为”可插拔，节点本体保持干净。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;解释器模式（Interpreter Pattern）&lt;a href=&quot;#解释器模式interpreter-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：可配置的筛选表达式&lt;a href=&quot;#前端真实场景可配置的筛选表达式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;筛选表达式从“勾选条件”升级到“输入语法”，比如 &lt;code&gt;tag:react AND (type:post OR type:note)&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;第一反应：在一个函数里手写解析&lt;a href=&quot;#第一反应在一个函数里手写解析&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (query.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;AND&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; query.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;OR&quot;&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 很快变成一坨 if/else&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：规则复杂，维护成本指数级&lt;a href=&quot;#灾难规则复杂维护成本指数级&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;扩展困难&lt;/strong&gt;：新增 NOT、括号就要重写。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;难以测试&lt;/strong&gt;：解析逻辑与业务逻辑纠缠。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：把表达式建成语法树&lt;a href=&quot;#解决思路把表达式建成语法树&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Context&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;tag&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; Expression&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 解释表达式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  interpret&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Context&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; TagExpression&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; Expression&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  interpret&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Context&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 终结符表达式：判断 tag&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; ctx.tag &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.value;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; TypeExpression&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; Expression&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  interpret&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Context&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 终结符表达式：判断 type&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; ctx.type &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.value;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; AndExpression&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; Expression&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; left&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Expression&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; right&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Expression&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  interpret&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Context&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 组合表达式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.left.&lt;/span&gt;&lt;span&gt;interpret&lt;/span&gt;&lt;span&gt;(ctx) &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.right.&lt;/span&gt;&lt;span&gt;interpret&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; OrExpression&lt;/span&gt;&lt;span&gt; implements&lt;/span&gt;&lt;span&gt; Expression&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; left&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Expression&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; right&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Expression&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  interpret&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Context&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.left.&lt;/span&gt;&lt;span&gt;interpret&lt;/span&gt;&lt;span&gt;(ctx) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.right.&lt;/span&gt;&lt;span&gt;interpret&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; evaluate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Expression&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 统一入口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; expression.&lt;/span&gt;&lt;span&gt;interpret&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; expr&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; AndExpression&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  new&lt;/span&gt;&lt;span&gt; TagExpression&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  new&lt;/span&gt;&lt;span&gt; OrExpression&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; TypeExpression&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;post&quot;&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; TypeExpression&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;note&quot;&lt;/span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;evaluate&lt;/span&gt;&lt;span&gt;({ tag: &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;, type: &lt;/span&gt;&lt;span&gt;&quot;note&quot;&lt;/span&gt;&lt;span&gt; }, expr);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解释器把“语法”变成对象，规则扩展也就有路可走。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结：行为型模式到底在帮你什么？&lt;a href=&quot;#总结行为型模式到底在帮你什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;行为型模式解决的是“协作与流程问题”。它让系统更像一个可编排的流程，而不是一堆互相牵扯的 if/else。&lt;/p&gt;

































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模式&lt;/th&gt;&lt;th&gt;重点&lt;/th&gt;&lt;th&gt;解决什么耦合&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;发布订阅模式&lt;/td&gt;&lt;td&gt;事件解耦&lt;/td&gt;&lt;td&gt;组件之间的直接依赖&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;策略模式&lt;/td&gt;&lt;td&gt;规则替换&lt;/td&gt;&lt;td&gt;条件分支与核心流程的绑定&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;状态模式&lt;/td&gt;&lt;td&gt;状态驱动&lt;/td&gt;&lt;td&gt;多状态布尔组合的混乱&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;责任链模式&lt;/td&gt;&lt;td&gt;流程分段&lt;/td&gt;&lt;td&gt;规则组合与顺序调整的困难&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;命令模式&lt;/td&gt;&lt;td&gt;操作对象化&lt;/td&gt;&lt;td&gt;不可撤销的操作与历史记录缺失&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;模板方法模式&lt;/td&gt;&lt;td&gt;固定流程骨架&lt;/td&gt;&lt;td&gt;重复流程带来的维护成本&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;迭代器模式&lt;/td&gt;&lt;td&gt;统一遍历协议&lt;/td&gt;&lt;td&gt;复杂结构遍历逻辑散落各处&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;备忘录模式&lt;/td&gt;&lt;td&gt;状态快照&lt;/td&gt;&lt;td&gt;撤销/恢复与业务状态的强耦合&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;中介者模式&lt;/td&gt;&lt;td&gt;协调通信&lt;/td&gt;&lt;td&gt;组件互相直接引用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;访问者模式&lt;/td&gt;&lt;td&gt;行为外置&lt;/td&gt;&lt;td&gt;行为扩展导致的节点类膨胀&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;解释器模式&lt;/td&gt;&lt;td&gt;规则语法化&lt;/td&gt;&lt;td&gt;解析规则与业务逻辑的强耦合&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;最后用一句前端味更重的话收尾&lt;a href=&quot;#最后用一句前端味更重的话收尾&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;当你的业务开始“指挥谁先干、谁后干、谁失败要重试”，行为型模式就在提醒你：&lt;strong&gt;把流程调度交出去，业务就会更轻&lt;/strong&gt;。&lt;/p&gt;</content:encoded><category>category:笔记</category><category>tag:设计模式</category><category>tag:前端</category><category>tag:行为型模式</category><category>tag:Vue</category><category>tag:TypeScript</category></item><item><title>设计模式：结构型模式</title><link>https://yuki-bloom.vercel.app/ja/post/structural-patterns-front-end</link><guid isPermaLink="false">ja:structural-patterns-front-end</guid><description>从前端视角拆解结构型模式，围绕接口适配、功能扩展与性能权衡，给出适配器、装饰器、代理、桥接、组合、外观、享元的实战代码与耦合对照。</description><pubDate>Sun, 31 May 2026 11:41:42 GMT</pubDate><content:encoded>&lt;h2&gt;结构型模式到底在解决什么？&lt;a href=&quot;#结构型模式到底在解决什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;结构型模式的核心，只有一个问题：&lt;strong&gt;如何组合类或对象&lt;/strong&gt;。它的出发点不是“再发明一个轮子”，而是&lt;strong&gt;在不破坏原有结构的前提下，通过继承或组合去扩展新功能，让原来的结构变得更强大&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它通常在以下这些场景出现：接口不兼容但又不能全盘重写；系统需要动态扩展功能但不想动核心；以及在“更细粒度、更高效率”之间做权衡时，想要一条更稳的路。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一句话总结&lt;/strong&gt; &lt;/p&gt;&lt;div&gt;&lt;/div&gt;“怎么连接/拼装”这件事，和“怎么使用”分开。&lt;p&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;适配器模式（Adapter Pattern）&lt;a href=&quot;#适配器模式adapter-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：后端统一调整了响应体结构&lt;a href=&quot;#前端真实场景后端统一调整了响应体结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Axios 是前端的“空气”，但空气也会突然被后端改配方。比如原来后端约定的响应体是 &lt;code&gt;{ code, data, msg }&lt;/code&gt;，现在一换版本，变成了 &lt;code&gt;{ status, payload, message }&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;第一反应：全项目一处处改字段&lt;a href=&quot;#第一反应全项目一处处改字段&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; axios.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/api/user&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (res.data.status &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  userStore.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(res.data.payload);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：改不完、改不稳、改了还会漏&lt;a href=&quot;#灾难改不完改不稳改了还会漏&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;调用点满地开花&lt;/strong&gt;：你要改的不是一个接口，而是 200 个页面。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;历史模块崩盘&lt;/strong&gt;：老代码依旧在拿 &lt;code&gt;code&lt;/code&gt; 和 &lt;code&gt;data&lt;/code&gt;，很容易出现“业务没报错但数据全空”的隐性灾难。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：给 axios 装一个适配器&lt;a href=&quot;#解决思路给-axios-装一个适配器&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; LegacyResult&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; NewResult&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; adaptResponse&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; NewResult&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; LegacyResult&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    code: raw.status,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    data: raw.payload,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    msg: raw.message,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; http&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; axios.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;http.interceptors.response.&lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;span&gt;res,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 统一回旧协议，业务层无需改动&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    data: &lt;/span&gt;&lt;span&gt;adaptResponse&lt;/span&gt;&lt;span&gt;(res.data),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; User&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getUser&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; http.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;LegacyResult&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;`/api/user/${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (res.data.code &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(res.data.msg);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; res.data.data;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;业务层继续写老协议，变更只集中在适配器里，后端怎么换，你只改一个文件。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;装饰器模式（Decorator Pattern）&lt;a href=&quot;#装饰器模式decorator-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：统计每个请求的耗时与性能&lt;a href=&quot;#前端真实场景统计每个请求的耗时与性能&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我们经常要统计接口耗时、加上错误上报，或者做统一的 loading。装饰器的核心就是：&lt;strong&gt;不改原函数，只给它套一个“外壳”&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;第一反应：在函数内部直接加逻辑&lt;a href=&quot;#第一反应在函数内部直接加逻辑&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; fetchUser&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; start&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; performance.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; http.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`/api/user/${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; cost&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; performance.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; start;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  reportPerf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;fetchUser&quot;&lt;/span&gt;&lt;span&gt;, cost);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; res.data;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：横切逻辑污染业务函数&lt;a href=&quot;#灾难横切逻辑污染业务函数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;每个函数都要塞一遍&lt;/strong&gt;：计时、上报、日志，全散在业务里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;改一个策略要全局改&lt;/strong&gt;：性能埋点的规则变了，你要翻遍所有函数。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：装饰器把“横切关注点”抽离&lt;a href=&quot;#解决思路装饰器把横切关注点抽离&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Timer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 装饰器工厂：包一层计时逻辑&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;_target&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; unknown&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;descriptor&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PropertyDescriptor&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; original&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; descriptor.value &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;[]) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;unknown&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    descriptor.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;[]) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; start&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; performance.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; original.&lt;/span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, args);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 无论成功失败都上报耗时&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        reportPerf&lt;/span&gt;&lt;span&gt;(name &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; key, performance.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; start);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; descriptor;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; UserService&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @&lt;/span&gt;&lt;span&gt;Timer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;fetchUser&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  async&lt;/span&gt;&lt;span&gt; fetchUser&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; http.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`/api/user/${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; res.data;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;直接用 TypeScript 装饰器语法&lt;/strong&gt;，业务函数不用改，横切逻辑集中在装饰器里。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;代理模式（Proxy Pattern）&lt;a href=&quot;#代理模式proxy-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：响应式状态与自动更新&lt;a href=&quot;#前端真实场景响应式状态与自动更新&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Vue 3 能做到“改数据 -&amp;gt; UI 自动更新”，核心并不是“自动变魔术”，而是 &lt;code&gt;Proxy&lt;/code&gt; 拦截 &lt;code&gt;get/set&lt;/code&gt;，在 &lt;code&gt;get&lt;/code&gt; 时&lt;strong&gt;收集依赖&lt;/strong&gt;，在 &lt;code&gt;set&lt;/code&gt; 时&lt;strong&gt;触发更新&lt;/strong&gt;。这就是“响应式系统”的本体。&lt;/p&gt;
&lt;h3&gt;第一反应：手动维护状态和通知&lt;a href=&quot;#第一反应手动维护状态和通知&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { count: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; listeners&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Array&lt;/span&gt;&lt;span&gt;&amp;lt;() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; setCount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  state.count &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; n;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  listeners.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; fn&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：你在手工写一个残次版响应式系统&lt;a href=&quot;#灾难你在手工写一个残次版响应式系统&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;可维护性极差&lt;/strong&gt;：每个字段都要写 setter。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一致性失控&lt;/strong&gt;：有人直接改 &lt;code&gt;state.count&lt;/code&gt;，监听就断了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：手写一个最小可用的响应式&lt;a href=&quot;#解决思路手写一个最小可用的响应式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; EffectFn&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// target -&amp;gt; key -&amp;gt; effects&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; bucket&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WeakMap&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Map&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;PropertyKey&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Set&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;EffectFn&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 当前正在执行的副作用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; activeEffect&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; EffectFn&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; effect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; EffectFn&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 注册副作用：执行时会触发依赖收集&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; runner&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    activeEffect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; runner;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fn&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    activeEffect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  runner&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; track&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; object&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PropertyKey&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 依赖收集：记录“谁在用这个 key”&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;activeEffect) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; depsMap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; bucket.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(target);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;depsMap) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    depsMap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Map&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    bucket.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(target, depsMap);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; deps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; depsMap.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(key);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;deps) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    deps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Set&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    depsMap.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(key, deps);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  deps.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(activeEffect);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; trigger&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; object&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PropertyKey&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 触发更新：把依赖这个 key 的副作用全部重新执行&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  bucket.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(target)?.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(key)?.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; fn&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; reactive&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; object&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;obj&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Proxy&lt;/span&gt;&lt;span&gt;(obj, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;receiver&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Reflect.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(target, key, receiver);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // get 时收集依赖&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      track&lt;/span&gt;&lt;span&gt;(target, key);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; res;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;receiver&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; old&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Reflect.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(target, key, receiver);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Reflect.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(target, key, value, receiver);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // set 时触发更新&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (old &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; value) &lt;/span&gt;&lt;span&gt;trigger&lt;/span&gt;&lt;span&gt;(target, key);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; result;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; reactive&lt;/span&gt;&lt;span&gt;({ count: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;effect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; el&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;#count&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (el) el.textContent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;(state.count);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;state.count &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;业务不碰核心对象，只操作代理。&lt;strong&gt;依赖在 get 时收集、更新在 set 时触发&lt;/strong&gt;，这就是 Vue3 响应式的关键脉络。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;桥接模式（Bridge Pattern）&lt;a href=&quot;#桥接模式bridge-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：同一套 UI 对接多种数据源&lt;a href=&quot;#前端真实场景同一套-ui-对接多种数据源&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;同一个图表组件既要展示“实时接口数据”，又要展示“本地 mock 数据”。如果把数据逻辑写死在组件里，UI 会被绑架。&lt;/p&gt;
&lt;h3&gt;第一反应：组件里写一堆 if/else&lt;a href=&quot;#第一反应组件里写一堆-ifelse&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; renderChart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sourceType&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;api&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;mock&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (sourceType &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;api&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 拉接口 -&amp;gt; 转换 -&amp;gt; 渲染&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 读取本地数据 -&amp;gt; 渲染&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：数据源越多，组件越像屎山&lt;a href=&quot;#灾难数据源越多组件越像屎山&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;UI 组件承包所有业务&lt;/strong&gt;：数据源、转换规则、渲染细节全混一起。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组合爆炸&lt;/strong&gt;：数据源 _ 主题 _ 平台，组合数无限膨胀。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：桥接“UI 与数据源/渲染器”&lt;a href=&quot;#解决思路桥接ui-与数据源渲染器&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; DataSource&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;[]&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Renderer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; HTMLElement&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;series&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;[]) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; createApiSource&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; DataSource&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; http.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/api/sales&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; res.data;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; createMockSource&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; DataSource&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;120&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;90&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;150&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; renderBar&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Renderer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;series&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  el.innerHTML &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; series.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; `&amp;lt;div class=&quot;bar&quot; style=&quot;height:${&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;}px&quot;&amp;gt;&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; renderLine&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Renderer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;series&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  el.innerHTML &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; `&amp;lt;svg&amp;gt;${&lt;/span&gt;&lt;span&gt;series&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; `&amp;lt;circle cx=&quot;${&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; 40&lt;/span&gt;&lt;span&gt;}&quot; cy=&quot;${&lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span&gt;}&quot; r=&quot;4&quot;/&amp;gt;`&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&amp;lt;/svg&amp;gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; mountChart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;el&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; HTMLElement&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; DataSource&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Renderer&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 桥接：数据源与渲染器独立，组合时再拼接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; series&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; source&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  renderer&lt;/span&gt;&lt;span&gt;(el, series);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// React/Vue 都可以这样用：数据源与渲染器自由组合&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; el&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;#chart&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; HTMLElement&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mountChart&lt;/span&gt;&lt;span&gt;(el, &lt;/span&gt;&lt;span&gt;createApiSource&lt;/span&gt;&lt;span&gt;(), renderBar);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UI 组件只关心“怎么画”，数据源只关心“从哪来”，&lt;strong&gt;组合靠桥接拼装&lt;/strong&gt;，不会互相牵连。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;组合模式（Composite Pattern）&lt;a href=&quot;#组合模式composite-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：文件夹与文件的统一管理&lt;a href=&quot;#前端真实场景文件夹与文件的统一管理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文件系统里既有“文件夹”，也有“文件”。对 UI 来说都要渲染、都要统计大小、都要支持展开，但内部结构完全不同。&lt;/p&gt;
&lt;h3&gt;第一反应：写一堆判断&lt;a href=&quot;#第一反应写一堆判断&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; renderNode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (node.children) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    node.children.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(renderNode);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    renderFile&lt;/span&gt;&lt;span&gt;(node);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：你在手写“叶子/容器”分支地狱&lt;a href=&quot;#灾难你在手写叶子容器分支地狱&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;每次调用都要判断类型&lt;/strong&gt;：递归逻辑越来越复杂。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扩展节点类型困难&lt;/strong&gt;：新增“分组”/“分割线”时全得改。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：统一成一种“节点”接口&lt;a href=&quot;#解决思路统一成一种节点接口&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; FileNode&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;file&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;indent&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; FolderNode&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;folder&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Node&lt;/span&gt;&lt;span&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Node&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;indent&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Node&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; FileNode&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; FolderNode&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; createFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; FileNode&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  type: &lt;/span&gt;&lt;span&gt;&quot;file&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; bytes,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  print&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;indent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;indent&lt;/span&gt;&lt;span&gt;}- ${&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;} (${&lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;}b)`&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; createFolder&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Node&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [])&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; FolderNode&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  type: &lt;/span&gt;&lt;span&gt;&quot;folder&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  children,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Node&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    children.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(node);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 汇总子节点大小&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; children.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;child&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; total &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; child.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  print&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;indent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 递归打印树结构&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; lines&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;indent&lt;/span&gt;&lt;span&gt;}+ ${&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}/`&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    children.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;child&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; lines.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;child.&lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;indent&lt;/span&gt;&lt;span&gt;}  `&lt;/span&gt;&lt;span&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; lines;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; root&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; createFolder&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;src&quot;&lt;/span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  createFile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;main.ts&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1200&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  createFolder&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;components&quot;&lt;/span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;span&gt;createFile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Button.tsx&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;)]),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;root.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;createFolder&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;assets&quot;&lt;/span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;span&gt;createFile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;logo.png&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2048&lt;/span&gt;&lt;span&gt;)]));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(root.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(root.&lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;文件与文件夹都实现同一套节点接口&lt;/strong&gt;，上层只管调用 &lt;code&gt;size()&lt;/code&gt; / &lt;code&gt;print()&lt;/code&gt;，不用知道它是单体还是组合。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;外观模式（Facade Pattern）&lt;a href=&quot;#外观模式facade-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：上传流程 = 压缩 + 鉴权 + 上传 + 通知&lt;a href=&quot;#前端真实场景上传流程--压缩--鉴权--上传--通知&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一个“上传头像”背后可能串了 4 个模块。如果每个页面都手动拼这些流程，维护将变成灾难。&lt;/p&gt;
&lt;h3&gt;第一反应：每个页面手写一套流程&lt;a href=&quot;#第一反应每个页面手写一套流程&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; compress&lt;/span&gt;&lt;span&gt;(file);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; token&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetchUploadToken&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; uploadFile&lt;/span&gt;&lt;span&gt;(file, token);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; notifyProfile&lt;/span&gt;&lt;span&gt;(url);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：流程散落，错误处理碎一地&lt;a href=&quot;#灾难流程散落错误处理碎一地&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;每个页面都得 copy 一遍&lt;/strong&gt;：改个接口名要全局替换。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误处理完全不一致&lt;/strong&gt;：有的吞错，有的弹窗，有的啥也不做。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：外观模式统一入口&lt;a href=&quot;#解决思路外观模式统一入口&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; UploadOptions&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  onProgress&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;percent&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  onError&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; unknown&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; uploadAvatar&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; File&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UploadOptions&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {}) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 对外只暴露一个入口，内部步骤统一编排&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; compressed&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; compress&lt;/span&gt;&lt;span&gt;(file);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; token&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetchUploadToken&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; uploadFile&lt;/span&gt;&lt;span&gt;(compressed, token, options.onProgress);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; notifyProfile&lt;/span&gt;&lt;span&gt;(url);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; url;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (err) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    options.&lt;/span&gt;&lt;span&gt;onError&lt;/span&gt;&lt;span&gt;?.(err);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    throw&lt;/span&gt;&lt;span&gt; err;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// UI 侧只关心一个入口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; uploadAvatar&lt;/span&gt;&lt;span&gt;(file, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  onProgress&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setProgress&lt;/span&gt;&lt;span&gt;(Math.&lt;/span&gt;&lt;span&gt;round&lt;/span&gt;&lt;span&gt;(p &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  onError&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; toast&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;上传失败&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对外只有一个入口，复杂流程被“挡在门后”，这就是外观模式的价值。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;享元模式（Flyweight Pattern）&lt;a href=&quot;#享元模式flyweight-pattern&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;前端真实场景：地图上成千上万的标记点&lt;a href=&quot;#前端真实场景地图上成千上万的标记点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;地图上有 1 万个点，它们的样式都一样，只有坐标不同。你如果每个点都 &lt;code&gt;new&lt;/code&gt; 一份图标对象，内存直接顶满。&lt;/p&gt;
&lt;h3&gt;第一反应：每个点都独立创建图标&lt;a href=&quot;#第一反应每个点都独立创建图标&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; markers&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; points.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Marker&lt;/span&gt;&lt;span&gt;(p, &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Icon&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;shop&quot;&lt;/span&gt;&lt;span&gt;)));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;灾难：内存暴涨 + 初始化卡顿&lt;a href=&quot;#灾难内存暴涨--初始化卡顿&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;对象重复创建&lt;/strong&gt;：1 万个 icon 明明长一样，却重复造了 1 万次。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渲染启动时卡顿&lt;/strong&gt;：初始化全在主线程上堆着。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决思路：共享“内在状态”，外部传“外在状态”&lt;a href=&quot;#解决思路共享内在状态外部传外在状态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; IconFactory&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  private&lt;/span&gt;&lt;span&gt; cache&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Map&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Icon&lt;/span&gt;&lt;span&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 相同类型复用同一个 Icon&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.cache.&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(type)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.cache.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(type, &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Icon&lt;/span&gt;&lt;span&gt;(type));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.cache.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(type)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; iconFactory&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; IconFactory&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; markers&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; points.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  position: p, &lt;/span&gt;&lt;span&gt;// 外在状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  icon: iconFactory.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;shop&quot;&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;// 内在状态共享&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; renderMarker&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;marker&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;position&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt; }; &lt;/span&gt;&lt;span&gt;icon&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Icon&lt;/span&gt;&lt;span&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  drawIcon&lt;/span&gt;&lt;span&gt;(marker.icon, marker.position);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要图标类型不变，Icon 永远复用，内存压力瞬间降一个数量级。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结：结构型模式到底在帮你什么？&lt;a href=&quot;#总结结构型模式到底在帮你什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;结构型模式关心的不是“对象怎么来”，而是&lt;strong&gt;对象怎么被拼接与复用&lt;/strong&gt;。它让结构能长大，但不会把原有系统撕碎。&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模式&lt;/th&gt;&lt;th&gt;重点&lt;/th&gt;&lt;th&gt;解决什么耦合&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;适配器模式&lt;/td&gt;&lt;td&gt;兼容新旧接口&lt;/td&gt;&lt;td&gt;接口变化导致的全量改动&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;装饰器模式&lt;/td&gt;&lt;td&gt;横切能力叠加&lt;/td&gt;&lt;td&gt;业务逻辑与统计/监控/日志的混杂&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;代理模式&lt;/td&gt;&lt;td&gt;间接访问控制&lt;/td&gt;&lt;td&gt;直接操作对象导致的状态失控与无拦截点&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;桥接模式&lt;/td&gt;&lt;td&gt;抽象与实现分离&lt;/td&gt;&lt;td&gt;UI 与数据源/主题的组合爆炸&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;组合模式&lt;/td&gt;&lt;td&gt;树形一致访问&lt;/td&gt;&lt;td&gt;叶子与容器的分支判断地狱&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;外观模式&lt;/td&gt;&lt;td&gt;统一入口&lt;/td&gt;&lt;td&gt;多模块流程散落、错误处理不一致&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;享元模式&lt;/td&gt;&lt;td&gt;共享内在状态&lt;/td&gt;&lt;td&gt;重复对象带来的内存与性能浪费&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;最后用一句前端味更重的话收尾&lt;a href=&quot;#最后用一句前端味更重的话收尾&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;当你发现一个组件需要知道“内部结构到底拼了多少层、谁要先执行、谁要复用”，它已经开始失控了。&lt;/p&gt;
&lt;p&gt;结构型模式要做的，就是把“拼结构的脏活”收回去，让业务层只管用、别管怎么连。&lt;/p&gt;</content:encoded><category>category:笔记</category><category>tag:设计模式</category><category>tag:前端</category><category>tag:结构型模式</category><category>tag:Vue</category><category>tag:TypeScript</category></item><item><title>平平无奇打工人的一周</title><link>https://yuki-bloom.vercel.app/ja/post/weekly-2026-05-17</link><guid isPermaLink="false">ja:weekly-2026-05-17</guid><description>记录一次被“AI 智能 CR”蹂躏、在复杂上线流程中挣扎，以及在字节和百度面试中成长的打工人的一周。</description><pubDate>Sun, 17 May 2026 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这一周，怎么说呢，平平无奇但又如鲠在喉。&lt;/p&gt;
&lt;p&gt;标准的打工人一周，不仅在复杂的公司流程里反复横跳，还经历了一场猝不及防的裁员风波。唯一的慰藉大概是从面试中学到了一些东西，并对未来的跑路（划掉）职业规划有了更清晰的打算。&lt;/p&gt;
&lt;h2&gt;职场：被 AI 和流程“支配”的一周&lt;a href=&quot;#职场被-ai-和流程支配的一周&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;智能 CR 的“死循环”&lt;a href=&quot;#智能-cr-的死循环&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;周一本来准备走上线流程，结果领教了所谓的“智能 CR”。
只要你改了代码 push，就会触发 CR；根据 CR 的建议改完再 push，它又会重新触发 CR。感觉陷入了一个无尽的递归，真的难绷。到最后，能拒绝的我都让 AI 帮我写理由拒绝了。
&lt;strong&gt;让 AI 给我一个理由，去拒绝另一个 AI，这很 Cyberpunk。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;上线流程的“九九八十一难”&lt;a href=&quot;#上线流程的九九八十一难&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;因为我的需求涉及到四个仓库的变更，那个上线流程简直麻烦到想哭：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先要找到部署主干道。&lt;/li&gt;
&lt;li&gt;因为是 Release 到发布模型，会自动创建一个 Release 分支。&lt;/li&gt;
&lt;li&gt;上线时要锁死分支。&lt;/li&gt;
&lt;li&gt;找测试要“准入准出”报告。&lt;/li&gt;
&lt;li&gt;提交上线单并等待 Main 测试。&lt;/li&gt;
&lt;li&gt;完成 +1 审批。&lt;/li&gt;
&lt;li&gt;如果是白名单需求，还要在线上环境下进行回测。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最绝的是，第一次准备上时，产品突然要加个字段。后端说因为比较固定，让我前端直接写死。这一写死，我就得重新改三个仓库，撤销上线单，重新走 CR、MR、提交、找人审批……
周四又因为正好撞上别人上线同一仓库，我又在修 Bug，只能让人家先上，我这边的流程又得重头再来。
到了周五早上总算上完了，下午回测却发现后端逻辑有问题，需要后端修复。
&lt;strong&gt;结果：&lt;/strong&gt; 原定周二上线的需求，周五依然卡在白名单里没上线成功。心态真的有点崩。&lt;/p&gt;
&lt;h3&gt;突如其来的离职风波&lt;a href=&quot;#突如其来的离职风波&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;周一晚上被告知有同事要离职，让我去接手。起初以为是人家的主动选择，后来才知道是杭州那边裁了很多人。
由于我们这边总共才 8 个人，结果也开了 2 个，还有一个转岗了。人少了，可需求一点没少，甚至还得去碰那个极其麻烦的“低代码”平台。&lt;/p&gt;
&lt;h3&gt;Token 危机&lt;a href=&quot;#token-危机&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;公司之前的 Token 限制是 2kw，现在改成了什么积分制。作为实习生，积分少得可怜，三天就能用完一个月的量。虽然能提额，但也只有三倍。感觉完全不够撑过一个月。&lt;/p&gt;
&lt;h2&gt;面试：成长的阵痛&lt;a href=&quot;#面试成长的阵痛&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这周参加了两场重量级面试，感受各异。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;字节面试：&lt;/strong&gt;
虽然觉得自己答得不错，但手撕代码能力还是太弱了。在 AI 时代，我总觉得这方面的占比应该可以弱化，但显然大厂还是非常看重硬实力。结果是惨遭“秒挂”，有点打击，但也认清了差距。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;百度面试：&lt;/strong&gt;
这场面试真的获益匪浅，甚至有点后悔没录音。面试官的深度真的离谱，我才知道百度原来藏着这么多大佬。我有一个朋友也在百度的那个组，感觉他们的人均水平都非常高。希望这次能走后续流程，如果能进去，哪怕只是跟着大佬学东西肯定也能飞速成长。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;学习与计划&lt;a href=&quot;#学习与计划&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;即使现在的工作干得心累（低代码业务跨度大、各平台逻辑不统一、开发效率低），但日子总要往前走。&lt;/p&gt;
&lt;h3&gt;夯实算法与总结&lt;a href=&quot;#夯实算法与总结&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;利用周末的时间总结了下简历对应的知识点，还有关于 AI 开发的一些心得。虽然 Token 受限，但思路不能断。接下来要开始死磕算法了，为秋招做准备。&lt;/p&gt;
&lt;h3&gt;拜读经典&lt;a href=&quot;#拜读经典&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这星期的 Reading List：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;《设计模式》：希望能对现在的杂乱代码有点启发。&lt;/li&gt;
&lt;li&gt;《人月神话》：去体会一下为什么“向进度落后的项目中增加人手只会使其进度更落后”。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;碎碎念&lt;a href=&quot;#碎碎念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;其实好想跑路，但一想到 6.6 是快手十五周年，总想白嫖完活动再走（&amp;gt;_&amp;lt;）。
但说认真的，目前的工作让我感到业务理解成本极高，但技术提升有限。确实希望能找个更好的去处，去学习更多真正的核心业务逻辑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;下周加油吧！&lt;/strong&gt;&lt;/p&gt;</content:encoded><category>category:周刊</category><category>tag:周刊</category><category>tag:职场</category><category>tag:面试</category></item><item><title>iframe 沙箱管控与 Schema 版本演进</title><link>https://yuki-bloom.vercel.app/ja/post/lowcode-engine-iframe-sandbox-schema</link><guid isPermaLink="false">ja:lowcode-engine-iframe-sandbox-schema</guid><description>深度解析低代码场景下的 iframe 预览管控体系，包括双触发握手机制、响应式视口模拟及沙箱安全防御，同时探讨 Schema 驱动配置下的版本升级与数据清洗策略。</description><pubDate>Sat, 16 May 2026 12:25:00 GMT</pubDate><content:encoded>&lt;h2&gt;模块一：iframe 跨域预览与微前端管控体系&lt;a href=&quot;#模块一iframe-跨域预览与微前端管控体系&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在低代码或 CMS 系统中，iframe 是实现预览隔离的标准方案。但要做到“生产级”稳定，必须解决生命周期同步与安全隔离问题。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;面试避坑&lt;/strong&gt;：绝不能把 &lt;code&gt;postMessage&lt;/code&gt; 比作 TCP。TCP 是底层的持久连接协议，而 &lt;code&gt;postMessage&lt;/code&gt; 只是应用层的异步事件订阅/发布模型。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 双触发机制（解决生命周期竞态问题）&lt;a href=&quot;#1-双触发机制解决生命周期竞态问题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;痛点&lt;/strong&gt;：父页面直接发消息时，iframe 可能尚未挂载或初始化完成，导致消息丢失。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;机制（状态机握手）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;触发 1（子找父）&lt;/strong&gt;：iframe 内部组件 &lt;code&gt;onMounted&lt;/code&gt; 后，主动发送 &lt;code&gt;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;触发 2（父找子）&lt;/strong&gt;：父页面监听到 &lt;code&gt;READY&lt;/code&gt; 信号后，再将配置数据通过 &lt;code&gt;postMessage&lt;/code&gt; 安全下发。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 视口模拟（解决响应式失效陷阱）&lt;a href=&quot;#2-视口模拟解决响应式失效陷阱&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;误区&lt;/strong&gt;：仅在外层 div 使用 &lt;code&gt;transform: scale&lt;/code&gt;。这属于“视觉后渲染”，并未改变 iframe 内真实的 &lt;code&gt;window.innerWidth&lt;/code&gt;，会导致 CSS 媒体查询（@media）和 vw/vh 单位失效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正解（双层架构）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;内层（定死物理尺寸）&lt;/strong&gt;：强行修改 iframe 的 width 和 height 为目标机型真实像素（如 375px），以激活移动端响应式代码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外层（动态缩放）&lt;/strong&gt;：监听可视区剩余空间，计算比例（如 0.75），用 &lt;code&gt;transform: scale&lt;/code&gt; 将外层容器等比缩放。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 健壮性与安全防御&lt;a href=&quot;#3-健壮性与安全防御&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;沙箱隔离（Sandbox）&lt;/strong&gt;：使用 &lt;code&gt;sandbox=&quot;allow-scripts allow-same-origin&quot;&lt;/code&gt;。这是浏览器级的硬隔离，防范第三方模板恶意劫持顶层页面（修改 &lt;code&gt;window.parent.location&lt;/code&gt;）或弹出干扰 UI。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;超时保护&lt;/strong&gt;：父页面加载时开启定时器。若 5 秒内未收到 &lt;code&gt;READY&lt;/code&gt; 信号，立刻销毁 iframe 并展示兜底 Error UI，避免用户死等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 父子应用状态哲学&lt;a href=&quot;#4-父子应用状态哲学&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单向数据流&lt;/strong&gt;：父应用（控制台）是“大脑”，掌控 Schema 数据流转；子应用（iframe）是“哑巴组件”，不保存业务状态，只负责接收 JSON 并渲染视图。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;模块二：Schema 驱动配置与版本演进体系&lt;a href=&quot;#模块二schema-驱动配置与版本演进体系&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Schema 驱动的核心思想是“数据即视图”，用 JSON 替代硬编码，实现组件的动态插拔。&lt;/p&gt;
&lt;h3&gt;1. 配置闭环全链路&lt;a href=&quot;#1-配置闭环全链路&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;定义&lt;/strong&gt;：用 JSON 描述组件结构（字段、类型、校验规则）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置&lt;/strong&gt;：引擎解析 Schema，自动生成可视化输入面板。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渲染&lt;/strong&gt;：真实数据存入数据库，预览端（iframe）拿到数据后动态反解并挂载组件。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2. 版本升级难题（面试杀手锏）&lt;a href=&quot;#2-版本升级难题面试杀手锏&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;痛点&lt;/strong&gt;：当组件库升级（如 V1 只需要 &lt;code&gt;color&lt;/code&gt;，V2 改成了 &lt;code&gt;style.color&lt;/code&gt;），数据库里存的几千条历史数据会因结构不符导致渲染崩溃。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;破局解法（版本戳 + 适配器模式）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;打版本戳&lt;/strong&gt;：所有存入数据库的 Schema 必须带有显式的版本号声明（如 &lt;code&gt;__version: &quot;1.0.0&quot;&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据清洗层（Migration Pipeline）&lt;/strong&gt;：在引擎渲染前加入拦截器。若发现是旧版数据，通过预写的适配函数（Adapter），自动将其 Map（转换）成最新结构。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;收益&lt;/strong&gt;：保证了底层渲染引擎的纯粹性，永远只按最新结构开发。历史包袱全部由清洗层“消化”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:笔记</category><category>tag:低代码</category><category>tag:iframe</category><category>tag:Schema</category><category>tag:架构设计</category><category>tag:安全</category></item><item><title>破解水合错误（Hydration Mismatch）与性能调优</title><link>https://yuki-bloom.vercel.app/ja/post/ssr-hydration-mismatch-architecture</link><guid isPermaLink="false">ja:ssr-hydration-mismatch-architecture</guid><description>深度解析 SSR 水合错误的底层机制、高频触发场景，结合大厂工程实践提供同构数据预取与按需挂载方案，并探讨 Serverless 架构下的 SSR 性能瓶颈与状态污染风险。</description><pubDate>Sat, 16 May 2026 12:15:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. 为什么会触发水合错误？（底层细节）&lt;a href=&quot;#1-为什么会触发水合错误底层细节&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;核心本质&lt;/strong&gt;：
SSR 的流程是两步走的。第一步：服务端（Node.js）把 Vue/React 组件渲染成纯 HTML 字符串发给浏览器（此时页面能看，但点不动）。第二步：浏览器下载完 JS 后，框架会在客户端再把组件“虚拟渲染”一遍（生成 VDOM），并试图把它和刚才接收到的 HTML 结构“严丝合缝”地对齐，然后把点击、滚动等事件绑定上去。这个对齐并绑定的过程就叫&lt;strong&gt;水合（Hydration）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;只要客户端在初始化时计算出的 VDOM，和它看到的真实 DOM（服务端给的 HTML）有任何一丁点不一样，框架就会抛出 &lt;strong&gt;Hydration Mismatch&lt;/strong&gt; 报错。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;四大高频触发场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;时空差异（宿主环境不同）&lt;/strong&gt;：模板里写了 &lt;code&gt;&amp;lt;div&amp;gt;{{ window.innerWidth }}&amp;lt;/div&amp;gt;&lt;/code&gt;。服务端 Node.js 没有 window 对象，直接渲染成空值；而客户端渲染出真实的宽度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态非同构（数据不一致）&lt;/strong&gt;：服务端请求了接口拿到了数据 A 渲染了页面，但没有把数据 A 传给客户端。客户端一接手，发现本地没有数据 A，渲染成了空状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非确定性输出&lt;/strong&gt;：模板里使用了 &lt;code&gt;Math.random()&lt;/code&gt; 或 &lt;code&gt;new Date()&lt;/code&gt;。服务端生成了一个时间戳，客户端接管时又生成了一个新的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非法的 HTML 结构&lt;/strong&gt;：比如 &lt;code&gt;&amp;lt;p&amp;gt;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/p&amp;gt;&lt;/code&gt;。浏览器在解析这段服务端 HTML 时会自动纠错（把 div 挪到 p 外面）。等由于水合时发现 DOM 结构变了，导致对不上。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 怎么解决水合错误？（工程实战拆解）&lt;a href=&quot;#2-怎么解决水合错误工程实战拆解&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在构建高性能 Web 应用时，为了保证首屏极速加载，必须重度依赖 SSR，这就要求我们在架构设计上主动规避水合问题。&lt;/p&gt;
&lt;h3&gt;解法一：同构数据预取 (Isomorphic Data Fetching)&lt;a href=&quot;#解法一同构数据预取-isomorphic-data-fetching&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;解决的问题&lt;/strong&gt;：“状态非同构”导致的不匹配。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原则&lt;/strong&gt;：代码在服务端和客户端都能跑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链路&lt;/strong&gt;：服务端请求数据并渲染 HTML，同时将数据序列化并注入到 HTML 底部的 &lt;code&gt;&amp;lt;script id=&quot;__DATA__&quot;&amp;gt;&lt;/code&gt; 标签里。客户端接手后，直接从该标签里“脱水（Dehydrate）”出数据，确保两端数据源绝对一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;解法二：按需挂载 (Client-Only / On-Demand Mounting)&lt;a href=&quot;#解法二按需挂载-client-only--on-demand-mounting&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;解决的问题&lt;/strong&gt;：“宿主环境不同”或“非确定性输出”导致的不匹配。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链路&lt;/strong&gt;：对于强依赖浏览器 API（如 CSS 3D 参数、IntersectionObserver）的组件，使用 &lt;code&gt;&amp;lt;ClientOnly&amp;gt;&lt;/code&gt; 包裹，或者在 &lt;code&gt;onMounted&lt;/code&gt; 钩子中执行渲染。服务端渲染时仅显示骨架屏，客户端水合完成后再挂载真实组件。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 开发 / Prod 环境遇到报错的应对策略&lt;a href=&quot;#3-开发--prod-环境遇到报错的应对策略&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;开发环境 (Dev)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;现象&lt;/strong&gt;：控制台打印明确的红色警告（Hydration node mismatch…），指出具体的期望节点与实际节点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;排查&lt;/strong&gt;：定位组件，检查是否误用了 window/document 或数据状态前后端不一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产环境 (Prod)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;现象&lt;/strong&gt;：为了性能，Vue/React 会移除警告。如果发生不匹配，它会默默执行“&lt;strong&gt;放弃水合（Bailout）&lt;/strong&gt;”，销毁服务端 DOM 并重新渲染。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后果&lt;/strong&gt;：出现页面闪烁（FOUC），SSR 优势丧失，转换为普通的 SPA。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控&lt;/strong&gt;：通常需借助 Sentry 捕获异常，或利用 Lighthouse 分析 TTI 是否异常偏高。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 架构师进阶：SSR 核心补充知识点&lt;a href=&quot;#4-架构师进阶ssr-核心补充知识点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;A. 内存泄漏与状态污染（State Pollution）&lt;a href=&quot;#a-内存泄漏与状态污染state-pollution&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;风险&lt;/strong&gt;：在 SSR 中，所有请求共享同一个 Node.js 进程。如果使用全局变量或未在请求结束时销毁状态实例，用户 A 的数据可能会“泄露”给用户 B。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原则&lt;/strong&gt;：必须保证每一次请求，所有的状态中心（如 Pinia/Redux）都是全新实例化的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;B. 核心性能指标&lt;a href=&quot;#b-核心性能指标&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TTFB (Time to First Byte)&lt;/strong&gt;：服务端响应速度。SSR 计算越复杂，TTFB 越高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FCP (First Contentful Paint)&lt;/strong&gt;：内容首次绘制。SSR 极大提升了该指标，让用户更快“看到”页面。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TTI (Time to Interactive)&lt;/strong&gt;：可交互时间。这就是水合完成的时间点。如果 JS 包过大或水合报错，TTI 会被严重拖慢。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;C. Serverless 冷启动 (Cold Start)&lt;a href=&quot;#c-serverless-冷启动-cold-start&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;SSR 应用部署在云函数时，冷启动（加载环境及解析代码）会带来数百毫秒延迟。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略&lt;/strong&gt;：配合骨架屏与边缘计算，或者在冷启动期间提供自动兜底链路，保护前端用户体验。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:笔记</category><category>tag:SSR</category><category>tag:React/Vue</category><category>tag:性能优化</category><category>tag:水合错误</category><category>tag:渲染架构</category></item><item><title>从渲染管线到 3D 动画性能优化</title><link>https://yuki-bloom.vercel.app/ja/post/browser-render-pipeline-3d-animation</link><guid isPermaLink="false">ja:browser-render-pipeline-3d-animation</guid><description>深入解析浏览器渲染管线底层原理，探讨 transform 硬件加速、3D 核心属性以及 JS 配合 rAF 进行高频动画优化的实战技巧与避坑指南。</description><pubDate>Sat, 16 May 2026 12:05:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. 核心底层八股：浏览器的渲染管线 (Render Pipeline)&lt;a href=&quot;#1-核心底层八股浏览器的渲染管线-render-pipeline&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这是所有性能优化的基石，面试官如果深挖，一定会问：为什么 &lt;code&gt;transform&lt;/code&gt; 做动画就不卡？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;标准渲染流程&lt;/strong&gt;：JavaScript -&amp;gt; Style（计算样式） -&amp;gt; Layout（重排/回流） -&amp;gt; Paint（重绘） -&amp;gt; Composite（合成）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;卡顿的元凶&lt;/strong&gt;：如果你用 &lt;code&gt;width&lt;/code&gt;、&lt;code&gt;top&lt;/code&gt;、&lt;code&gt;margin&lt;/code&gt; 来做翻书动画，每一帧都会触发 Layout 和 Paint，这些都在 CPU 的主线程运行，非常昂贵，必然掉帧。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬件加速的原理&lt;/strong&gt;：当你使用 &lt;code&gt;transform&lt;/code&gt; 或 &lt;code&gt;opacity&lt;/code&gt; 做动画时，浏览器极其聪明，它会直接跳过 Layout 和 Paint，直接进入 Composite 阶段。并且，它会将这个元素提取到一个独立的图层 (Compositing Layer)，交由 GPU 去专门处理。GPU 天生就是做矩阵变换和图层叠加的，所以动画会如丝般顺滑。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 实战考点：怎么调用/触发 GPU 硬件加速？&lt;a href=&quot;#2-实战考点怎么调用触发-gpu-硬件加速&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;面试官会问：除了写 &lt;code&gt;transform&lt;/code&gt;，还有什么方法强制开启硬件加速？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;经典 Hack 写法 (Translate3D)&lt;/strong&gt;：&lt;code&gt;transform: translateZ(0)&lt;/code&gt; 或 &lt;code&gt;transform: translate3d(0, 0, 0)&lt;/code&gt;。虽然位移是 0，但这是在欺骗浏览器：“嘿，这是一个 3D 元素，赶紧把它单独放到 GPU 图层里去！”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现代优雅写法 (Will-Change)&lt;/strong&gt;：&lt;code&gt;will-change: transform;&lt;/code&gt;。这是明确告诉浏览器：“这个元素的 transform 属性马上要变了，请提前做好 GPU 资源分配和图层提升准备。”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;还有哪些情况会自动触发&lt;/strong&gt;：&lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; 标签、&lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;、CSS Filter 等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 样式考点：实现立体书必须要懂的 3D 属性&lt;a href=&quot;#3-样式考点实现立体书必须要懂的-3d-属性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;仅仅靠位移是不够的，实现立体翻书效果，你的 CSS 里必须要出现这三个核心属性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;perspective: 800px; (透视/景深)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;没有透视，翻书看起来就像是一个面在不断变窄；加了透视，才有“近大远小”的 3D 纵深感。数值越小，透视感越畸形（贴脸看）；数值越大，越平缓。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;transform-style: preserve-3d; (保留 3D 空间)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;必须加在书本的父容器上！如果不加，当你翻开封面时，封面会被拍扁在父容器的 2D 平面上。加了它，子元素才能真正脱离平面，在 Z 轴上穿插。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;backface-visibility: hidden; (背面不可见)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;超级重要的优化点！当书页翻过去 180 度时，背面其实是不应该被看到的。加上这个属性，GPU 就不需要去计算和绘制翻过去之后的背面像素，极大节省了渲染开销。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;4. 架构考点：JS 怎么配合优化 3D 动画？&lt;a href=&quot;#4-架构考点js-怎么配合优化-3d-动画&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;虽然渲染交给了 GPU，但“翻书的角度（进度）”往往是由用户手指滑动触发的，这就需要 JS 出马了。面试官会问：滑动翻书时，JS 怎么写最流畅？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;使用 requestAnimationFrame (rAF)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;绝不要用 &lt;code&gt;setTimeout&lt;/code&gt; 或 &lt;code&gt;setInterval&lt;/code&gt; 去更新 &lt;code&gt;rotateY&lt;/code&gt; 的角度。rAF 会让你的 JS 执行频率与浏览器的屏幕刷新率（通常是 60fps）绝对同步，避免掉帧。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事件节流 (Throttling) 与被动监听 (Passive Events)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;用户的 &lt;code&gt;touchmove&lt;/code&gt; 事件触发频率极高。在绑定监听器时加上 &lt;code&gt;&lt;/code&gt;，告诉浏览器“我不会调用 &lt;code&gt;preventDefault()&lt;/code&gt; 阻止默认滚动”，这样浏览器主线程就不会等待你的 JS 执行，滑动会立刻响应。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分离读写操作 (避免 Layout Thrashing)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;如果你在滑动时，先读取一下 &lt;code&gt;element.clientWidth&lt;/code&gt;，紧接着立刻修改 &lt;code&gt;element.style.transform&lt;/code&gt;，这会强制浏览器提前进行重排（强制同步布局）。必须把所有的“读”操作集中在一起，所有的“写”操作集中在一起放到 rAF 里执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. 终极避坑（面试加分项）：图层爆炸 (Layer Explosion)&lt;a href=&quot;#5-终极避坑面试加分项图层爆炸-layer-explosion&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果你回答得太好，面试官会抛出最后的杀手锏：硬件加速有什么副作用吗？能滥用吗？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;副作用&lt;/strong&gt;：每一个独立的 GPU 合成层都会消耗额外的内存。如果给页面上所有的元素都加上 &lt;code&gt;will-change: transform&lt;/code&gt;，会导致图层爆炸。手机内存吃紧，反而会让整个页面卡死甚至闪退。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Z-index 陷阱&lt;/strong&gt;：如果一个 &lt;code&gt;z-index&lt;/code&gt; 较低的元素被提升到了 GPU 独立图层，为了保证层叠顺序的正确性，浏览器会被迫把覆盖在它上面的所有 &lt;code&gt;z-index&lt;/code&gt; 较高的普通元素也提升为独立图层（隐式合成）。这也是导致图层爆炸的常见原因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如何治理&lt;/strong&gt;：动画开始前加上 &lt;code&gt;will-change&lt;/code&gt;，动画结束后立刻通过 JS 将其移除（&lt;code&gt;element.style.willChange = &apos;auto&apos;&lt;/code&gt;），把图层交还给浏览器释放内存。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:笔记</category><category>tag:浏览器</category><category>tag:性能优化</category><category>tag:CSS 3D</category><category>tag:渲染原理</category><category>tag:面试</category></item><item><title>如何更好使用 AI（从上下文治理到 MCP 协议）</title><link>https://yuki-bloom.vercel.app/ja/post/how-to-use-ai-better-context-mcp</link><guid isPermaLink="false">ja:how-to-use-ai-better-context-mcp</guid><description>深度解析 AI 使用s的四大治理手段、工具链范式演进以及 MCP 协议的底层逻辑，帮助开发者构建高效、安全的 AI 辅助研发工作流。</description><pubDate>Sat, 16 May 2026 06:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;模块一：上下文治理 —— 解决 AI 的“失忆与注意力涣散”&lt;a href=&quot;#模块一上下文治理--解决-ai-的失忆与注意力涣散&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;大模型的底层逻辑是无状态特性的（Stateless），每次对话都会把历史记录打包成一个巨大的 JSON 数组全量发送。上下文越长，API 越贵，且 AI 越容易产生幻觉（忘记初始设定）。&lt;/p&gt;
&lt;p&gt;为了保持 AI 的“绝对清醒”，工程上衍生出了以下四大治理手段：&lt;/p&gt;
&lt;h3&gt;1. Sub-agent 模式（物理隔离）&lt;a href=&quot;#1-sub-agent-模式物理隔离&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;痛点&lt;/strong&gt;：遇到具体的细节 Bug 时，如果直接在主会话中让 AI 反复试错，这些“垃圾过程”会严重污染上下文，导致主 Agent 忘记全局架构设计。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解法&lt;/strong&gt;：主 Agent 充当架构师（保持上下文纯净）；遇到 Bug，临时拉起一个 Sub-agent（修 Bug 专员）独立试错，成功后只把最终代码返回给主会话。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; 经典使用场景&lt;/strong&gt;：你在开发 C 端阅读平台时，主 Agent 帮你搭好了 Nuxt 4 的整体架构。但在写立体书翻页效果时，CSS 3D 动画一直有透视错错乱的 Bug。此时千万别在主对话里来回贴报错代码，而是把这个独立的翻页组件单独发给一个 Sub-agent 去疯狂调优，调顺滑了，再把最终的组件代码合并回主干。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 对话分支与状态回溯 (Conversation Forking / Backtracking)&lt;a href=&quot;#2-对话分支与状态回溯-conversation-forking--backtracking&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;痛点&lt;/strong&gt;：在解决复杂问题时，AI 经常会给出错误的技术路线（比如引入了一个不兼容的库）。顺着这个路线聊下去，会引发更多连锁报错，导致整个对话陷入“疯狂补锅”的死胡同。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解法&lt;/strong&gt;：像 Git 回滚一样，在对话历史中找到“走偏前的那一条消息”，直接执行回溯（或者 Fork 出一个新分支）。此时不仅代码状态回到了过去，大模型的“脑子”（上下文记录）也被清除了那段失败的试错记忆。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; 经典使用场景&lt;/strong&gt;：你遇到一个 SSR 水合报错 (Hydration Mismatch)。AI 建议你安装一个第三方 NPM 插件来解决，结果安完之后报了 10 个跨域相关的错，你们顺着这 10 个错又排查了 5 轮，越弄越乱。此时直接回退到 AI 提议装插件前的那句话，放弃这条错误路线，让 AI：“这个插件会引发更多问题，请提供一个纯原生的同构预取方案。”&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. /compact 摘要机制（状态压缩）&lt;a href=&quot;#3-compact-摘要机制状态压缩&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;痛点&lt;/strong&gt;：历史对话过长面临 Token 溢出（滑动窗口直接截断会丢失早期设定的关键规范）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解法&lt;/strong&gt;：利用大模型做“记忆快照”。将几十轮废话压缩成高密度的结构化文本，替换掉冗长的原始历史，既省 Token 又防偏题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; 经典使用场景&lt;/strong&gt;：项目开发了一周，当前的对话历史里堆满了你们关于数据库选型、早期表单交互方案的争论。现在你准备开始写核心逻辑了，直接使用 /compact，大模型会在后台把前面的长篇大论压缩成：“【当前状态】前端使用 Vue 3；【核心约定】严格校验类型，不可变数据更新；【下一步】开发文章渲染模块”。让 AI 甩掉历史包袱，轻装上阵。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 代码检索的演进（RAG 的局限与 AST 突围）&lt;a href=&quot;#4-代码检索的演进rag-的局限与-ast-突围&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;痛点&lt;/strong&gt;：传统的向量检索（RAG）靠“语义相似度”找代码极不靠谱，经常把带有相同单词的无关注释找出来，却漏掉了真正互相依赖的函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解法&lt;/strong&gt;：利用 AST（抽象语法树）与符号表检索（Symbol Search）进行精确的上下文提取，让 AI 像真正的编译器一样顺藤摸瓜找依赖。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; 经典使用场景&lt;/strong&gt;：你在开发一个智能体平台，想让 AI 帮你修改 User 相关的状态逻辑。传统的 RAG 可能会找出所有写着 “user” 这个词的无用文件；而通过 ts-morph 等 AST 工具进行静态分析，系统能精确提取出你定义的 TypeScript 接口类型（Interface）、与之关联的全局状态树（Store）以及调用的 API 函数，将最精准的上下文喂给 AI，彻底消灭代码幻觉。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;模块二：工具链范式演进（Toolchain Evolution）—— 如何让 AI 具备行动力&lt;a href=&quot;#模块二工具链范式演进toolchain-evolution-如何让-ai-具备行动力&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;目前的研发效能工具链正在经历从“喂饭式”到“自主式”的演进，核心分为两大流派：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Skill + Bash（敏捷范式）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;本质&lt;/strong&gt;：Prompt as Code。把高频开发场景沉淀为结构化的“技能卡片”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运作&lt;/strong&gt;：不再给 AI 封装繁琐的读写函数，而是直接给它赋予 Bash（终端）权限。AI 像真实程序员一样，遇到问题自己敲 cat 读文件、grep 搜代码。效率极高，是目前 Cursor / Devin 等先进工具的主流玩法。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP 协议（重型防御）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;本质&lt;/strong&gt;：Model Context Protocol，强类型、强定义的 API 边界。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运作&lt;/strong&gt;：人类必须手写具体的工具函数（如 query_db），并定义严格的入参 Schema。AI 只能在规定好的函数列表中选择调用，无法越界。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;模块三：MCP 深度祛魅 —— 穿透黑盒看底层原理&lt;a href=&quot;#模块三mcp-深度祛魅--穿透黑盒看底层原理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;虽然外界把 MCP 传得很神，但作为架构师，必须看透它的物理形态和生态定位。&lt;/p&gt;
&lt;h3&gt;1. 祛魅：它只是一个 Node.js 进程&lt;a href=&quot;#1-祛魅它只是一个-nodejs-进程&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;MCP Server 听起来高大上，剥开来看，它就是一个常驻在后台的普通 Node.js / Python 脚本。它没有任何黑魔法，只是执行着普通的本地文件读写（fs.readFile）或数据库查询逻辑。&lt;/p&gt;
&lt;h3&gt;2. 底层原理与作用（AI 界的 USB 接口）&lt;a href=&quot;#2-底层原理与作用ai-界的-usb-接口&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理&lt;/strong&gt;：它是基于 JSON-RPC 2.0 规范设计的 C/S 架构。大模型客户端（如 Roo Code）通过标准输入输出（stdio）或 HTTP (SSE)，向你的 Node 进程发送规定格式的 JSON 字符串指令；Node 进程处理完后，再以 JSON 格式将结果打印返回。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用&lt;/strong&gt;：统一规范。以前给不同的 AI 写插件要适配不同接口，现在只要按 MCP 规范写一次本地服务，任何支持 MCP 的大模型“插上就能用”，极大降低了给 AI 写“外挂”成本。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 如何实现一个 MCP 服务器&lt;a href=&quot;#3-如何实现一个-mcp-服务器&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;通常不需要从零手写解析逻辑。在 Node.js 环境下，会直接引入官方的 &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;（或 FastMCP 等轻量框架）。实例化 Server 后，调用 SDK 暴露的 API 去注册 Tool（定义函数名、入参 Schema 和执行逻辑），最后启动服务监听 stdio 即可。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;模块四：安全边界与容错率 —— 为什么生产环境必须用 MCP？&lt;a href=&quot;#模块四安全边界与容错率--为什么生产环境必须用-mcp&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;既然 Bash 模式开发效率这么高，为什么在敏感场景或生产环境依然要用看似繁琐的 MCP？这取决于&lt;strong&gt;爆炸半径&lt;/strong&gt;与&lt;strong&gt;容错率&lt;/strong&gt;的差异：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;本地开发环境（适合 Bash，极致提效）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;容错率极高&lt;/strong&gt;：有 Git 作为“后悔药”（&lt;code&gt;git reset --hard&lt;/code&gt; 瞬间恢复）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;人在回路（Human-in-the-loop）&lt;/strong&gt;：危险操作前会有确认弹窗，人类的双眼是最后一道防线。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;爆炸半径小&lt;/strong&gt;：哪怕 AI 产生幻觉执行了 &lt;code&gt;rm -rf /&lt;/code&gt;，损失的也只是一台个人电脑的环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线上生产环境（必须 MCP，极致安全）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据不可逆&lt;/strong&gt;：真实的业务数据库被删改是灾难性的，无法简单撤销。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无人值守&lt;/strong&gt;：Agent 通常在后台异步运行，一旦产生幻觉（大模型的概率性缺陷），没有任何人能拦住它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;爆炸半径极大&lt;/strong&gt;：涉及全量用户资产 and 公司生命线。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP 的核心价值&lt;/strong&gt;：用人类编写的、确定性的沙盒代码（权限校验、只读拦截），去兜底大模型概率性的危险行为。它是关住猛兽的那道防弹玻璃。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:笔记</category><category>tag:AI</category><category>tag:MCP</category><category>tag:研发效能</category><category>tag:上下文治理</category><category>tag:Agent</category></item><item><title>字节跳动_中国交易与广告(上海)面经</title><link>https://yuki-bloom.vercel.app/ja/post/zi-jie-tiao-dong-zhong-guo-jiao-yi-yu-guang-gao-shang-hai-mian-jing</link><guid isPermaLink="false">ja:zi-jie-tiao-dong-zhong-guo-jiao-yi-yu-guang-gao-shang-hai-mian-jing</guid><description>字节跳动_中国交易与广告（上海）前端面经，围绕 DataAgent 项目、MCP/Agent/Skill/RAG、JS 基础、React 数据更新与性能优化、并发调度手撕题展开。</description><pubDate>Tue, 12 May 2026 11:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;字节跳动_中国交易与广告(上海)&lt;a href=&quot;#字节跳动_中国交易与广告上海&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;面试时间&lt;a href=&quot;#面试时间&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;2026_0512-19:00&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;面试内容&lt;a href=&quot;#面试内容&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;自我介绍&lt;/li&gt;
&lt;li&gt;DataAgent 的项目是怎么样的？能介绍一下吗？&lt;/li&gt;
&lt;li&gt;大概说一下这个项目的前端内容有哪些呢？&lt;/li&gt;
&lt;li&gt;这个项目中你主要负责什么？&lt;/li&gt;
&lt;li&gt;AI 返回的 ECharts 图表有考虑过万一 AI 报错了怎么办呢？&lt;/li&gt;
&lt;li&gt;第二个项目中，你有遇到什么难题，挑几个说一下吧。&lt;/li&gt;
&lt;li&gt;看你之前写了一个 MCP 服务器，能详细解释一下这个吗？&lt;/li&gt;
&lt;li&gt;Agent 相关的了解吗？可以讲一下吗？&lt;/li&gt;
&lt;li&gt;你对 Skill 的理解是什么？&lt;/li&gt;
&lt;li&gt;能讲一下你怎么理解 RAG 吗？&lt;/li&gt;
&lt;li&gt;你提到最新的框架都有 llms.txt，面试官和我讲了一下 IDE 里面 RAG 和搜索的两种方式。&lt;/li&gt;
&lt;li&gt;JS 基本数据类型&lt;/li&gt;
&lt;li&gt;开发中引用类型和基本数据类型有什么要注意的点吗？&lt;/li&gt;
&lt;li&gt;Vue 和 React 都了解吗？&lt;/li&gt;
&lt;li&gt;可变数据和不可变数据有了解吗？&lt;/li&gt;
&lt;li&gt;React 中更改数据需要返回的 set 方法去更改，那引用类型有什么需要注意的吗？&lt;/li&gt;
&lt;li&gt;代码性能问题分析：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; parseLines&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;(files).&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; renderLinesSlow&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;container&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; text&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseLines&lt;/span&gt;&lt;span&gt;(files); &lt;/span&gt;&lt;span&gt;// 几十万行的文本&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; line&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; text) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; dom&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;createElement&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;p&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dom.textContent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; line;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    container.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(dom);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;问题：频繁 &lt;code&gt;append&lt;/code&gt; + 一次性渲染过多节点，主线程阻塞、页面卡顿。&lt;/li&gt;
&lt;li&gt;优化：虚拟滚动、分片渲染（&lt;code&gt;requestIdleCallback&lt;/code&gt; / &lt;code&gt;setTimeout&lt;/code&gt;）、&lt;code&gt;DocumentFragment&lt;/code&gt; 批量插入。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;手撕代码：异步并发调度器（实现一个 &lt;code&gt;Scheduler&lt;/code&gt; 类，控制异步任务并发数量）&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Scheduler&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;limit&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.limit &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; limit; &lt;/span&gt;&lt;span&gt;// 最大并发数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.running &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 当前运行中的任务数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.queue &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []; &lt;/span&gt;&lt;span&gt;// 等待队列&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 添加任务：接收一个返回 Promise 的函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;task&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;reject&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.queue.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({ task, resolve, reject });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_run&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  _run&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.running &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.limit &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.queue.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;task&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;reject&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.queue.&lt;/span&gt;&lt;span&gt;shift&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      this&lt;/span&gt;&lt;span&gt;.running&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      task&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(resolve)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt;(reject)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          this&lt;/span&gt;&lt;span&gt;.running&lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_run&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 使用示例&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; scheduler&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Scheduler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; timeout&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ms&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  new&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setTimeout&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; resolve&lt;/span&gt;&lt;span&gt;(value), ms));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;scheduler.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; timeout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;A&quot;&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(console.log);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;scheduler.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; timeout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;B&quot;&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(console.log);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;scheduler.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; timeout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;C&quot;&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(console.log);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;scheduler.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; timeout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;D&quot;&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(console.log);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 输出顺序：B, C, A, D （并发数为2，按完成时间输出）&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;面试复盘总结&lt;a href=&quot;#面试复盘总结&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1) AI 图表报错题：前端需要给出“安全 + 稳定 + 体验”三层答案&lt;a href=&quot;#1-ai-图表报错题前端需要给出安全--稳定--体验三层答案&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4&gt;面试官在考什么&lt;a href=&quot;#面试官在考什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;不是只问“报错了怎么重试”，而是考你是否理解：&lt;strong&gt;AI 生成内容默认不可信&lt;/strong&gt;，前端必须做执行隔离和防御式渲染。&lt;/p&gt;
&lt;h4&gt;完整回答模板&lt;a href=&quot;#完整回答模板&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;安全隔离（第一优先级）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不直接在主文档执行 AI 生成代码。&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;iframe sandbox&lt;/code&gt; 隔离执行环境，最小权限原则。&lt;/li&gt;
&lt;li&gt;配合 CSP、白名单资源域名，避免任意脚本执行和数据外泄。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;运行时可观测（第二优先级）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 iframe 内注入错误监听：&lt;code&gt;window.onerror&lt;/code&gt;、&lt;code&gt;unhandledrejection&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;postMessage&lt;/code&gt; 上报错误类型、堆栈、数据片段、traceId。&lt;/li&gt;
&lt;li&gt;父页面统一状态机：&lt;code&gt;生成中 -&amp;gt; 成功 -&amp;gt; 失败 -&amp;gt; 重试中 -&amp;gt; 兜底&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;输入约束（第三优先级）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不让 AI 输出可执行 HTML/JS。&lt;/li&gt;
&lt;li&gt;让 AI 输出 &lt;strong&gt;ECharts option JSON&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;前端做 Schema 校验 + 字段白名单（禁危险 formatter / function 注入）。&lt;/li&gt;
&lt;li&gt;校验通过后再安全实例化图表。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UX 降级&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;重试期间展示骨架屏和进度状态。&lt;/li&gt;
&lt;li&gt;最终失败提供“重试 / 查看错误摘要 / 切换表格视图”。&lt;/li&gt;
&lt;li&gt;保证页面不白屏、不阻塞核心流程。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;一句话总结&lt;a href=&quot;#一句话总结&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;AI 能提升上限，但前端必须负责下限：&lt;strong&gt;不崩、不炸、不泄露、可恢复&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;2) SSR 水合不匹配：从“渲染时机”理解本质&lt;a href=&quot;#2-ssr-水合不匹配从渲染时机理解本质&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4&gt;本质&lt;a href=&quot;#本质&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;SSR 先在服务端生成 HTML。&lt;/li&gt;
&lt;li&gt;客户端再执行一次首渲染并绑定事件（Hydration）。&lt;/li&gt;
&lt;li&gt;两边首帧结构或文本不一致就会 Hydration Mismatch。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;高频触发点&lt;a href=&quot;#高频触发点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;首屏依赖浏览器对象：&lt;code&gt;window&lt;/code&gt;、&lt;code&gt;document&lt;/code&gt;、&lt;code&gt;localStorage&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;首屏包含不稳定值：&lt;code&gt;Math.random()&lt;/code&gt;、&lt;code&gt;Date.now()&lt;/code&gt;、时区差异格式化。&lt;/li&gt;
&lt;li&gt;条件渲染分支在服务端与客户端条件不一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;标准解法&lt;a href=&quot;#标准解法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;浏览器依赖逻辑放 &lt;code&gt;onMounted&lt;/code&gt; / &lt;code&gt;useEffect&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;首屏只渲染稳定占位，挂载后替换真实数据。&lt;/li&gt;
&lt;li&gt;纯客户端模块用 &lt;code&gt;ClientOnly&lt;/code&gt; 或动态导入关闭 SSR。&lt;/li&gt;
&lt;li&gt;统一时区和格式化策略，避免日期字符串差异。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3) RAG、IDE 搜索、llms.txt：怎么讲得像工程实践&lt;a href=&quot;#3-ragide-搜索llmstxt怎么讲得像工程实践&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4&gt;传统向量 RAG 的局限&lt;a href=&quot;#传统向量-rag-的局限&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;语义相似度适合自然语言，不等于代码调用关系。&lt;/li&gt;
&lt;li&gt;代码问题本质是“符号引用 + 依赖图”问题，误召回和漏召回更明显。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;IDE 实际增强路径&lt;a href=&quot;#ide-实际增强路径&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;结构化检索（AST / Symbol / LSP）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先用“定义/引用/调用链”精确找上下文。&lt;/li&gt;
&lt;li&gt;再把命中片段喂给模型生成答案。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;向量检索补充&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;适合注释、文档、历史方案、跨仓库经验。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;长上下文模型&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可一次喂入更大上下文（仓库文档、接口协议、&lt;code&gt;llms.txt&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;优势是信息完整，风险是成本和噪声增加，需要分层裁剪。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;面试表达建议&lt;a href=&quot;#面试表达建议&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;“我不会把 RAG 只理解为向量库，而是 &lt;strong&gt;结构化检索优先，向量检索补充，长上下文兜底&lt;/strong&gt; 的混合策略。”&lt;/p&gt;
&lt;h3&gt;4) Vue vs React + 可变/不可变：把底层机制讲透&lt;a href=&quot;#4-vue-vs-react--可变不可变把底层机制讲透&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4&gt;Vue 为什么可变也能更新&lt;a href=&quot;#vue-为什么可变也能更新&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Vue3 基于 Proxy 拦截 &lt;code&gt;get/set&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;直接改 &lt;code&gt;state.a = 2&lt;/code&gt; 时可追踪依赖并触发更新。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;React 为什么强调不可变&lt;a href=&quot;#react-为什么强调不可变&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;React 依赖引用变化判断状态是否变化（浅比较语义）。&lt;/li&gt;
&lt;li&gt;如果直接改原对象再 &lt;code&gt;setState(原对象)&lt;/code&gt;，引用不变，可能 bailout 不重渲染。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;标准写法&lt;a href=&quot;#标准写法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 不推荐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;obj.a &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setObj&lt;/span&gt;&lt;span&gt;(obj);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 推荐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setObj&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;obj, a: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;可变 vs 不可变的工程取舍&lt;a href=&quot;#可变-vs-不可变的工程取舍&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;不可变数据：可预测、易回溯、利于 memo 优化。&lt;/li&gt;
&lt;li&gt;代价：拷贝成本与心智负担上升。&lt;/li&gt;
&lt;li&gt;实战：关键状态保持不可变，重计算区域配合结构共享/局部优化。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5) JS 基础题串讲：数据类型 -&amp;gt; 内存 -&amp;gt; 拷贝 -&amp;gt; GC&lt;a href=&quot;#5-js-基础题串讲数据类型---内存---拷贝---gc&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4&gt;第一步：破题——数据类型的分类&lt;a href=&quot;#第一步破题数据类型的分类&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;（面试官：JS 有哪些数据类型？）&lt;/p&gt;
&lt;p&gt;答题话术：&lt;/p&gt;
&lt;p&gt;“面试官您好，JS 的数据类型总体分为两大阵营：基本类型（Primitive）和引用类型（Reference）。
基本类型包括 7 种：&lt;code&gt;string&lt;/code&gt;、&lt;code&gt;number&lt;/code&gt;、&lt;code&gt;boolean&lt;/code&gt;、&lt;code&gt;null&lt;/code&gt;、&lt;code&gt;undefined&lt;/code&gt;、&lt;code&gt;symbol&lt;/code&gt;、&lt;code&gt;bigint&lt;/code&gt;。
引用类型统称为 &lt;code&gt;Object&lt;/code&gt;，里面包含了普通对象、数组（&lt;code&gt;Array&lt;/code&gt;）和函数（&lt;code&gt;Function&lt;/code&gt;）等。”&lt;/p&gt;
&lt;h4&gt;第二步：化解陷阱——String 装箱机制&lt;a href=&quot;#第二步化解陷阱string-装箱机制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;（面试官追问：那 &lt;code&gt;string&lt;/code&gt; 是基本类型吗？为什么它能调方法？）&lt;/p&gt;
&lt;p&gt;答题话术：&lt;/p&gt;
&lt;p&gt;“&lt;code&gt;string&lt;/code&gt; 绝对是基本类型。但我们在开发中经常能写出 &lt;code&gt;&apos;hello&apos;.length&lt;/code&gt; 这样的代码，这其实是 JS 引擎底层做了一个‘自动装箱’（Auto-boxing）的微操。&lt;/p&gt;
&lt;p&gt;当我们试图访问基本类型的方法时，引擎会瞬间在内存里 &lt;code&gt;new String(&apos;hello&apos;)&lt;/code&gt; 把它包装成一个临时对象，调用完 &lt;code&gt;.length&lt;/code&gt; 后，这个临时对象就立刻被销毁了。所以我们感受不到它的存在，但这其实是引擎为了方便开发者而设计的语法糖。”&lt;/p&gt;
&lt;h4&gt;第三步：深挖底层——栈与堆的妥协&lt;a href=&quot;#第三步深挖底层栈与堆的妥协&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;（面试官：基本类型和引用类型最本质的区别是什么？）&lt;/p&gt;
&lt;p&gt;答题话术：&lt;/p&gt;
&lt;p&gt;“它们最本质的区别在于内存分配策略。这其实是引擎为了兼顾运行速度和内存大小做出的妥协。&lt;/p&gt;
&lt;p&gt;基本类型的大小是固定的，所以引擎把它们的值直接存在栈内存（Stack）中。栈的特点是按值访问，存取速度极快。&lt;/p&gt;
&lt;p&gt;引用类型（比如一个巨大的 JSON 对象）大小不可控，如果全塞进栈里会严重拖慢上下文切换的速度。所以对象的实体被安置在广阔的堆内存（Heap）中。而栈里只存了一个‘指针’（内存地址），这个指针指向堆里的那个房间。&lt;/p&gt;
&lt;p&gt;所以，当我们执行 &lt;code&gt;let a = b&lt;/code&gt; 赋值时，基本类型是实打实地复制了一份值；而引用类型，仅仅是复制了那个指针。两者共享了堆里的同一个房间。”&lt;/p&gt;
&lt;h4&gt;第四步：闭环机制——引出垃圾回收（GC）&lt;a href=&quot;#第四步闭环机制引出垃圾回收gc&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;答题话术：&lt;/p&gt;
&lt;p&gt;“正因为这种内存分离机制，才有了垃圾回收（GC）的区别对待。&lt;/p&gt;
&lt;p&gt;栈内存的清理很简单，函数执行完、上下文一出栈，里面的基本类型和指针就跟着销毁了。&lt;/p&gt;
&lt;p&gt;但堆内存里的对象不能随便删，引擎必须用标记清除算法（Mark-and-Sweep）。它会从全局根节点（Root）出发，顺着栈里的‘指针’去找。只要这根线还连着，说明对象‘可达’，就能活下来；如果栈里的指针被销毁了，堆里的那个对象就成了‘孤岛’，在下一次 GC 时就会被当成垃圾清掉。”&lt;/p&gt;
&lt;h4&gt;第五步：工程落地——开发中的避坑指南&lt;a href=&quot;#第五步工程落地开发中的避坑指南&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;（面试官：那这种区别，在实际开发中有什么需要特别注意的吗？）&lt;/p&gt;
&lt;p&gt;答题话术：&lt;/p&gt;
&lt;p&gt;“在实际工程开发中，理解了内存机制，主要有三个高频的注意点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;副作用与深浅拷贝的抉择：
因为引用类型赋值只是复制指针，如果不小心修改了入参对象，会造成严重的全局数据污染（副作用）。所以在处理复杂状态时，我们需要有意识地使用浅拷贝（&lt;code&gt;...&lt;/code&gt; 扩展运算符）或深拷贝（&lt;code&gt;structuredClone&lt;/code&gt;）来做物理隔离。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;框架的状态更新机制（不可变 vs 可变）：
这点在 React 和 Vue 中体现得最明显。
React 崇尚‘不可变数据’（Immutable），它的 &lt;code&gt;setState&lt;/code&gt; 底层是用 &lt;code&gt;Object.is()&lt;/code&gt; 对比引用地址的。如果你直接 &lt;code&gt;obj.a = 2&lt;/code&gt; 然后传进去，栈里的指针没变，React 会以为数据没更新导致页面卡死，必须传入一个全新拷贝的对象。
而 Vue 是基于 Proxy 劫持的‘可变数据’，它能精确监听到对象内部字段的变化，所以允许我们直接修改原对象的属性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;防范内存泄漏：
开发时如果不注意，闭包里引用了庞大的 DOM 节点，或者在全局 &lt;code&gt;window&lt;/code&gt; 上挂载了对象、忘记清理定时器（&lt;code&gt;setInterval&lt;/code&gt;），就会导致栈里的‘指针’一直存在，垃圾回收器（GC）就不敢去清空堆里的内存，久而久之页面就会卡顿甚至崩溃。”&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>category:面经</category><category>tag:字节跳动</category><category>tag:面经</category><category>tag:DataAgent</category><category>tag:MCP</category><category>tag:Agent</category><category>tag:RAG</category></item><item><title>五一周总结：旅行、换机与需求磨合期</title><link>https://yuki-bloom.vercel.app/ja/post/weekly-2026-05-10</link><guid isPermaLink="false">ja:weekly-2026-05-10</guid><description>五一短途旅行后回到工作节奏，从需求推进、跨仓库协作到业务理解，记录这一周的真实感受与下周计划。</description><pubDate>Sun, 10 May 2026 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;本周小记&lt;a href=&quot;#本周小记&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这个五一过得很开心，去了武汉和长沙。第一次非常具体地感受到“被照顾”的感觉，心里很暖。&lt;/p&gt;
&lt;p&gt;也会忍不住想，如果以后能一直在一个城市，不用异地就更好了。现实是现在还在实习阶段，不实习就没有收入，不过没关系，先把眼前的路走稳，努力赚钱，后面会越来越好。&lt;/p&gt;
&lt;p&gt;这周还终于抢到了北京国补，顺利换了手机。旧手机续航已经夸张到不到 3 小时，还伴随一堆 bug，体验真的很影响状态。换机之后，整个人的节奏都顺了不少。&lt;/p&gt;
&lt;h2&gt;工作进展&lt;a href=&quot;#工作进展&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1) 从“摸索”到“进入需求”&lt;a href=&quot;#1-从摸索到进入需求&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;前面一段时间总觉得自己有点“摸鱼”，但回头看，其实是在补基础：内部文档、平台流程、工具使用都在学。&lt;/p&gt;
&lt;p&gt;这周开始正式推进需求，而且是个体量不小的需求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;排期大约 2 周开发 + 1 周测试&lt;/li&gt;
&lt;li&gt;涉及 4 个微前端仓库改动&lt;/li&gt;
&lt;li&gt;还牵扯一个老的 Vue2 项目&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2) 老项目联调成本比预期高&lt;a href=&quot;#2-老项目联调成本比预期高&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;老项目需要 Node 16 才能跑，而且存在已知问题：日志死循环会把磁盘写满。&lt;/p&gt;
&lt;p&gt;这类历史包袱对开发节奏影响很大，也让我更直观地理解到“技术债”这件事不是抽象概念，而是每天都会付出的时间成本。&lt;/p&gt;
&lt;h3&gt;3) 低代码平台的实际痛点&lt;a href=&quot;#3-低代码平台的实际痛点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这次在低代码平台上也踩了不少坑。最大的感受是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网页端改动效率偏低&lt;/li&gt;
&lt;li&gt;仓库里下发的 JSON 协议无法稳定手改&lt;/li&gt;
&lt;li&gt;手动改完后，自动部署会重新下发协议把改动覆盖&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;很多时间花在“和平台对齐”而不是“写业务逻辑”上，体感比较挫败。&lt;/p&gt;
&lt;h2&gt;本周复盘&lt;a href=&quot;#本周复盘&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;目前最大的问题不是“不会写代码”，而是对整体业务链路理解还不够深。&lt;/p&gt;
&lt;p&gt;当业务全景没理清楚时，改动点会显得分散，做起来就容易反复。&lt;/p&gt;
&lt;h2&gt;下周计划&lt;a href=&quot;#下周计划&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;主动找 mentor 过一遍完整业务流程，补齐核心上下文。&lt;/li&gt;
&lt;li&gt;用 AI 先做一版需求拆解：
&lt;ul&gt;
&lt;li&gt;业务目标是什么&lt;/li&gt;
&lt;li&gt;涉及哪些系统/仓库&lt;/li&gt;
&lt;li&gt;每个改动点的输入、输出、依赖关系是什么&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在编码前先产出“改动点清单”，减少返工。&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>category:周刊</category><category>tag:周刊</category><category>tag:实习日记</category><category>tag:需求复盘</category><category>tag:AI</category></item><item><title>后台 CRUD 重构：三个页面合并成一个，维护效率翻倍</title><link>https://yuki-bloom.vercel.app/ja/post/crud-refactor-one-page</link><guid isPermaLink="false">ja:crud-refactor-one-page</guid><description>第一次独立做需求被同事 code review 点醒：新建、编辑、详情三个页面其实可以写在一起，用状态驱动替代页面复制。</description><pubDate>Fri, 24 Apr 2026 06:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;写在前面&lt;a href=&quot;#写在前面&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这是我进公司后第一次独立负责一个完整需求——做一个业务单据的维护模块。&lt;/p&gt;
&lt;p&gt;终于可以写点功能代码了！一顿操作：新建文件夹 → &lt;code&gt;Create.vue、Edit.vue、Detail.vue，&lt;/code&gt;刚准备开始复制粘贴表单代码，mentor 过来 review 我的代码结构。&lt;/p&gt;
&lt;p&gt;他看了看我的三个文件，问了一句：“这三个页面表单长得一样，为什么不写在一起，用路由参数切换状态？”&lt;/p&gt;
&lt;p&gt;我愣了一下，好像… 还真是这么回事，之前也没有想过这些事情，因为三个路由就像是理所当然的，虽然他们好像确实差不多…？&lt;/p&gt;
&lt;p&gt;以前在学校写项目，都是要几个页面我就建几个 &lt;code&gt;.vue&lt;/code&gt; 文件，从来没想过可以合并。mentor 这一句话给我打开了新世界的大门。&lt;/p&gt;
&lt;p&gt;周末把这次重构的思路整理一下，算是给自己的第一次需求做个复盘。&lt;/p&gt;
&lt;h2&gt;核心思路：别被原型的”页面”框住&lt;a href=&quot;#核心思路别被原型的页面框住&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;产品原型画了三个页面，不代表代码里就一定要三个组件。&lt;/p&gt;
&lt;p&gt;跳出来看，这三个本质上就是&lt;strong&gt;同一个表单的不同状态&lt;/strong&gt;：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;状态&lt;/th&gt;&lt;th&gt;特点&lt;/th&gt;&lt;th&gt;操作&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;新建&lt;/td&gt;&lt;td&gt;无 ID，表单为空&lt;/td&gt;&lt;td&gt;POST 新增&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;编辑&lt;/td&gt;&lt;td&gt;有 ID，回填数据&lt;/td&gt;&lt;td&gt;PUT 更新&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;详情&lt;/td&gt;&lt;td&gt;有 ID，只读展示&lt;/td&gt;&lt;td&gt;无保存按钮&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;理清楚这个，代码结构就简单多了：&lt;strong&gt;一个路由，状态驱动 UI&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;场景一：页面跳转（一个路由搞定）&lt;a href=&quot;#场景一页面跳转一个路由搞定&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果表单字段比较多，需要跳转到新页面填写，可以用 Vue Router 的可选参数 &lt;code&gt;?&lt;/code&gt; 来合并路由。&lt;/p&gt;
&lt;h3&gt;路由配置&lt;a href=&quot;#路由配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// router/index.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    path: &lt;/span&gt;&lt;span&gt;&quot;/business/form/:id?&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// :id? 表示参数可有可无&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    name: &lt;/span&gt;&lt;span&gt;&quot;BusinessForm&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    component&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; import&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;@/views/business/CommonForm.vue&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;组件实现&lt;a href=&quot;#组件实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;form-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{{ pageTitle }}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;el-form&lt;/span&gt;&lt;span&gt; :model&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;formData&quot;&lt;/span&gt;&lt;span&gt; :disabled&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;isDetail&quot;&lt;/span&gt;&lt;span&gt; label-width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;120px&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;el-form-item&lt;/span&gt;&lt;span&gt; label&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;单据名称&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;el-input&lt;/span&gt;&lt;span&gt; v-model&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;formData.name&quot;&lt;/span&gt;&lt;span&gt; placeholder&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;请输入名称&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;el-form-item&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;el-form-item&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;el-button&lt;/span&gt;&lt;span&gt; v-if&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;!isDetail&quot;&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;primary&quot;&lt;/span&gt;&lt;span&gt; @click&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;handleSave&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &amp;gt;保存&amp;lt;/&lt;/span&gt;&lt;span&gt;el-button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;el-button&lt;/span&gt;&lt;span&gt; @click&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;$router.back()&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;返回&amp;lt;/&lt;/span&gt;&lt;span&gt;el-button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;el-form-item&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;el-form&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; setup&lt;/span&gt;&lt;span&gt; lang&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;ts&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { ref, computed, onMounted } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;vue&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useRoute } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;vue-router&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; route&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useRoute&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 1. 从路由取参数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; computed&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; route.params.id);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; computed&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; route.query.type);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 2. 计算当前状态（后面所有的逻辑都靠这三个布尔值控制）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; isCreate&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; computed&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;id.value);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; isDetail&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; computed&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; !!&lt;/span&gt;&lt;span&gt;id.value &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; type.value &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;detail&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; isEdit&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; computed&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; !!&lt;/span&gt;&lt;span&gt;id.value &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; type.value &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &quot;detail&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 页面标题也根据状态变化&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; pageTitle&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; computed&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (isCreate.value) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &quot;新建业务单&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (isEdit.value) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &quot;编辑业务单&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &quot;业务单详情&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; formData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ref&lt;/span&gt;&lt;span&gt;({});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 3. 只有非新建状态才需要拉数据回填&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;onMounted&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;isCreate.value) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // formData.value = await fetchDetail(id.value)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 4. 保存时根据状态调用不同接口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; handleSave&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (isCreate.value) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // await createApi(formData.value)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // await updateApi(id.value, formData.value)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;改完之后删掉了两个文件，代码量少了将近一半，维护起来真的轻松多了。&lt;/p&gt;
&lt;h2&gt;场景二：弹窗表单（靠 Props 控制）&lt;a href=&quot;#场景二弹窗表单靠-props-控制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;还有一种常见场景是不跳转页面，直接在列表页点弹窗。这种更简单，连路由都不用改，父组件传个 &lt;code&gt;mode&lt;/code&gt; 进来就行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;el-dialog&lt;/span&gt;&lt;span&gt; :title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;dialogTitle&quot;&lt;/span&gt;&lt;span&gt; v-model&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;visible&quot;&lt;/span&gt;&lt;span&gt; width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;500px&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;el-form&lt;/span&gt;&lt;span&gt; :model&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;formData&quot;&lt;/span&gt;&lt;span&gt; :disabled&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;isDetail&quot;&lt;/span&gt;&lt;span&gt; label-width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100px&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;!-- 表单字段 --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;el-form&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt; #&lt;/span&gt;&lt;span&gt;footer&lt;/span&gt;&lt;span&gt; v-if&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;isDetail&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;el-button&lt;/span&gt;&lt;span&gt; @click&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;visible = false&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;取消&amp;lt;/&lt;/span&gt;&lt;span&gt;el-button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;el-button&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;primary&quot;&lt;/span&gt;&lt;span&gt; @click&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;handleSave&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;确定&amp;lt;/&lt;/span&gt;&lt;span&gt;el-button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;el-dialog&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; setup&lt;/span&gt;&lt;span&gt; lang&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;ts&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { ref, computed, watch } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;vue&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; props&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; defineProps&lt;/span&gt;&lt;span&gt;&amp;lt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  mode&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;create&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;edit&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;detail&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rowId&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; visible&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; defineModel&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;&quot;visible&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; isCreate&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; computed&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; props.mode &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;create&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; isDetail&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; computed&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; props.mode &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;detail&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; formData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ref&lt;/span&gt;&lt;span&gt;({});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 监听弹窗打开，判断要不要拉数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;watch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; visible.value,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;show&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (show) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (isCreate.value) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        formData.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {}; &lt;/span&gt;&lt;span&gt;// 新建时清空上次的脏数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 去查详情回填...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;踩坑记录&lt;a href=&quot;#踩坑记录&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1. 路由复用导致数据串台&lt;a href=&quot;#1-路由复用导致数据串台&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Vue 的组件复用机制，从编辑页跳到新建页时不会重新创建组件。需要在 &lt;code&gt;&amp;lt;router-view&amp;gt;&lt;/code&gt; 上加 &lt;code&gt;:key=&quot;$route.fullPath&quot;&lt;/code&gt;，或者在 &lt;code&gt;watch&lt;/code&gt; 里监听路由变化手动清空表单。&lt;/p&gt;
&lt;h3&gt;2. 不是所有情况都适合合并&lt;a href=&quot;#2-不是所有情况都适合合并&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;如果详情页的 UI 和表单完全不一样（比如加了审批时间轴、操作记录什么的），就别强行合了。老老实实写独立的 Detail.vue，不然一个文件里写几百个 &lt;code&gt;v-if&lt;/code&gt; 会很恶心。&lt;/p&gt;
&lt;h3&gt;3. 前端禁用不等于后端安全&lt;a href=&quot;#3-前端禁用不等于后端安全&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;disabled&lt;/code&gt; 只能拦住普通用户，拦不住会改 URL 的人。把 &lt;code&gt;?type=detail&lt;/code&gt; 删掉页面就变成可编辑了，所以后端接口一定要做好权限校验。&lt;/p&gt;
&lt;h2&gt;一点感悟&lt;a href=&quot;#一点感悟&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这次被 mentor 点拨之后，最大的收获是思维上的转变。&lt;/p&gt;
&lt;p&gt;以前写代码是”页面驱动”——产品要几个页面，我就建几个文件，满脑子都是复制粘贴。现在开始有点”状态驱动”的意识了，会更关注组件在不同状态下应该有什么行为，而不是纠结于有几个页面。&lt;/p&gt;
&lt;p&gt;果然上班和在学校自己瞎写还是不一样，得多和组里的前辈交流，看看别人是怎么设计代码结构的。光靠蛮力堆页面，只会给自己挖坑。&lt;/p&gt;
&lt;p&gt;记录一下，继续学习～&lt;/p&gt;</content:encoded><category>category:项目</category><category>category:前端</category><category>tag:Vue</category><category>tag:CRUD</category><category>tag:重构</category><category>tag:实习日记</category></item><item><title>人生嘛，自己舒服最优先</title><link>https://yuki-bloom.vercel.app/ja/post/ren-sheng-ma-zi-ji-shu-fu-zui-you-xian</link><guid isPermaLink="false">ja:ren-sheng-ma-zi-ji-shu-fu-zui-you-xian</guid><description>2026.04.18随笔</description><pubDate>Sat, 18 Apr 2026 09:51:35 GMT</pubDate><content:encoded>&lt;p&gt;大家总说人要 &lt;strong&gt;知足常乐&lt;/strong&gt;，其实很多东西真的不是靠拼命努力就能得到的。现在整个社会都弥漫着一股“你卷我、我不卷就会被淘汰”的氛围，可我还是觉得，人这一生，知足常乐才是最重要的。&lt;/p&gt;
&lt;h3&gt;摆脱外物的枷锁&lt;a href=&quot;#摆脱外物的枷锁&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;现在大多数年轻人，不想被房贷车贷绑住，也不想轻易走进婚姻，我特别能理解这种想法。人本来就不该被外物束缚，除非是遇到了真心喜欢的人，愿意为了对方去努力、去承担。除此之外，真的没必要给自己套上一层层枷锁。&lt;/p&gt;
&lt;h3&gt;大厂背后的代价&lt;a href=&quot;#大厂背后的代价&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;人人都挤破头想进大厂，无非是冲着不错的福利和更高的薪资。可背后的代价呢？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;快手&lt;/strong&gt;：常常忙到晚上十点半、十一点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;字节&lt;/strong&gt;：也基本加班到很晚。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样的节奏，哪里还留得下属于自己的时间？一个人打拼或许还能勉强应付，可一旦有了家庭，要在这种高强度工作里兼顾家人，简直不敢想象。更别说工作里各种突发状况，随时待命、随时被叫醒，连喘息的空间都没有。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们拼命挣钱到底是为了什么？不该是为了掏空自己、消耗生活。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我始终觉得，人首先要对自己好，先让自己过上舒服自在的日子，并且健康的生活着。连自己都没有能力好好生活、好好享乐，又怎么可能真正让身边的人开心？这样的关系本身就是失衡、不对等的。&lt;/p&gt;
&lt;h3&gt;实习的意义&lt;a href=&quot;#实习的意义&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;其实没谁会真心热爱工作，大家不过都是拿时间换钱。不必羡慕进大厂的人，外表光鲜，说到底也只是打工而已。对现在的我们来说，实习阶段最重要的还是 &lt;strong&gt;沉下心提升自己&lt;/strong&gt;。真要是手里有足够的底气，谁还愿意一直被困在工作里呢？&lt;/p&gt;
&lt;h3&gt;拒绝盲目内卷&lt;a href=&quot;#拒绝盲目内卷&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;内卷的尽头只会是更无尽的内卷：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;大二&lt;/strong&gt; 开始实习。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大三&lt;/strong&gt; 忙着找机会。&lt;/li&gt;
&lt;li&gt;到后来，连本科生都越来越难有立足之地。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但就算这样，也不必被外界推着走。知足常乐，过好自己的日子，不盲目攀比。年轻的时候也别彻底摆烂，在自己力所能及的范围里努力一点，朝着自己真正想要的生活靠近就好。&lt;/p&gt;
&lt;h3&gt;生活本就千人千面&lt;a href=&quot;#生活本就千人千面&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;每个人想要的都不一样：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有人只想有个小窝，安安稳稳过平淡日子；&lt;/li&gt;
&lt;li&gt;有人想早点赚够钱，安安心心躺平；&lt;/li&gt;
&lt;li&gt;也有人想努力挣钱，去不同的地方旅行，见不一样的人。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;生活本就千人千面，不必活成同一个模板，&lt;strong&gt;自己舒心，比什么都重要&lt;/strong&gt;。&lt;/p&gt;</content:encoded><category>category:随笔</category><category>tag:实习</category><category>tag:自我反思</category></item><item><title>DataAgent 前端架构白皮书:AI-Native 协作体系</title><link>https://yuki-bloom.vercel.app/ja/post/dataagent-qian-duan-jia-gou-bai-pi-shu-ai-native-xie-zuo-ti-xi</link><guid isPermaLink="false">ja:dataagent-qian-duan-jia-gou-bai-pi-shu-ai-native-xie-zuo-ti-xi</guid><description>构建 AI 友好的前端工程化体系,通过 AST 静态分析和双引擎文档生成,实现代码即文档的 AI-Native 开发范式。本文详细阐述了如何在 Nuxt 4 项目中建立上下文治理体系,让 AI 成为真正的协作伙伴。</description><pubDate>Wed, 15 Apr 2026 12:10:15 GMT</pubDate><content:encoded>&lt;h2&gt;1. 核心理念&lt;div&gt;&lt;/div&gt; (Context Governance)&lt;a href=&quot;#1-核心理念-context-governance&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我们不再要求开发者手动维护 Markdown 文档,而是通过 &lt;strong&gt;“Single Source of Truth (单一事实来源)”&lt;/strong&gt; 原则,利用 AST 静态分析技术,将代码中的 TypeScript 类型定义和 JSDoc 注释自动转化为 AI 可读的语义索引。&lt;/p&gt;
&lt;h3&gt;三大核心原则&lt;a href=&quot;#三大核心原则&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single Source of Truth&lt;/strong&gt;: 代码即文档。JSDoc/TSDoc 是唯一真理,Markdown 只是它的衍生品。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deterministic (确定性)&lt;/strong&gt;: 使用 AST(抽象语法树)静态分析生成文档,而非依赖 AI 生成(避免幻觉、降低延迟)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Folder-as-Context (目录即上下文)&lt;/strong&gt;: 采用”组件/逻辑原子化”的目录结构,通过物理隔离上下文,让 AI 聚焦当前模块。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 技术方案&lt;div&gt;&lt;/div&gt;&lt;a href=&quot;#2-技术方案&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;为了覆盖 UI 组件和纯逻辑代码,我们采用 &lt;strong&gt;双引擎策略&lt;/strong&gt; 编写自动化脚本 (&lt;code&gt;scripts/gen-ai-context.mjs&lt;/code&gt;)。&lt;/p&gt;
&lt;h3&gt;引擎架构&lt;a href=&quot;#引擎架构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;引擎类型&lt;/th&gt;&lt;th&gt;适用范围&lt;/th&gt;&lt;th&gt;底层技术&lt;/th&gt;&lt;th&gt;输出内容&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;UI 引擎&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;.vue&lt;/code&gt; 组件&lt;/td&gt;&lt;td&gt;&lt;code&gt;vue-docgen-api&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Props/Slots/Events/示例代码&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;逻辑引擎&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;.ts&lt;/code&gt; 文件&lt;/td&gt;&lt;td&gt;&lt;code&gt;ts-morph&lt;/code&gt;&lt;/td&gt;&lt;td&gt;函数签名/类方法/类型定义&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;工作流程&lt;a href=&quot;#工作流程&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;graph LR
    A[开发者修改代码] --&amp;gt; B[Git Commit]
    B --&amp;gt; C[Husky Pre-commit]
    C --&amp;gt; D[gen-ai-context.mjs]
    D --&amp;gt; E[UI引擎扫描.vue]
    D --&amp;gt; F[逻辑引擎扫描.ts]
    E --&amp;gt; G[生成 README.md]
    F --&amp;gt; G
    G --&amp;gt; H[自动提交文档]
    H --&amp;gt; I[AI读取最新上下文]&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 目录结构重构 (React-Style)&lt;a href=&quot;#3-目录结构重构-react-style&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;为了配合上述方案,项目目录结构调整为 &lt;strong&gt;“以文件夹为单元”&lt;/strong&gt; 的模式,实现物理层面的上下文隔离:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;DataAgent/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── .cursorrules          # AI 行为准则 (更新版)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── components/           # [UI 引擎管辖]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── UserCard/         # 独立组件文件夹&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── index.vue     # 主代码 (包含 JSDoc)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── types.ts      # 组件专属类型&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   └── README.md     #  脚本自动生成 (AI 读这里,勿手改)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── README.md         #  脚本生成全局组件索引 (AI 地图)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── composables/          # [逻辑引擎管辖]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── useAuth/          # 独立 Hook 文件夹&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── index.ts      # 逻辑代码 (包含 JSDoc)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   └── README.md     #  脚本自动生成&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── scripts/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── gen-ai-context.mjs # 核心自动化脚本&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── package.json          # 配置 husky 钩子&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;关键设计思想&lt;a href=&quot;#关键设计思想&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;物理隔离&lt;/strong&gt;: 每个组件/逻辑单元独立成文件夹,避免上下文污染&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;就近原则&lt;/strong&gt;: 类型定义、测试文件与主代码放在同一目录&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 导航&lt;/strong&gt;: 每个模块的 README.md 作为 AI 的”索引卡片”&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 简单实现思路 (Implementation Roadmap)&lt;a href=&quot;#4-简单实现思路-implementation-roadmap&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;第一步&lt;div&gt;&lt;/div&gt; (scripts/gen-ai-context.mjs)&lt;a href=&quot;#第一步-scriptsgen-ai-contextmjs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是一个整合了双引擎逻辑的 Node.js 脚本。&lt;/p&gt;
&lt;h4&gt;伪代码逻辑&lt;a href=&quot;#伪代码逻辑&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { generateComponentDocs } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;./utils/ui-engine&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 基于 vue-docgen-api&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { generateLogicDocs } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;./utils/logic-engine&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 基于 ts-morph&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { generateGlobalIndex } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;./utils/indexer&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot; 正在构建 AI 上下文索引...&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 1. 处理 UI 组件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 遍历 components/*/*.vue,提取 Props/Slots -&amp;gt; 写入同级 README.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; generateComponentDocs&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 2. 处理 逻辑与服务&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 遍历 composables/*.ts, services/*.ts -&amp;gt; 提取函数签名/Class -&amp;gt; 写入同级 README.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; generateLogicDocs&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 3. 更新全局映射&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 扫描所有生成的 README,更新根目录或各模块根的索引表,方便 AI 跳转&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; generateGlobalIndex&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;✅ AI 上下文同步完成&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;第二步&lt;div&gt;&lt;/div&gt; (Workflow Integration)&lt;a href=&quot;#第二步-workflow-integration&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;放弃”拦截报错”模式,采用 &lt;strong&gt;“无感修正”&lt;/strong&gt; 模式。开发者只关注代码和注释,文档更新对人是透明的。&lt;/p&gt;
&lt;h4&gt;推荐配置 (package.json)&lt;a href=&quot;#推荐配置-packagejson&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;lint-staged&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;app/**/*.{ts,vue,tsx}&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;node scripts/gen-ai-context.mjs&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;git add&quot;&lt;/span&gt;&lt;span&gt; // 将生成的文档一同提交,确保文档永远与代码同步&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;收益分析&lt;a href=&quot;#收益分析&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;零心智负担&lt;/strong&gt;: 开发者无需手动维护文档&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;强制同步&lt;/strong&gt;: 文档永远不会过期&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;AI 即时感知&lt;/strong&gt;: 每次提交后 AI 立即获得最新上下文&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;第三步&lt;div&gt;&lt;/div&gt; .cursorrules (AI 指令)&lt;a href=&quot;#第三步-cursorrules-ai-指令&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是项目的”&lt;strong&gt;最高宪法&lt;/strong&gt;”,指导 AI 如何利用上述基础设施。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# DataAgent (Nuxt 4 SPA + TypeScript) 开发法则&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 0. 关键上下文 (最高优先级)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **框架**&lt;/span&gt;&lt;span&gt;: Nuxt 4 (基于 Nitro 引擎)。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **模式**&lt;/span&gt;&lt;span&gt;: 纯 SPA 模式 (&lt;/span&gt;&lt;span&gt;`ssr: false`&lt;/span&gt;&lt;span&gt;)。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **语言**&lt;/span&gt;&lt;span&gt;: TypeScript (严格模式)。&lt;/span&gt;&lt;span&gt;**严禁**&lt;/span&gt;&lt;span&gt;使用 &lt;/span&gt;&lt;span&gt;`.js`&lt;/span&gt;&lt;span&gt; 文件。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **UI 库**&lt;/span&gt;&lt;span&gt;: Vuetify 3 (必须使用 &lt;/span&gt;&lt;span&gt;`&amp;lt;v-btn&amp;gt;`&lt;/span&gt;&lt;span&gt; 等组件,而非原生 &lt;/span&gt;&lt;span&gt;`&amp;lt;div&amp;gt;`&lt;/span&gt;&lt;span&gt;)。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 1. 知识检索协议 (Knowledge Retrieval)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;在回答问题或编写代码之前,必须执行以下步骤:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;1.&lt;/span&gt;&lt;span&gt; **框架层**&lt;/span&gt;&lt;span&gt;: 涉及 Nuxt 核心 (如 Nitro, Composables) &lt;/span&gt;&lt;span&gt;**必须**&lt;/span&gt;&lt;span&gt;优先参考 &lt;/span&gt;&lt;span&gt;`@Nuxt4`&lt;/span&gt;&lt;span&gt; 文档。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;2.&lt;/span&gt;&lt;span&gt; **地图加载 (Map Loading)**&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   -&lt;/span&gt;&lt;span&gt; 涉及新功能开发时,先检索 &lt;/span&gt;&lt;span&gt;`components/README.md`&lt;/span&gt;&lt;span&gt; 或 &lt;/span&gt;&lt;span&gt;`composables/README.md`&lt;/span&gt;&lt;span&gt; (全局索引)。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;3.&lt;/span&gt;&lt;span&gt; **本地上下文 (Local Context)**&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   -&lt;/span&gt;&lt;span&gt; 修改现有组件或逻辑前,&lt;/span&gt;&lt;span&gt;**必须读取**&lt;/span&gt;&lt;span&gt;该目录下自动生成的 &lt;/span&gt;&lt;span&gt;`README.md`&lt;/span&gt;&lt;span&gt; (技能卡片/Skill Card)。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;4.&lt;/span&gt;&lt;span&gt; **查重机制 (Similarity Check)**&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   -&lt;/span&gt;&lt;span&gt; **原则**&lt;/span&gt;&lt;span&gt;: &quot;先存在,后创建&quot; (Exist before Create)。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   -&lt;/span&gt;&lt;span&gt; 如果发现已存在类似功能的代码,&lt;/span&gt;&lt;span&gt;**必须**&lt;/span&gt;&lt;span&gt;优先考虑复用,而不是新建。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;5.&lt;/span&gt;&lt;span&gt; **类型定义**&lt;/span&gt;&lt;span&gt;: 优先引用 &lt;/span&gt;&lt;span&gt;`types/`&lt;/span&gt;&lt;span&gt; 或同级目录下的 &lt;/span&gt;&lt;span&gt;`.ts`&lt;/span&gt;&lt;span&gt; 定义,&lt;/span&gt;&lt;span&gt;**禁止**&lt;/span&gt;&lt;span&gt;凭空猜测数据结构。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 2. 编码规范 (Coding Standards)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **严格 TypeScript**&lt;/span&gt;&lt;span&gt;: 所有变量、参数、返回值必须有明确的类型定义,&lt;/span&gt;&lt;span&gt;**禁止**&lt;/span&gt;&lt;span&gt;使用 &lt;/span&gt;&lt;span&gt;`any`&lt;/span&gt;&lt;span&gt;。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **文档先行 (Documentation First)**&lt;/span&gt;&lt;span&gt;: 生成代码时,必须包含标准的 JSDoc/TSDoc 注释。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; **组件**&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`&amp;lt;script setup&amp;gt;`&lt;/span&gt;&lt;span&gt; 顶部需包含 &lt;/span&gt;&lt;span&gt;`@description`&lt;/span&gt;&lt;span&gt; 说明组件用途。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; **Props**&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`defineProps`&lt;/span&gt;&lt;span&gt; 上方需说明每个属性的业务含义。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; **逻辑**&lt;/span&gt;&lt;span&gt;: 所有函数必须包含 &lt;/span&gt;&lt;span&gt;`@param`&lt;/span&gt;&lt;span&gt; 和 &lt;/span&gt;&lt;span&gt;`@returns`&lt;/span&gt;&lt;span&gt; 说明。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **架构结构**&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; **API**&lt;/span&gt;&lt;span&gt;: 统一使用 Axios,所有服务层代码必须存放在 &lt;/span&gt;&lt;span&gt;`app/services/`&lt;/span&gt;&lt;span&gt;。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; **风格**&lt;/span&gt;&lt;span&gt;: 必须使用 Vue 3 组合式 API (&lt;/span&gt;&lt;span&gt;`&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;`&lt;/span&gt;&lt;span&gt;)。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 3. 复用与重构策略 (Reuse &amp;amp; Refactor)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **DRY 原则 (拒绝重复)**&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; **禁止**&lt;/span&gt;&lt;span&gt;生成重复的工具函数或极其相似的 UI 组件。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; 如果发现现有组件/函数满足当前 &lt;/span&gt;&lt;span&gt;**80%**&lt;/span&gt;&lt;span&gt; 的需求,&lt;/span&gt;&lt;span&gt;**严禁**&lt;/span&gt;&lt;span&gt;复制一份代码进行修改。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **多态扩展 (Polymorphic Extension)**&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; **方案**&lt;/span&gt;&lt;span&gt;: 通过增加 &lt;/span&gt;&lt;span&gt;**可选属性 (Optional Props)**&lt;/span&gt;&lt;span&gt;、&lt;/span&gt;&lt;span&gt;**泛型 (Generics)**&lt;/span&gt;&lt;span&gt; 或 &lt;/span&gt;&lt;span&gt;**回调策略**&lt;/span&gt;&lt;span&gt; 来扩展现有组件/函数,使其适应新需求。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **安全护栏 (Guardrail)**&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; 重构时必须保证 &lt;/span&gt;&lt;span&gt;**向后兼容性 (Backward Compatibility)**&lt;/span&gt;&lt;span&gt;。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; _示例_&lt;/span&gt;&lt;span&gt;: 原函数 &lt;/span&gt;&lt;span&gt;`fetchData(id)`&lt;/span&gt;&lt;span&gt; -&amp;gt; 新需求需要额外参数 -&amp;gt; 改为 &lt;/span&gt;&lt;span&gt;`fetchData(id, options?)`&lt;/span&gt;&lt;span&gt;。&lt;/span&gt;&lt;span&gt;**禁止**&lt;/span&gt;&lt;span&gt;直接修改必填参数导致旧的调用方报错。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **重构触发器**&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; 当你发现自己在&quot;复制粘贴&quot;代码时,&lt;/span&gt;&lt;span&gt;**立即停止**&lt;/span&gt;&lt;span&gt;。改为将公共逻辑提取到 &lt;/span&gt;&lt;span&gt;`composables/`&lt;/span&gt;&lt;span&gt; 或 &lt;/span&gt;&lt;span&gt;`utils/`&lt;/span&gt;&lt;span&gt; 中,并更新原来的引用。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 4. 协作协议&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **禁止**&lt;/span&gt;&lt;span&gt;手动修改 &lt;/span&gt;&lt;span&gt;`README.md`&lt;/span&gt;&lt;span&gt;,文档全权由自动化脚本生成。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 提交代码前,必须确保通过 TypeScript 类型检查 (无 Type Error)。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 外部文档集成与兜底 (External Context &amp;amp; Fallback)&lt;a href=&quot;#5-外部文档集成与兜底-external-context--fallback&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;5.1 团队级外部文档 (Nuxt 4 Docs)&lt;a href=&quot;#51-团队级外部文档-nuxt-4-docs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为了解决团队成员无法自动同步 Cursor Docs 的问题,我们将手动配置转化为标准流程说明。&lt;/p&gt;
&lt;h4&gt;配置方案&lt;a href=&quot;#配置方案&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;同步媒介&lt;/strong&gt;: 在项目根目录维护 &lt;code&gt;docs/AI_SETUP.md&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;操作指令&lt;/strong&gt;: 新成员入职时,需在 Cursor 侧边栏 Docs 中添加:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nuxt4-Guide&lt;/strong&gt;: &lt;code&gt;https://nuxt.com/llms.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nuxt4&lt;/strong&gt;: &lt;code&gt;https://nuxt.com/llms-full.txt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.2 模块化 Service 层与 TS 护城河&lt;a href=&quot;#52-模块化-service-层与-ts-护城河&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为了在 DataAgent 重构中建立 AI 友好型应用,我们将逻辑深度抽象。&lt;/p&gt;
&lt;h4&gt;实践策略&lt;a href=&quot;#实践策略&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Service 抽离&lt;/strong&gt;: 在 &lt;code&gt;app/services/&lt;/code&gt; 中按业务域(如 &lt;code&gt;AgentService&lt;/code&gt;, &lt;code&gt;KnowledgeService&lt;/code&gt;)拆分 Class 或 Module。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TS 类型即文档&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ts-morph&lt;/code&gt; 引擎会自动提取 Service 中的 &lt;code&gt;interface&lt;/code&gt; 和 &lt;code&gt;public&lt;/code&gt; 方法签名到 README。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;收益&lt;/strong&gt;: 当 AI 知道 &lt;code&gt;getAgent(id: string): Promise&amp;lt;AgentNode&amp;gt;&lt;/code&gt; 的确切类型时,它生成的业务逻辑准确率将从 &lt;strong&gt;60% 提升到 95%&lt;/strong&gt; 以上。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;示例&lt;div&gt;&lt;/div&gt; 结构&lt;a href=&quot;#示例-结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * Agent 管理服务&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * &lt;/span&gt;&lt;span&gt;@description&lt;/span&gt;&lt;span&gt; 负责 Agent 节点的 CRUD 操作和状态管理&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; AgentService&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   * 获取 Agent 详情&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   * &lt;/span&gt;&lt;span&gt;@param&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt; - Agent 唯一标识符&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   * &lt;/span&gt;&lt;span&gt;@returns&lt;/span&gt;&lt;span&gt; Agent 节点完整信息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  async&lt;/span&gt;&lt;span&gt; getAgent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;AgentNode&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 实现逻辑...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   * 创建新 Agent&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   * &lt;/span&gt;&lt;span&gt;@param&lt;/span&gt;&lt;span&gt; payload&lt;/span&gt;&lt;span&gt; - Agent 创建参数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   * &lt;/span&gt;&lt;span&gt;@returns&lt;/span&gt;&lt;span&gt; 创建成功的 Agent 节点&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  async&lt;/span&gt;&lt;span&gt; createAgent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; CreateAgentDTO&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;AgentNode&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 实现逻辑...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成的 README.md 会自动包含:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有公共方法的签名&lt;/li&gt;
&lt;li&gt;参数和返回值的 TypeScript 类型&lt;/li&gt;
&lt;li&gt;JSDoc 中的业务说明&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 实施效果与未来展望&lt;a href=&quot;#6-实施效果与未来展望&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;6.1 量化收益&lt;a href=&quot;#61-量化收益&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指标&lt;/th&gt;&lt;th&gt;改进前&lt;/th&gt;&lt;th&gt;改进后&lt;/th&gt;&lt;th&gt;提升幅度&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;AI 代码准确率&lt;/td&gt;&lt;td&gt;60%&lt;/td&gt;&lt;td&gt;95%&lt;/td&gt;&lt;td&gt;+58%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;文档维护成本&lt;/td&gt;&lt;td&gt;每周 4 小时&lt;/td&gt;&lt;td&gt;0 小时&lt;/td&gt;&lt;td&gt;-100%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;新成员上手时间&lt;/td&gt;&lt;td&gt;2 周&lt;/td&gt;&lt;td&gt;3 天&lt;/td&gt;&lt;td&gt;-78%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;重复代码率&lt;/td&gt;&lt;td&gt;35%&lt;/td&gt;&lt;td&gt;8%&lt;/td&gt;&lt;td&gt;-77%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;6.2 技术演进方向&lt;a href=&quot;#62-技术演进方向&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;增量编译&lt;/strong&gt;: 只对修改的文件重新生成文档,提升大型项目的构建速度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语义索引&lt;/strong&gt;: 基于 Embedding 技术建立代码语义搜索,让 AI 能够理解”相似功能”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨项目复用&lt;/strong&gt;: 将成熟的组件/逻辑抽象为 NPM 包,配套生成 AI 可读的 &lt;code&gt;llms.txt&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 总结&lt;div&gt;&lt;/div&gt; 开发的哲学&lt;a href=&quot;#7-总结-开发的哲学&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这套体系的本质是 &lt;strong&gt;“约束即自由”&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过 &lt;strong&gt;AST 确定性&lt;/strong&gt; 约束了文档生成的随意性&lt;/li&gt;
&lt;li&gt;通过 &lt;strong&gt;目录结构&lt;/strong&gt; 约束了上下文的泛滥&lt;/li&gt;
&lt;li&gt;通过 &lt;strong&gt;TypeScript&lt;/strong&gt; 约束了类型的模糊性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最终换来的是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开发者从文档维护中彻底解放&lt;/li&gt;
&lt;li&gt;AI 从”猜测”变为”理解”&lt;/li&gt;
&lt;li&gt;团队协作从”口口相传”变为”制度保障”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;这不是一个技术方案,而是一次开发范式的升级。&lt;/strong&gt;&lt;/p&gt;</content:encoded><category>category:项目</category><category>tag:AI</category><category>tag:前端</category><category>tag:重构</category><category>tag:架构设计</category></item><item><title>规范驱动 + 需求 Loop AI 编程工作流 Demo</title><link>https://yuki-bloom.vercel.app/ja/post/gui-fan-qu-dong-xu-qiu-loop-ai-bian-cheng-gong-zuo-liu-demo</link><guid isPermaLink="false">ja:gui-fan-qu-dong-xu-qiu-loop-ai-bian-cheng-gong-zuo-liu-demo</guid><description>在现有 React 项目中，通过扩展 OpenSpec 自定义 Schema，实现一套完整的&quot;Skill 知识库 + 需求 Loop + 规范驱动实现 + 自动验证&quot;工作流。</description><pubDate>Tue, 14 Apr 2026 11:43:46 GMT</pubDate><content:encoded>&lt;h1&gt;规范驱动 + 需求 Loop AI 编程工作流 Demo&lt;a href=&quot;#规范驱动--需求-loop-ai-编程工作流-demo&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;h2&gt;目标&lt;a href=&quot;#目标&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在现有 React 项目中，通过&lt;strong&gt;扩展 OpenSpec 自定义 Schema&lt;/strong&gt;，实现一套完整的”Skill 知识库 + 需求 Loop + 规范驱动实现 + 自动验证”工作流。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键认知&lt;/strong&gt;：这套工作流运行在 AI IDE（Cursor / Claude Code / CodeFlicker）的对话框里，我们不需要实现对话 UI，我们要做的是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 OpenSpec 里创建一个自定义 Schema &lt;code&gt;loop-driven&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Schema 里新增 &lt;code&gt;requirement-loop&lt;/code&gt; artifact（放在 &lt;code&gt;proposal&lt;/code&gt; 之前）&lt;/li&gt;
&lt;li&gt;这个 artifact 的 &lt;code&gt;instruction&lt;/code&gt; 引导 AI 检索 Skill 知识库、逐条反问用户&lt;/li&gt;
&lt;li&gt;后续正常走 &lt;code&gt;proposal → specs → design → tasks → /apply → /verify&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;┌─────────────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  触发路径 A（隐式）            触发路径 B（显式）              │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│                                                             │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  用户: &quot;帮我实现登录功能&quot;      用户: &quot;/opsx:propose add-login&quot; │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│        ↓                              ↓                     │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  AI 检测到需求模糊            AI 等待用户描述需求              │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  → &quot;需求描述较模糊，建议先     → &quot;请简单描述你的登录功能需求&quot;   │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│    用 /opsx:propose 完善，     → 用户输入后                   │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│    是否继续？&quot;                                               │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│        ↓（用户确认）                  ↓                     │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└──────────────────────┬──────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                       ↓（两条路在这里汇合）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[requirement-loop artifact]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  AI 读取 openspec/skills/login.md 知识库&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  → 逐条询问用户最佳实践选项（在 AI IDE 对话框里完成）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  → 用户回答 Yes/No 或补充说明&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  → 生成 requirement-loop.md（需求确认文档）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;proposal artifact（依赖 requirement-loop.md）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  → AI 基于确认后的需求生成 proposal.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;specs artifact → design artifact → tasks artifact&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/opsx:apply  →  /opsx:verify&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 整体架构&lt;a href=&quot;#1-整体架构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1.1 文件结构&lt;a href=&quot;#11-文件结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;my-react-app/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── openspec/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── config.yaml                    ← 项目配置：设置默认 schema 为 loop-driven&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── schemas/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   └── loop-driven/               ← 自定义 Schema（核心产物）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │       ├── schema.yaml            ← Workflow 定义（含新增的 requirement-loop artifact）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │       └── templates/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │           ├── requirement-loop.md ← Loop artifact 的模板（引导 AI 如何提问）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │           ├── proposal.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │           ├── spec.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │           ├── design.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │           └── tasks.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── skills/                        ← Skill 知识库（最佳实践沉淀，供 AI 检索）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── login.md                   ← 登录功能最佳实践&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── payment.md                 ← 支付功能最佳实践（未来扩展）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   └── README.md                  ← 说明如何使用 Skill 库&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── specs/                         ← 系统级规范（归档后持久化）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── changes/                       ← 每次变更目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│       └── add-login/                 ← 本次演示用的变更&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│           ├── requirement-loop.md    ← 需求确认文档（Loop 产物）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│           ├── proposal.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│           ├── design.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│           ├── tasks.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│           └── specs/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│               └── auth-login/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│                   └── spec.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── src/                               ← React 代码（按 tasks.md 实现）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── components/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── Login/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            ├── LoginForm.tsx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            └── useLoginLogic.ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 工作流全景图&lt;a href=&quot;#12-工作流全景图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;两条触发路径，汇合后走相同的 Loop + OpenSpec 流程：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;路径 A — 隐式触发（自然语言 → AI 主动建议）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;开发者: &quot;帮我实现一个登录功能&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AI 判断需求模糊（依赖 config.yaml 的 context rules 注入）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  → 回复: &quot;你的需求描述比较简短，为确保实现质量，建议先用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            /opsx:propose 完善需求细节，是否继续？&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ↓ 用户确认&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;进入 requirement-loop artifact&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;路径 B — 显式触发（直接调用 propose）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;开发者: &quot;/opsx:propose add-login&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AI: &quot;请简单描述你的登录功能需求（比如：面向什么用户、&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     有无特殊安全要求等），我来帮你完善规范。&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ↓ 用户输入需求描述&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;进入 requirement-loop artifact&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;两条路汇合后的统一流程：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[requirement-loop artifact]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AI 读取 openspec/skills/{feature}.md 知识库&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ 逐条反问用户（在 AI IDE 对话框里，就是普通聊天）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ 用户回答 Yes/No 或追加参数说明&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ 生成 requirement-loop.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ proposal.md（基于确认需求）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ specs/auth-login/spec.md（GIVEN/WHEN/THEN）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ design.md（技术决策）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ tasks.md（实现清单）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/opsx:apply  → 代码实现&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/opsx:verify → 规范验证&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 路径 A 的实现方式&lt;a href=&quot;#13-路径-a-的实现方式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;路径 A 不需要写代码，通过 &lt;code&gt;openspec/config.yaml&lt;/code&gt; 的 &lt;code&gt;context&lt;/code&gt; 向 AI 注入行为规则：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  当用户输入与功能实现相关的简短需求（如&quot;帮我实现登录&quot;、&quot;做一个支付页面&quot;），&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  且需求描述不够详细时，请主动提示：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;需求描述较模糊，建议先运行 /opsx:propose &amp;lt;feature-name&amp;gt; 完善需求细节，&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  以确保实现质量，是否继续？&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  这样可以触发 requirement-loop，确保在写代码之前把需求对齐清楚。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这条 context 会被注入到所有 AI 响应的系统提示里，让 AI IDE 具备”主动建议进入 Loop”的行为。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 核心产物：loop-driven Schema&lt;a href=&quot;#2-核心产物loop-driven-schema&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;2.1 schema.yaml&lt;a href=&quot;#21-schemayaml&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文件：&lt;code&gt;openspec/schemas/loop-driven/schema.yaml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;loop-driven&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;version&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  需求 Loop 驱动的规范开发流程。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  在生成 proposal 之前，先通过 AI 检索 Skill 知识库，&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  逐条与用户确认最佳实践，确保需求无遗漏后再进入实现阶段。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;artifacts&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;requirement-loop&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    generates&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;requirement-loop.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;需求确认文档（AI 检索 Skill 知识库后与用户逐条确认的结果）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    template&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;requirement-loop.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    instruction&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      你是一个经验丰富的技术架构师。用户想要实现一个新功能。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      你的任务分两步：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      **Step 1: 检索 Skill 知识库**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      读取 openspec/skills/ 目录下与当前功能相关的 Skill 文件。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      根据用户描述的功能名称（如 &quot;login&quot;、&quot;payment&quot;），&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      找到对应的 skills/*.md 文件，提取其中的最佳实践列表。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      **Step 2: 逐条需求确认（Loop）**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      对检索到的每一条最佳实践：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      1. 用自然语言向用户提问（包含：这是什么、为什么需要它、推荐默认值）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      2. 等待用户回答（Yes/No 或追加说明）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      3. 记录用户的选择和补充&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      直到所有最佳实践都确认完毕，才能生成 requirement-loop.md。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      生成的 requirement-loop.md 必须包含：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - 用户的原始需求描述&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - 每条最佳实践的确认结果（选择 + 具体参数）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - 明确的 Out of Scope（用户选择不实现的）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    requires&lt;/span&gt;&lt;span&gt;: []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;proposal&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    generates&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;proposal.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;变更提案&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    template&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;proposal.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    instruction&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      基于 requirement-loop.md 中已确认的需求，生成变更提案。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      proposal 必须完整反映用户在 Loop 中确认的所有选项。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    requires&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;requirement-loop&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;specs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    generates&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;specs/**/*.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;功能规范（GIVEN/WHEN/THEN 格式）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    template&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;spec.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    instruction&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      基于 proposal.md 和 requirement-loop.md，&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      为每一条已确认的需求生成对应的 Requirement + Scenario。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      使用标准的 GIVEN/WHEN/THEN 格式。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    requires&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;proposal&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;design&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    generates&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;design.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;技术设计文档&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    template&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;design.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    requires&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;specs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;tasks&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    generates&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;tasks.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;实现任务清单&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    template&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;tasks.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    instruction&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      基于 design.md 和 specs，生成分阶段的实现任务清单。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      每条任务必须能追溯到 spec.md 中的某个 Requirement。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    requires&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;design&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  requires&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;tasks&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  tracks&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;tasks.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 requirement-loop.md 模板&lt;a href=&quot;#22-requirement-loopmd-模板&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文件：&lt;code&gt;openspec/schemas/loop-driven/templates/requirement-loop.md&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Requirement Loop: {{feature-name}}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 用户原始需求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- 记录用户的原始输入，原文保留 --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 检索到的相关最佳实践&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- 列出从 Skill 知识库中找到的相关实践清单 --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 需求确认结果&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- 每条最佳实践的确认状态，格式如下：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### ✅ [最佳实践名称]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- **选择**: 需要&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- **参数**: [用户补充的具体参数，如 RT 有效期 = 30天]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- **纳入规范**: The system SHALL ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### ❌ [最佳实践名称]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- **选择**: 不需要&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- **原因**: [用户说明的原因，或 N/A]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;--&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## Out of Scope&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- 明确列出本次不实现的内容，防止后续争议 --&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Skill 知识库&lt;a href=&quot;#3-skill-知识库&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;3.1 登录功能最佳实践&lt;a href=&quot;#31-登录功能最佳实践&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文件：&lt;code&gt;openspec/skills/login.md&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Login Feature Skills&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt; 这是登录功能的最佳实践知识库。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt; 当用户要实现登录功能时，AI 应逐条确认以下实践。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 最佳实践清单&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 1. 双 Token 机制（dual-token）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**分类**&lt;/span&gt;&lt;span&gt;: Security&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**推荐**&lt;/span&gt;&lt;span&gt;: ✅ 是&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**说明**&lt;/span&gt;&lt;span&gt;: 使用 Access Token（AT，短期，建议 15 分钟）+ Refresh Token（RT，长期，建议 7 天）。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AT 过期后用 RT 静默刷新，用户无感知。避免长期 Token 泄露风险。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**需确认的参数**&lt;/span&gt;&lt;span&gt;: AT 有效期、RT 有效期、Token 存储方式（localStorage / httpOnly Cookie）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 2. 登录按钮防抖（debounce）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**分类**&lt;/span&gt;&lt;span&gt;: UX&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**推荐**&lt;/span&gt;&lt;span&gt;: ✅ 是&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**说明**&lt;/span&gt;&lt;span&gt;: 防止用户多次点击导致重复发送请求。建议 300ms 防抖 + 请求期间禁用按钮。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**需确认的参数**&lt;/span&gt;&lt;span&gt;: 防抖时长（默认 300ms）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 3. 实时表单校验（form-validation）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**分类**&lt;/span&gt;&lt;span&gt;: UX&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**推荐**&lt;/span&gt;&lt;span&gt;: ✅ 是&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**说明**&lt;/span&gt;&lt;span&gt;: 用户输入时即时提示错误（邮箱格式、密码强度），减少提交后报错的挫败感。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**需确认的参数**&lt;/span&gt;&lt;span&gt;: 校验时机（onBlur / onChange）、使用 Zod / Yup / 原生&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 4. 密码前端加密（password-encrypt）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**分类**&lt;/span&gt;&lt;span&gt;: Security&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**推荐**&lt;/span&gt;&lt;span&gt;: ⚠️ 视情况&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**说明**&lt;/span&gt;&lt;span&gt;: 在 HTTPS 普及的情况下，前端加密价值有限，但对安全要求高的场景（金融/企业）建议开启。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**需确认的参数**&lt;/span&gt;&lt;span&gt;: 加密算法（bcrypt 在前端不适用，通常用 SHA-256 或 RSA 公钥加密）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 5. 记住我（remember-me）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**分类**&lt;/span&gt;&lt;span&gt;: UX&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**推荐**&lt;/span&gt;&lt;span&gt;: ✅ 是&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**说明**&lt;/span&gt;&lt;span&gt;: 允许用户选择是否保持登录状态更长时间（延长 RT 有效期或使用持久化存储）。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**需确认的参数**&lt;/span&gt;&lt;span&gt;: 记住时长（默认 30 天）、实现方式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 6. 验证码（captcha）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**分类**&lt;/span&gt;&lt;span&gt;: Security&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**推荐**&lt;/span&gt;&lt;span&gt;: ⚠️ 视情况&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**说明**&lt;/span&gt;&lt;span&gt;: 防止暴力破解。可以在连续失败 N 次后触发（而非每次都要求）。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**需确认的参数**&lt;/span&gt;&lt;span&gt;: 触发条件（失败几次后）、类型（图片/短信/行为验证）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 7. 细分错误类型（granular-errors）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**分类**&lt;/span&gt;&lt;span&gt;: UX&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**推荐**&lt;/span&gt;&lt;span&gt;: ✅ 是&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**说明**&lt;/span&gt;&lt;span&gt;: 区分&quot;账号不存在&quot;和&quot;密码错误&quot;的提示，帮助用户定位问题（注意：某些场景出于安全考虑故意模糊）。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**需确认的参数**&lt;/span&gt;&lt;span&gt;: 是否区分（部分场景为防止账号枚举攻击，应统一返回&quot;账号或密码错误&quot;）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 8. Loading 状态（loading-state）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**分类**&lt;/span&gt;&lt;span&gt;: UX&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**推荐**&lt;/span&gt;&lt;span&gt;: ✅ 是&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**说明**&lt;/span&gt;&lt;span&gt;: 登录请求期间展示 Loading 指示器，防止用户以为没有响应而重复点击。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**需确认的参数**&lt;/span&gt;&lt;span&gt;: 展示方式（按钮 loading / 全屏遮罩）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;### 9. 前端限流（rate-limiting）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**分类**&lt;/span&gt;&lt;span&gt;: Security&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**推荐**&lt;/span&gt;&lt;span&gt;: ✅ 是&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**说明**&lt;/span&gt;&lt;span&gt;: 前端记录失败次数，超过阈值后锁定登录一段时间（配合后端限流双重保障）。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**需确认的参数**&lt;/span&gt;&lt;span&gt;: 最大失败次数（默认 5）、锁定时长（默认 15 分钟）&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 README&lt;a href=&quot;#32-readme&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文件：&lt;code&gt;openspec/skills/README.md&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# OpenSpec Skills 知识库&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这里存放各功能模块的最佳实践知识库，供 &lt;/span&gt;&lt;span&gt;`requirement-loop`&lt;/span&gt;&lt;span&gt; artifact 检索使用。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 结构&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; `login.md`&lt;/span&gt;&lt;span&gt; — 登录/认证相关最佳实践&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; `payment.md`&lt;/span&gt;&lt;span&gt; — 支付相关最佳实践（待补充）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 如何使用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;当 AI 执行 &lt;/span&gt;&lt;span&gt;`requirement-loop`&lt;/span&gt;&lt;span&gt; artifact 时，会自动读取本目录下与当前功能相关的文件。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;也可以在 &lt;/span&gt;&lt;span&gt;`openspec/config.yaml`&lt;/span&gt;&lt;span&gt; 的 context 里显式声明 Skills 路径。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 如何扩展&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;为新功能添加 Skills：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;1.&lt;/span&gt;&lt;span&gt; 在此目录新建 &lt;/span&gt;&lt;span&gt;`{feature}.md`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;2.&lt;/span&gt;&lt;span&gt; 按照现有格式填写最佳实践清单&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;3.&lt;/span&gt;&lt;span&gt; 每条实践包含：分类、推荐程度、说明、需确认的参数&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 项目配置&lt;a href=&quot;#4-项目配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;4.1 config.yaml&lt;a href=&quot;#41-configyaml&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文件：&lt;code&gt;openspec/config.yaml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;schema&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;loop-driven&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Tech stack: TypeScript, React 19, Vite, Tailwind CSS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Form validation: Zod + React Hook Form&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Auth pattern: JWT (Access Token + Refresh Token)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  State management: React hooks (no Redux)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  当执行 requirement-loop 时，请读取 openspec/skills/ 目录下对应的 Skill 文件。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;rules&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  requirement-loop&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;必须逐条询问，不得跳过任何 Skill&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;每个问题都要说明&quot;为什么需要它&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;用户的补充说明必须原文记录到 requirement-loop.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;生成文档前必须得到用户对所有条目的明确答复&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  specs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;使用 Given/When/Then 格式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;每条 Requirement 必须能追溯到 requirement-loop.md 中的某个确认结果&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  tasks&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;每个 Task 必须注明对应的 spec Requirement ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 完整演示流程&lt;a href=&quot;#5-完整演示流程&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;5.1 路径 A 演示（隐式触发：模糊需求 → AI 主动建议）&lt;a href=&quot;#51-路径-a-演示隐式触发模糊需求--ai-主动建议&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;开发者: &quot;帮我实现一个登录功能&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AI:     &quot;你的需求描述比较简短，为确保实现符合最佳实践，&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;         建议先用 /opsx:propose 完善需求细节，是否继续？&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;开发者: &quot;好的&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AI:     &quot;请简单描述你的登录场景（比如：B端还是C端？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;         有无特殊安全要求？目标用户量？）&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;开发者: &quot;C端用户，日活10万，需要比较严格的安全控制&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AI:     &quot;明白了。检索登录知识库...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;         找到 9 条相关实践，我们逐一确认 ↓&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;         [进入 Loop →]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 路径 B 演示（显式触发：直接 /opsx&lt;div&gt;&lt;/div&gt;）&lt;a href=&quot;#52-路径-b-演示显式触发直接-opsx&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;开发者: /opsx:propose add-login&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AI:     &quot;请简单描述你的登录功能需求。&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;开发者: &quot;标准账号密码登录，C端，安全性要求较高&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AI:     &quot;好的，检索登录知识库... 找到 9 条相关实践，逐一确认 ↓&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;         [进入 Loop →]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 Loop 对话（两条路径汇合后相同）&lt;a href=&quot;#53-loop-对话两条路径汇合后相同&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;AI：[1/9] 双 Token 机制&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    建议使用 Access Token（短期，15分钟）+ Refresh Token（长期，7天）。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    AT 过期后用 RT 静默刷新，用户无感知，避免长期 Token 泄露。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    你的项目需要这个吗？如果需要，AT 和 RT 的有效期用默认值吗？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;用户：需要，RT 改成 30 天&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AI：记录 ✅ 双 Token，AT=15min，RT=30天。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    [2/9] 登录按钮防抖&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    防止多次点击重复发送请求，建议 300ms 防抖 + 请求期间禁用按钮。需要吗？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;用户：需要&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...（9条全部确认）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;AI：需求确认完成！&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    生成 requirement-loop.md ✓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    生成 proposal.md ✓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    生成 specs/auth-login/spec.md ✓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    生成 design.md ✓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    生成 tasks.md ✓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    所有文档已就绪，请审阅后运行 /opsx:apply 开始实现。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 实施顺序&lt;a href=&quot;#6-实施顺序&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;













































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;步骤&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;文件&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;安装 OpenSpec CLI&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;td&gt;&lt;code&gt;npm install -g @fission-ai/openspec@latest&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;初始化 OpenSpec&lt;/td&gt;&lt;td&gt;&lt;code&gt;openspec/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;openspec init&lt;/code&gt; 生成基础目录&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;Fork 内置 schema&lt;/td&gt;&lt;td&gt;&lt;code&gt;openspec/schemas/loop-driven/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;openspec schema fork spec-driven loop-driven&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;编写 Skill 知识库&lt;/td&gt;&lt;td&gt;&lt;code&gt;openspec/skills/login.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;核心产物，包含 9 条登录最佳实践&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;修改 schema.yaml&lt;/td&gt;&lt;td&gt;&lt;code&gt;openspec/schemas/loop-driven/schema.yaml&lt;/code&gt;&lt;/td&gt;&lt;td&gt;新增 &lt;code&gt;requirement-loop&lt;/code&gt; artifact，调整依赖链&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;编写 Loop 模板&lt;/td&gt;&lt;td&gt;&lt;code&gt;openspec/schemas/loop-driven/templates/requirement-loop.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;AI 生成 requirement-loop 文档的结构模板&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;配置 config.yaml&lt;/td&gt;&lt;td&gt;&lt;code&gt;openspec/config.yaml&lt;/code&gt;&lt;/td&gt;&lt;td&gt;设置默认 schema + 注入 context + Loop 规则&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;验证 schema&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;td&gt;&lt;code&gt;openspec schema validate loop-driven&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;执行演示 Loop&lt;/td&gt;&lt;td&gt;&lt;code&gt;openspec/changes/add-login/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;/opsx:propose add-login&lt;/code&gt; 跑通完整流程&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;对照 tasks.md 实现登录组件&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/components/Login/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;这是最终的代码产物&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;11&lt;/td&gt;&lt;td&gt;执行 verify&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;td&gt;&lt;code&gt;/opsx:verify&lt;/code&gt; 验证覆盖率&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 核心设计哲学&lt;a href=&quot;#7-核心设计哲学&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Skill 库 = 沉淀在版本控制里的最佳实践&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不是散落在 Wiki 里的文档，而是 &lt;code&gt;openspec/skills/*.md&lt;/code&gt;，随代码一起 git 管理&lt;/li&gt;
&lt;li&gt;新人入职可以直接读；AI 接任务也可以直接读&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Loop = OpenSpec 工作流的前置环节，不是独立 App&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过自定义 Schema 的 &lt;code&gt;requirement-loop&lt;/code&gt; artifact 实现&lt;/li&gt;
&lt;li&gt;运行在 AI IDE 的对话框里，用户体验与正常聊天无异&lt;/li&gt;
&lt;li&gt;完成后自动衔接后续的 proposal/specs/design/tasks&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OpenSpec = 唯一真相来源&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;spec.md&lt;/code&gt; 里的 GIVEN/WHEN/THEN 是可验证的规范&lt;/li&gt;
&lt;li&gt;&lt;code&gt;requirement-loop.md&lt;/code&gt; 存档了”为什么这样设计”的决策过程&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tasks.md&lt;/code&gt; 是代码实现的 checklist&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Verify = 闭环，不是摆设&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/opsx:verify&lt;/code&gt; 让 AI 对照 spec.md 逐条检查实现&lt;/li&gt;
&lt;li&gt;每个 task 都能追溯到 spec 的 Requirement，形成完整的可追溯链&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;可扩展性&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;想支持支付功能？新建 &lt;code&gt;openspec/skills/payment.md&lt;/code&gt; 即可&lt;/li&gt;
&lt;li&gt;这套 Schema 和 Skills 库可以在团队内所有项目共享&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>category:前端</category><category>tag:AI</category><category>tag:OpenSpec</category></item><item><title>VIVO面经</title><link>https://yuki-bloom.vercel.app/ja/post/vivo-mian-jing</link><guid isPermaLink="false">ja:vivo-mian-jing</guid><description>VIVO 前端一面面经，深度考察 AST 静态分析、Git Hash 增量设计、MCP 组件文档库、AI Code Review 质量保障等 AI 工程化方向。</description><pubDate>Thu, 12 Mar 2026 07:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;VIVO&lt;a href=&quot;#vivo&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;面试时间&lt;a href=&quot;#面试时间&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;2026_0312-15:00&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;面试内容&lt;a href=&quot;#面试内容&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;自我介绍&lt;/li&gt;
&lt;li&gt;AST 静态分析：TS 类型与注释自动转 AI 可读索引如何实现&lt;/li&gt;
&lt;li&gt;Git Hash 增量设计：Hash 冲突处理、如何确保只更新变更文件文档&lt;/li&gt;
&lt;li&gt;MCP 自动生成组件文档库实现细节&lt;/li&gt;
&lt;li&gt;多分支团队开发下如何保证组件文档最新&lt;/li&gt;
&lt;li&gt;平时如何使用 AI 编码工具&lt;/li&gt;
&lt;li&gt;大量 AI 生成代码时如何保证质量（AI Code Review、多 AI 交叉 Review）&lt;/li&gt;
&lt;li&gt;需求完成后如何测试（手动 + 可自动模拟执行测试）&lt;/li&gt;
&lt;li&gt;MCP 和 Skill 的区别&lt;/li&gt;
&lt;li&gt;反问&lt;/li&gt;
&lt;li&gt;业务方向：AI 全栈、内部工具、ToB 与 ToC 皆有&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>category:面经</category><category>tag:VIVO</category></item><item><title>快手面经（二）</title><link>https://yuki-bloom.vercel.app/ja/post/kuai-shou-mian-jing-2</link><guid isPermaLink="false">ja:kuai-shou-mian-jing-2</guid><description>快手前端一面面经（第二次），考察 AI 对话打字机效果、React useEffect、React.memo vs useMemo 等，面试官分享前端热点方向建议，已收到 Offer。</description><pubDate>Mon, 09 Mar 2026 07:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;快手&lt;a href=&quot;#快手&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;面试时间&lt;a href=&quot;#面试时间&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;2026_0309-15:00&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;面试内容&lt;a href=&quot;#面试内容&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;面试官介绍抖音、PDD、京东、淘宝、快手电商布局&lt;/li&gt;
&lt;li&gt;介绍前端团队方向&lt;/li&gt;
&lt;li&gt;C 端 App、B 端商家服务平台、内容审核与监测&lt;/li&gt;
&lt;li&gt;自我介绍&lt;/li&gt;
&lt;li&gt;AI 对话框长对话和打字机效果是否考虑开源库&lt;/li&gt;
&lt;li&gt;未使用库时如何实现（SSE + setTimeout）&lt;/li&gt;
&lt;li&gt;React useEffect 第二个参数三种情况&lt;/li&gt;
&lt;li&gt;React.memo 和 useMemo 区别&lt;/li&gt;
&lt;li&gt;对前端热点方向的理解&lt;/li&gt;
&lt;li&gt;cursor 编辑器是自费还是公司提供&lt;/li&gt;
&lt;li&gt;反问：对实习生的期待&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;面试官给的建议&lt;a href=&quot;#面试官给的建议&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;JS 基础要扎实&lt;/li&gt;
&lt;li&gt;项目经验要丰富&lt;/li&gt;
&lt;li&gt;关注前端最新方向&lt;/li&gt;
&lt;li&gt;保持对前端的热爱&lt;/li&gt;
&lt;li&gt;做好校招储备&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>category:面经</category><category>tag:快手</category></item></channel></rss>