<?xml version="1.0" encoding="UTF-8"?><rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>掘金本周最热</title><link>https://juejin.im/recommended?sort=weekly_hottest</link><atom:link href="http://rsshub.bestblogs.dev/juejin/trending/all/weekly" rel="self" type="application/rss+xml"></atom:link><description>掘金本周最热 - Powered by RSSHub</description><generator>RSSHub</generator><webMaster>contact@rsshub.app (RSSHub)</webMaster><language>en</language><lastBuildDate>Tue, 21 Apr 2026 04:37:11 GMT</lastBuildDate><ttl>5</ttl><item><title>前端重生之 - 前端视角下的 Python</title><description>&lt;p&gt;以前我认为 JavaScript 就是编程世界的全部。从 jQuery 时代的 DOM 操作，到 React/Vue 的组件化革命，再到 TypeScript 的类型安全，见证了前端技术的每一次跃迁。然而，AI 时代来临，人人都在喊转 “全栈“，所以我也开始真正深入 Python 的生态系统，才发现这不仅是两门语言的对话，更是两种编程哲学、两种技术文化的碰撞与融合。这篇文章，是我从前端视角重新审视 Python 的记录，也是我对技术本质的一次探索，接下来我还将从前端视角看 Java、Go、C# 等不同的后端的语言，可能会有错误的地方，欢迎指正，也欢迎关注我，后期还将有分析其他语言的文章，奥利给！&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-0&quot;&gt;从 JS 的异步到 Python 的同步&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-1&quot;&gt;1.1 事件循环的底层机制&lt;/h3&gt;
&lt;p&gt;前端对事件循环（Event Loop）的理解，往往始于浏览器中的 &lt;code&gt;setTimeout&lt;/code&gt; 和 &lt;code&gt;Promise&lt;/code&gt;。JavaScript 的单线程异步模型，是为了应对浏览器环境中用户交互、网络请求等 I/O 密集型场景而设计的。我们习惯了回调地狱的煎熬，也享受过 &lt;code&gt;async/await&lt;/code&gt; 带来的语法糖甜蜜。但很少有人深入思考：为什么 JavaScript 必须是单线程的？这个设计选择背后的权衡是什么？&lt;/p&gt;
&lt;p&gt;JavaScript 诞生于浏览器环境，而浏览器的核心职责是渲染页面和响应用户交互。如果 JavaScript 是多线程的，一个线程正在修改 DOM，另一个线程同时也在修改同一个 DOM 节点，就会产生竞争条件（Race Condition），导致不可预测的行为。为了避免这种复杂性，JavaScript 的设计者选择了单线程模型，并通过事件循环来实现异步非阻塞 I/O。&lt;/p&gt;
&lt;p&gt;浏览器的事件循环可以简化为以下伪代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;while&lt;/span&gt; (&lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;) {
    &lt;span class=&quot;hljs-comment&quot;&gt;// 1. 执行宏任务队列中的一个任务&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; macroTask = macroTaskQueue.&lt;span class=&quot;hljs-title function_&quot;&gt;shift&lt;/span&gt;();
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (macroTask) &lt;span class=&quot;hljs-title function_&quot;&gt;execute&lt;/span&gt;(macroTask);
    
    &lt;span class=&quot;hljs-comment&quot;&gt;// 2. 执行所有微任务&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;while&lt;/span&gt; (microTaskQueue.&lt;span class=&quot;hljs-property&quot;&gt;length&lt;/span&gt; &amp;gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;) {
        &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; microTask = microTaskQueue.&lt;span class=&quot;hljs-title function_&quot;&gt;shift&lt;/span&gt;();
        &lt;span class=&quot;hljs-title function_&quot;&gt;execute&lt;/span&gt;(microTask);
    }
    
    &lt;span class=&quot;hljs-comment&quot;&gt;// 3. 渲染（如果需要）&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (shouldRender) &lt;span class=&quot;hljs-title function_&quot;&gt;render&lt;/span&gt;();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个模型保证了 JavaScript 的执行顺序是可预测的：宏任务 → 微任务 → 渲染。Promise 的回调之所以比 setTimeout 先执行，就是因为它们被放入了微任务队列。&lt;/p&gt;
&lt;p&gt;然而，当我第一次接触 Python 的 &lt;code&gt;asyncio&lt;/code&gt; 时，一种奇妙的熟悉感与陌生感同时涌现。Python 的协程机制与 JavaScript 的 Promise 有着惊人的相似性，但底层哲学却截然不同。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-2&quot;&gt;1.2 Python asyncio 的设计哲学&lt;/h3&gt;
&lt;p&gt;Python 的 &lt;code&gt;asyncio&lt;/code&gt; 是在 Python 3.4 中引入的，在 3.5 中通过 &lt;code&gt;async/await&lt;/code&gt; 语法得到大幅改进。与 JavaScript 不同，Python 并不是天生单线程的——它有多线程（&lt;code&gt;threading&lt;/code&gt; 模块）和多进程（&lt;code&gt;multiprocessing&lt;/code&gt; 模块）的完整支持。&lt;code&gt;asyncio&lt;/code&gt; 是 Python 对协程（Coroutine）这一并发模型的选择，而不是被迫的设计。&lt;/p&gt;
&lt;p&gt;让我们深入对比两者的实现：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Python asyncio 示例&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; asyncio

&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;fetch_data&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;url&lt;/span&gt;):
    &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;开始请求: &lt;span class=&quot;hljs-subst&quot;&gt;{url}&lt;/span&gt;&quot;&lt;/span&gt;)
    &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; asyncio.sleep(&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;)  &lt;span class=&quot;hljs-comment&quot;&gt;# 模拟网络请求&lt;/span&gt;
    &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;请求完成: &lt;span class=&quot;hljs-subst&quot;&gt;{url}&lt;/span&gt;&quot;&lt;/span&gt;)
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;f&quot;数据来自 &lt;span class=&quot;hljs-subst&quot;&gt;{url}&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;main&lt;/span&gt;():
    &lt;span class=&quot;hljs-comment&quot;&gt;# 并发执行多个任务&lt;/span&gt;
    tasks = [
        fetch_data(&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://api.example.com/1&quot;&lt;/span&gt;),
        fetch_data(&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://api.example.com/2&quot;&lt;/span&gt;),
        fetch_data(&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://api.example.com/3&quot;&lt;/span&gt;)
    ]
    results = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; asyncio.gather(*tasks)
    &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(results)

asyncio.run(main())
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// JavaScript 对比实现&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;fetchData&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;url&lt;/span&gt;) {
    &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;`开始请求: &lt;span class=&quot;hljs-subst&quot;&gt;${url}&lt;/span&gt;`&lt;/span&gt;);
    &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Promise&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;resolve&lt;/span&gt; =&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;setTimeout&lt;/span&gt;(resolve, &lt;span class=&quot;hljs-number&quot;&gt;1000&lt;/span&gt;));
    &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;`请求完成: &lt;span class=&quot;hljs-subst&quot;&gt;${url}&lt;/span&gt;`&lt;/span&gt;);
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;`数据来自 &lt;span class=&quot;hljs-subst&quot;&gt;${url}&lt;/span&gt;`&lt;/span&gt;;
}

&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;main&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; tasks = [
        &lt;span class=&quot;hljs-title function_&quot;&gt;fetchData&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://api.example.com/1&quot;&lt;/span&gt;),
        &lt;span class=&quot;hljs-title function_&quot;&gt;fetchData&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://api.example.com/2&quot;&lt;/span&gt;),
        &lt;span class=&quot;hljs-title function_&quot;&gt;fetchData&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://api.example.com/3&quot;&lt;/span&gt;)
    ];
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; results = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Promise&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;all&lt;/span&gt;(tasks);
    &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(results);
}

&lt;span class=&quot;hljs-title function_&quot;&gt;main&lt;/span&gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表面看两者几乎相同，但底层实现有本质区别：&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;特性&lt;/th&gt;&lt;th&gt;JavaScript&lt;/th&gt;&lt;th&gt;Python&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;事件循环&lt;/td&gt;&lt;td&gt;浏览器/Node 内置，不可替换&lt;/td&gt;&lt;td&gt;asyncio 库实现，可自定义&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;协程实现&lt;/td&gt;&lt;td&gt;基于 Promise 和微任务队列&lt;/td&gt;&lt;td&gt;基于生成器（Generator）和事件循环&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;GIL 影响&lt;/td&gt;&lt;td&gt;无（天生单线程）&lt;/td&gt;&lt;td&gt;有（多线程受 GIL 限制）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;并发性能&lt;/td&gt;&lt;td&gt;适合 I/O 密集型&lt;/td&gt;&lt;td&gt;适合 I/O 密集型，CPU 密集型需用多进程&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 data-id=&quot;heading-3&quot;&gt;1.3 GIL：Python 的&quot;阿喀琉斯之踵&quot;&lt;/h3&gt;
&lt;p&gt;谈到 Python 的并发，就不能不提 GIL（Global Interpreter Lock，全局解释器锁）。GIL 是 CPython 实现中的一个机制，它确保任何时候只有一个线程在执行 Python 字节码。这意味着，即使在多核 CPU 上，Python 的多线程也无法实现真正的并行计算。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; threading
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; time

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;cpu_bound_task&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;n&lt;/span&gt;):
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&quot;CPU 密集型任务&quot;&quot;&quot;&lt;/span&gt;
    count = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;range&lt;/span&gt;(n):
        count += i * i
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; count

&lt;span class=&quot;hljs-comment&quot;&gt;# 多线程版本（受 GIL 限制）&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;multi_threaded&lt;/span&gt;():
    threads = []
    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; _ &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;range&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt;):
        t = threading.Thread(target=cpu_bound_task, args=(&lt;span class=&quot;hljs-number&quot;&gt;10_000_000&lt;/span&gt;,))
        threads.append(t)
        t.start()
    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; t &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; threads:
        t.join()

&lt;span class=&quot;hljs-comment&quot;&gt;# 多进程版本（绕过 GIL）&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; multiprocessing &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; Process

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;multi_process&lt;/span&gt;():
    processes = []
    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; _ &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;range&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt;):
        p = Process(target=cpu_bound_task, args=(&lt;span class=&quot;hljs-number&quot;&gt;10_000_000&lt;/span&gt;,))
        processes.append(p)
        p.start()
    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; p &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; processes:
        p.join()

&lt;span class=&quot;hljs-comment&quot;&gt;# 性能对比&lt;/span&gt;
start = time.time()
multi_threaded()
&lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;多线程耗时: &lt;span class=&quot;hljs-subst&quot;&gt;{time.time() - start:&lt;span class=&quot;hljs-number&quot;&gt;.2&lt;/span&gt;f}&lt;/span&gt;秒&quot;&lt;/span&gt;)

start = time.time()
multi_process()
&lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;多进程耗时: &lt;span class=&quot;hljs-subst&quot;&gt;{time.time() - start:&lt;span class=&quot;hljs-number&quot;&gt;.2&lt;/span&gt;f}&lt;/span&gt;秒&quot;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在我的测试环境中（4 核 CPU），多线程版本耗时约 12 秒，而多进程版本仅需 3 秒。这就是 GIL 的影响——多线程在 CPU 密集型任务上无法发挥多核优势。&lt;/p&gt;
&lt;p&gt;JavaScript 没有 GIL 的问题，因为它天生就是单线程的。但这也意味着 JavaScript 无法利用多核 CPU 进行并行计算——除非使用 Worker Threads（Node.js）或 Web Workers（浏览器），但这些机制与主线程是隔离的，通信成本较高。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-4&quot;&gt;1.4 编程范式的思维转换&lt;/h3&gt;
&lt;p&gt;JavaScript 是一门多范式语言，但前端开发中函数式编程的影子无处不在：&lt;code&gt;map&lt;/code&gt;、&lt;code&gt;filter&lt;/code&gt;、&lt;code&gt;reduce&lt;/code&gt; 成为日常，Immutable.js 和 Ramda 这样的库广受欢迎。我们追求纯函数、避免副作用、崇尚不可变性。这种趋势在 React 的函数组件和 Hooks 中达到顶峰。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// React 函数组件 + Hooks（函数式风格）&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;React&lt;/span&gt;, { useState, useEffect } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;react&#39;&lt;/span&gt;;

&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;UserList&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;{ users }&lt;/span&gt;) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; [filteredUsers, setFilteredUsers] = &lt;span class=&quot;hljs-title function_&quot;&gt;useState&lt;/span&gt;([]);
    
    &lt;span class=&quot;hljs-title function_&quot;&gt;useEffect&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
        &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; activeUsers = users
            .&lt;span class=&quot;hljs-title function_&quot;&gt;filter&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;u&lt;/span&gt; =&amp;gt;&lt;/span&gt; u.&lt;span class=&quot;hljs-property&quot;&gt;isActive&lt;/span&gt;)
            .&lt;span class=&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;u&lt;/span&gt; =&amp;gt;&lt;/span&gt; ({ ...u, &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;: u.&lt;span class=&quot;hljs-property&quot;&gt;name&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;toUpperCase&lt;/span&gt;() }));
        &lt;span class=&quot;hljs-title function_&quot;&gt;setFilteredUsers&lt;/span&gt;(activeUsers);
    }, [users]);
    
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; (
        &lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;ul&lt;/span&gt;&amp;gt;&lt;/span&gt;
            {filteredUsers.map(u =&amp;gt; &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;key&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{u.id}&lt;/span&gt;&amp;gt;&lt;/span&gt;{u.name}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;li&lt;/span&gt;&amp;gt;&lt;/span&gt;)}
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;ul&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python 则是一门 &quot;batteries included&quot; 的语言，它拥抱多种范式却从不偏执。在 Python 中，你可以写出优雅的函数式代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Python 函数式风格&lt;/span&gt;
users = [
    {&lt;span class=&quot;hljs-string&quot;&gt;&quot;id&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;is_active&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;},
    {&lt;span class=&quot;hljs-string&quot;&gt;&quot;id&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;Bob&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;is_active&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;},
    {&lt;span class=&quot;hljs-string&quot;&gt;&quot;id&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;Charlie&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;is_active&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;}
]

&lt;span class=&quot;hljs-comment&quot;&gt;# 函数式写法&lt;/span&gt;
filtered_users = &lt;span class=&quot;hljs-built_in&quot;&gt;list&lt;/span&gt;(
    &lt;span class=&quot;hljs-built_in&quot;&gt;map&lt;/span&gt;(
        &lt;span class=&quot;hljs-keyword&quot;&gt;lambda&lt;/span&gt; u: {**u, &lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;: u[&lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;].upper()},
        &lt;span class=&quot;hljs-built_in&quot;&gt;filter&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;lambda&lt;/span&gt; u: u[&lt;span class=&quot;hljs-string&quot;&gt;&quot;is_active&quot;&lt;/span&gt;], users)
    )
)

&lt;span class=&quot;hljs-comment&quot;&gt;# 但更 Pythonic 的方式是列表推导式&lt;/span&gt;
filtered_users = [
    {**u, &lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;: u[&lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;].upper()} 
    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; u &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; users 
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; u[&lt;span class=&quot;hljs-string&quot;&gt;&quot;is_active&quot;&lt;/span&gt;]
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种&quot;列表推导式&quot;的语法，是 Python 对函数式编程的本土化改造。它既保留了函数式的表达能力，又符合 Python 简洁优雅的设计哲学。&lt;/p&gt;
&lt;p&gt;Python 还支持面向对象和命令式编程：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Python 面向对象风格&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;__init__&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, &lt;span class=&quot;hljs-built_in&quot;&gt;id&lt;/span&gt;, name, is_active&lt;/span&gt;):
        self.&lt;span class=&quot;hljs-built_in&quot;&gt;id&lt;/span&gt; = &lt;span class=&quot;hljs-built_in&quot;&gt;id&lt;/span&gt;
        self.name = name
        self.is_active = is_active
    
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;activate&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;/span&gt;):
        self.is_active = &lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;
    
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;__repr__&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;/span&gt;):
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;f&quot;User(&lt;span class=&quot;hljs-subst&quot;&gt;{self.name}&lt;/span&gt;)&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# 使用类&lt;/span&gt;
users = [User(&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;), User(&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;Bob&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;)]
&lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; user &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; users:
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;not&lt;/span&gt; user.is_active:
        user.activate()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这让我反思：前端开发中是否过度追求函数式的&quot;纯粹&quot;，而忽略了实用主义的平衡？React 的类组件被函数组件取代，但类组件在某些场景下（如复杂的生命周期管理）仍然有其优势。Python 的多范式支持提醒我们：没有最好的范式，只有最适合场景的范式。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-5&quot;&gt;类型系统——从动态到静态的考虑&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-6&quot;&gt;2.1 TypeScript 的革命&lt;/h3&gt;
&lt;p&gt;2012 年，TypeScript 的诞生改变了前端开发的格局。作为 JavaScript 的超集，TypeScript 为动态语言带来了静态类型的严谨。今天，几乎所有大型前端项目都采用 TypeScript，类型安全已成为行业标准。&lt;/p&gt;
&lt;p&gt;TypeScript 的成功不是偶然的。它解决了 JavaScript 开发中的几个核心痛点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;运行时错误前置&lt;/strong&gt;：在编译阶段发现类型错误，而不是在生产环境崩溃&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IDE 支持&lt;/strong&gt;：智能提示、自动补全、重构支持&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文档即代码&lt;/strong&gt;：类型定义就是最好的 API 文档&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;团队协作&lt;/strong&gt;：类型约束作为团队间的契约&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-typescript&quot; lang=&quot;typescript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// TypeScript 示例&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt; {
    &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;: &lt;span class=&quot;hljs-built_in&quot;&gt;number&lt;/span&gt;;
    &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;: &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;;
    email?: &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;;  &lt;span class=&quot;hljs-comment&quot;&gt;// 可选属性&lt;/span&gt;
}

&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;greet&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user: User&lt;/span&gt;): &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;`Hello, &lt;span class=&quot;hljs-subst&quot;&gt;${user.name}&lt;/span&gt;`&lt;/span&gt;;
}

&lt;span class=&quot;hljs-comment&quot;&gt;// 编译错误：类型不匹配&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; result = &lt;span class=&quot;hljs-title function_&quot;&gt;greet&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;1&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;Alice&quot;&lt;/span&gt; });  &lt;span class=&quot;hljs-comment&quot;&gt;// Error: id 应该是 number&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-id=&quot;heading-7&quot;&gt;2.2 Python 类型注解的演进&lt;/h3&gt;
&lt;p&gt;有趣的是，Python 的类型注解（Type Hints）几乎是与 TypeScript 同期发展的。PEP 484 在 2014 年引入类型注解，PEP 526 在 2016 年完善变量注解。两条平行线，却走向了相似的终点。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; typing &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;Optional&lt;/span&gt;, &lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;, &lt;span class=&quot;hljs-type&quot;&gt;Dict&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;__init__&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, &lt;span class=&quot;hljs-built_in&quot;&gt;id&lt;/span&gt;: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;, name: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;, email: &lt;span class=&quot;hljs-type&quot;&gt;Optional&lt;/span&gt;[&lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;] = &lt;span class=&quot;hljs-literal&quot;&gt;None&lt;/span&gt;&lt;/span&gt;):
        self.&lt;span class=&quot;hljs-built_in&quot;&gt;id&lt;/span&gt; = &lt;span class=&quot;hljs-built_in&quot;&gt;id&lt;/span&gt;
        self.name = name
        self.email = email

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;greet&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user: User&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;f&quot;Hello, &lt;span class=&quot;hljs-subst&quot;&gt;{user.name}&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# 类型检查工具（如 mypy）会在静态分析时报告错误&lt;/span&gt;
user = User(&lt;span class=&quot;hljs-built_in&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;1&quot;&lt;/span&gt;, name=&lt;span class=&quot;hljs-string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;)  &lt;span class=&quot;hljs-comment&quot;&gt;# mypy: Argument &quot;id&quot; has incompatible type &quot;str&quot;; expected &quot;int&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而，TypeScript 和 Python 类型系统的底层哲学存在本质差异：&lt;/p&gt;
&lt;h4 data-id=&quot;heading-8&quot;&gt;2.2.1 编译时 vs 运行时&lt;/h4&gt;
&lt;p&gt;TypeScript 的类型在编译时完全擦除，编译后的 JavaScript 不包含任何类型信息：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-typescript&quot; lang=&quot;typescript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// TypeScript 源码&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a: &lt;span class=&quot;hljs-built_in&quot;&gt;number&lt;/span&gt;, b: &lt;span class=&quot;hljs-built_in&quot;&gt;number&lt;/span&gt;&lt;/span&gt;): &lt;span class=&quot;hljs-built_in&quot;&gt;number&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; a + b;
}

&lt;span class=&quot;hljs-comment&quot;&gt;// 编译后的 JavaScript&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; a + b;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python 的类型注解在运行时保留，但解释器不做强制检查：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Python 源码&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;, b: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; a + b

&lt;span class=&quot;hljs-comment&quot;&gt;# 运行时可以通过 __annotations__ 访问类型信息&lt;/span&gt;
&lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(add.__annotations__)  &lt;span class=&quot;hljs-comment&quot;&gt;# {&#39;a&#39;: &amp;lt;class &#39;int&#39;&amp;gt;, &#39;b&#39;: &amp;lt;class &#39;int&#39;&amp;gt;, &#39;return&#39;: &amp;lt;class &#39;int&#39;&amp;gt;}&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# 但解释器不会检查类型&lt;/span&gt;
result = add(&lt;span class=&quot;hljs-string&quot;&gt;&quot;hello&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;world&quot;&lt;/span&gt;)  &lt;span class=&quot;hljs-comment&quot;&gt;# 正常运行，返回 &quot;helloworld&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-id=&quot;heading-9&quot;&gt;2.2.2 结构类型 vs 名义类型&lt;/h4&gt;
&lt;p&gt;TypeScript 采用结构类型系统（Structural Typing），也称为&quot;鸭子类型&quot;（Duck Typing）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-typescript&quot; lang=&quot;typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Point&lt;/span&gt; {
    &lt;span class=&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class=&quot;hljs-built_in&quot;&gt;number&lt;/span&gt;;
    &lt;span class=&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class=&quot;hljs-built_in&quot;&gt;number&lt;/span&gt;;
}

&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;printPoint&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;p: Point&lt;/span&gt;) {
    &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;`&lt;span class=&quot;hljs-subst&quot;&gt;${p.x}&lt;/span&gt;, &lt;span class=&quot;hljs-subst&quot;&gt;${p.y}&lt;/span&gt;`&lt;/span&gt;);
}

&lt;span class=&quot;hljs-comment&quot;&gt;// 只要结构匹配，就可以传递&lt;/span&gt;
&lt;span class=&quot;hljs-title function_&quot;&gt;printPoint&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt; });  &lt;span class=&quot;hljs-comment&quot;&gt;// OK&lt;/span&gt;
&lt;span class=&quot;hljs-title function_&quot;&gt;printPoint&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;z&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt; });  &lt;span class=&quot;hljs-comment&quot;&gt;// OK（多余属性允许）&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python 的类型检查器（如 mypy）同样支持结构类型，通过 &lt;code&gt;Protocol&lt;/code&gt;（PEP 544）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; typing &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; Protocol

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Point&lt;/span&gt;(&lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;Protocol&lt;/span&gt;):
    x: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;
    y: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;print_point&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;p: Point&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-literal&quot;&gt;None&lt;/span&gt;:
    &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;&lt;span class=&quot;hljs-subst&quot;&gt;{p.x}&lt;/span&gt;, &lt;span class=&quot;hljs-subst&quot;&gt;{p.y}&lt;/span&gt;&quot;&lt;/span&gt;)

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;MyPoint&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;__init__&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, x: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;, y: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;&lt;/span&gt;):
        self.x = x
        self.y = y

&lt;span class=&quot;hljs-comment&quot;&gt;# MyPoint 没有显式继承 Point，但结构匹配即可&lt;/span&gt;
print_point(MyPoint(&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;))  &lt;span class=&quot;hljs-comment&quot;&gt;# OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-id=&quot;heading-10&quot;&gt;2.2.3 类型推断&lt;/h4&gt;
&lt;p&gt;TypeScript 的类型推断更为激进：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-typescript&quot; lang=&quot;typescript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// TypeScript 能推断出 arr 是 number[]&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; arr = [&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;];

&lt;span class=&quot;hljs-comment&quot;&gt;// 能推断出 result 是 number&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; result = arr.&lt;span class=&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;x&lt;/span&gt; =&amp;gt;&lt;/span&gt; x * &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;filter&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;x&lt;/span&gt; =&amp;gt;&lt;/span&gt; x &amp;gt; &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python 的类型推断相对保守，需要显式注解：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; typing &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# Python 需要显式类型注解&lt;/span&gt;
arr: &lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;[&lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;] = [&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;]

&lt;span class=&quot;hljs-comment&quot;&gt;# 或者让 mypy 推断（有限支持）&lt;/span&gt;
result = [x * &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; x &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; arr &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; x &amp;gt; &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;]  &lt;span class=&quot;hljs-comment&quot;&gt;# mypy 能推断为 List[int]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-id=&quot;heading-11&quot;&gt;2.3 渐进式类型的价值&lt;/h3&gt;
&lt;p&gt;这种对比从侧面来说：类型系统的价值不在于&quot;正确性&quot;本身，而在于它如何帮助团队协作和代码演进。Python 的渐进式类型（Gradual Typing）策略——允许在需要时添加类型，在灵活时保持动态——或许比 TypeScript 的&quot;全有或全无&quot;更加务实。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Python 渐进式类型示例&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; typing &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; TYPE_CHECKING

&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; TYPE_CHECKING:
    &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; models &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; User  &lt;span class=&quot;hljs-comment&quot;&gt;# 仅在类型检查时导入&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;process_user&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user&lt;/span&gt;):  &lt;span class=&quot;hljs-comment&quot;&gt;# 动态类型，灵活&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; user.name.upper()

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;process_user_typed&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user: &lt;span class=&quot;hljs-string&quot;&gt;&quot;User&quot;&lt;/span&gt;&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;:  &lt;span class=&quot;hljs-comment&quot;&gt;# 静态类型，安全&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; user.name.upper()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在实际的开发中，我通常采用以下策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;公共 API 和核心模块使用完整的类型注解&lt;/li&gt;
&lt;li&gt;脚本和原型代码保持动态类型，快速迭代&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;mypy&lt;/code&gt; 在 CI 中检查关键模块的类型安全&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-12&quot;&gt;生态系统&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-13&quot;&gt;3.1 npm 与 PyPI：包管理的两种哲学&lt;/h3&gt;
&lt;p&gt;前端对于 npm 生态的复杂情感，可以用一句话概括：&quot;node_modules 是世界上最重的东西&quot;。JavaScript 的微包文化（left-pad 事件）和依赖，是每个前端的心头痛。&lt;/p&gt;
&lt;p&gt;让我们看看一个典型的 React 项目的依赖树：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;$ npm list | &lt;span class=&quot;hljs-built_in&quot;&gt;wc&lt;/span&gt; -l
&lt;span class=&quot;hljs-comment&quot;&gt;# 输出可能超过 1000 行&lt;/span&gt;

$ &lt;span class=&quot;hljs-built_in&quot;&gt;du&lt;/span&gt; -sh node_modules
&lt;span class=&quot;hljs-comment&quot;&gt;# 输出可能超过 500MB&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;：JavaScript 生态倾向于将功能拆分为极小的包，一个左填充函数（left-pad）也能成为一个包&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重复依赖&lt;/strong&gt;：不同版本的同一个库可能同时存在&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开发依赖混杂&lt;/strong&gt;：构建工具、测试框架、类型定义都混在一起&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;2016 年的微包事件是一个标志性案例。一个只有 11 行代码的包被作者从 npm 下架，导致全球数千个项目无法构建。这暴露了微包文化的脆弱性。&lt;/p&gt;
&lt;p&gt;Python 的包管理生态则呈现出不同的面貌。pip、conda、poetry、pipenv …… 工具超级多，但核心理念一致：&lt;strong&gt;显式优于隐式&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ini&quot; lang=&quot;ini&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Python 的 requirements.txt&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;requests&lt;/span&gt;==&lt;span class=&quot;hljs-number&quot;&gt;2.28&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;
numpy&amp;gt;=1.21.0
pandas~=1.5.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个文件明确告诉我们：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;requests&lt;/code&gt; 必须严格等于 2.28.1 版本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numpy&lt;/code&gt; 可以是 1.21.0 或更高版本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pandas&lt;/code&gt; 可以是 1.5.x 系列（补丁版本可以变）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种显式依赖管理的文化，让 Python 项目的可重现性远超 JavaScript。当你克隆一个 Python 项目，你知道需要安装什么；而当你克隆一个 Node.js 项目，node_modules 的深渊往往让人望而却步。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-14&quot;&gt;3.2 虚拟环境：Python 的隔离艺术&lt;/h3&gt;
&lt;p&gt;Python 的虚拟环境（virtualenv/venv）是包管理的另一大特色。每个项目可以有独立的 Python 环境和依赖，互不干扰。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# 创建虚拟环境&lt;/span&gt;
python -m venv myproject-env

&lt;span class=&quot;hljs-comment&quot;&gt;# 激活虚拟环境&lt;/span&gt;
&lt;span class=&quot;hljs-built_in&quot;&gt;source&lt;/span&gt; myproject-env/bin/activate  &lt;span class=&quot;hljs-comment&quot;&gt;# Linux/Mac&lt;/span&gt;
myproject-env\Scripts\activate  &lt;span class=&quot;hljs-comment&quot;&gt;# Windows&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# 安装依赖&lt;/span&gt;
pip install -r requirements.txt

&lt;span class=&quot;hljs-comment&quot;&gt;# 退出虚拟环境&lt;/span&gt;
deactivate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这与 Node.js 的 &lt;code&gt;node_modules&lt;/code&gt; 本地安装有相似之处，但更加彻底——虚拟环境甚至隔离了 Python 解释器本身。&lt;/p&gt;
&lt;p&gt;现在 Python 项目更倾向于使用 &lt;code&gt;pyproject.toml&lt;/code&gt;（PEP 518）来管理依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-toml&quot; lang=&quot;toml&quot;&gt;&lt;span class=&quot;hljs-section&quot;&gt;[build-system]&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;requires&lt;/span&gt; = [&lt;span class=&quot;hljs-string&quot;&gt;&quot;poetry-core&quot;&lt;/span&gt;]
&lt;span class=&quot;hljs-attr&quot;&gt;build-backend&lt;/span&gt; = &lt;span class=&quot;hljs-string&quot;&gt;&quot;poetry.core.masonry.api&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-section&quot;&gt;[tool.poetry]&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt; = &lt;span class=&quot;hljs-string&quot;&gt;&quot;my-project&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;version&lt;/span&gt; = &lt;span class=&quot;hljs-string&quot;&gt;&quot;0.1.0&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;description&lt;/span&gt; = &lt;span class=&quot;hljs-string&quot;&gt;&quot;A sample project&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-section&quot;&gt;[tool.poetry.dependencies]&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;python&lt;/span&gt; = &lt;span class=&quot;hljs-string&quot;&gt;&quot;^3.10&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;requests&lt;/span&gt; = &lt;span class=&quot;hljs-string&quot;&gt;&quot;^2.28&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;pydantic&lt;/span&gt; = &lt;span class=&quot;hljs-string&quot;&gt;&quot;^1.10&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-section&quot;&gt;[tool.poetry.dev-dependencies]&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;pytest&lt;/span&gt; = &lt;span class=&quot;hljs-string&quot;&gt;&quot;^7.0&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;black&lt;/span&gt; = &lt;span class=&quot;hljs-string&quot;&gt;&quot;^22.0&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;mypy&lt;/span&gt; = &lt;span class=&quot;hljs-string&quot;&gt;&quot;^0.991&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Poetry 不仅管理依赖，还管理虚拟环境、打包、发布，是一个完整的项目管理工具。这与 JavaScript 生态中 npm/yarn/pnpm 的竞争格局形成鲜明对比。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-15&quot;&gt;3.3 标准库的力量&lt;/h3&gt;
&lt;p&gt;Python 的 &quot;batteries included&quot; 哲学，在标准库中体现得淋漓尽致。从文件处理到网络编程，从正则表达式到 JSON 解析，从单元测试到并发编程——Python 标准库几乎覆盖了一个开发者 80% 的日常需求。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Python 标准库示例&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; json
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; re
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; urllib.request
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; datetime &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; datetime, timedelta
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; pathlib &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; Path
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; unittest &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; TestCase, main

&lt;span class=&quot;hljs-comment&quot;&gt;# JSON 处理&lt;/span&gt;
data = {&lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;age&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;30&lt;/span&gt;}
json_str = json.dumps(data, indent=&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# 正则表达式&lt;/span&gt;
pattern = &lt;span class=&quot;hljs-string&quot;&gt;r&quot;\b\w+@\w+\.\w+\b&quot;&lt;/span&gt;
emails = re.findall(pattern, &lt;span class=&quot;hljs-string&quot;&gt;&quot;Contact: alice@example.com, bob@test.org&quot;&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# 文件路径操作&lt;/span&gt;
config_path = Path.home() / &lt;span class=&quot;hljs-string&quot;&gt;&quot;.config&quot;&lt;/span&gt; / &lt;span class=&quot;hljs-string&quot;&gt;&quot;myapp&quot;&lt;/span&gt; / &lt;span class=&quot;hljs-string&quot;&gt;&quot;settings.json&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# 日期时间&lt;/span&gt;
now = datetime.now()
future = now + timedelta(days=&lt;span class=&quot;hljs-number&quot;&gt;7&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# HTTP 请求&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;with&lt;/span&gt; urllib.request.urlopen(&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://api.example.com/data&quot;&lt;/span&gt;) &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; response:
    data = json.loads(response.read())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相比之下，JavaScript 的标准库堪称贫瘠。直到 ES6 引入 Promise、fetch、模块化，JavaScript 才勉强跟上时代。但即便如此，lodash、axios、moment 依然是大多数项目的标配。&lt;/p&gt;
&lt;p&gt;这种差异的根源在于语言的设计目标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JavaScript 诞生于浏览器，被设计为轻量级脚本语言，依赖浏览器提供的 DOM API&lt;/li&gt;
&lt;li&gt;Python 诞生于通用编程，被设计为&quot;可执行的伪代码&quot;，需要在各种环境中独立运行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;标准库的丰富程度，反映的是语言设计者对&quot;开箱即用&quot;的不同理解。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-16&quot;&gt;3.4 生态系统的成熟度对比&lt;/h3&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;JavaScript/npm&lt;/th&gt;&lt;th&gt;Python/PyPI&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;包数量&lt;/td&gt;&lt;td&gt;200万+&lt;/td&gt;&lt;td&gt;40万+&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;嵌套依赖（node_modules）&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;丰富（&quot;batteries included&quot;）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;类型定义&lt;/td&gt;&lt;td&gt;@types/* 包&lt;/td&gt;&lt;td&gt;内置类型注解&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;安全审计&lt;/td&gt;&lt;td&gt;npm audit&lt;/td&gt;&lt;td&gt;safety, pip-audit&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;私有仓库&lt;/td&gt;&lt;td&gt;Verdaccio, Nexus&lt;/td&gt;&lt;td&gt;PyPI Enterprise, Devpi&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-17&quot;&gt;数据科学的疆域的追赶&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-18&quot;&gt;4.1 Python 的数据霸权&lt;/h3&gt;
&lt;p&gt;如果说前端是 JavaScript 的天下，那么数据科学就是 Python 的帝国。NumPy、Pandas、Matplotlib、Scikit-learn、TensorFlow、PyTorch——这些库构成了数据科学的完整工具链，而 Python 是它们的通用语言。&lt;/p&gt;
&lt;p&gt;这种霸权不是偶然的。Python 的简洁语法、丰富的科学计算库、与 C/C++/Fortran 的良好互操作性，使其成为数据科学家的首选语言。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-19&quot;&gt;4.1.1 NumPy：向量化计算的威力&lt;/h4&gt;
&lt;p&gt;NumPy 是 Python 科学计算的基础。它提供了高效的多维数组对象和数学函数库，底层使用 C 实现，性能远超纯 Python。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; numpy &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; np

&lt;span class=&quot;hljs-comment&quot;&gt;# 创建数组&lt;/span&gt;
arr = np.array([&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;5&lt;/span&gt;])

&lt;span class=&quot;hljs-comment&quot;&gt;# 向量化运算——比 Python 循环快 100 倍&lt;/span&gt;
result = arr * &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt; + &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;# [3, 5, 7, 9, 11]&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# 多维数组&lt;/span&gt;
matrix = np.array([[&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;], [&lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;5&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;6&lt;/span&gt;], [&lt;span class=&quot;hljs-number&quot;&gt;7&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;8&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;9&lt;/span&gt;]])

&lt;span class=&quot;hljs-comment&quot;&gt;# 矩阵运算&lt;/span&gt;
transposed = matrix.T
dot_product = matrix @ matrix.T

&lt;span class=&quot;hljs-comment&quot;&gt;# 统计函数&lt;/span&gt;
mean = np.mean(arr)
std = np.std(arr)
max_val = np.&lt;span class=&quot;hljs-built_in&quot;&gt;max&lt;/span&gt;(matrix, axis=&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;)  &lt;span class=&quot;hljs-comment&quot;&gt;# 每列的最大值&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;让我们做一个性能对比：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; time

&lt;span class=&quot;hljs-comment&quot;&gt;# Python 原生列表&lt;/span&gt;
python_list = &lt;span class=&quot;hljs-built_in&quot;&gt;list&lt;/span&gt;(&lt;span class=&quot;hljs-built_in&quot;&gt;range&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;1_000_000&lt;/span&gt;))

start = time.time()
result = [x * &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; x &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; python_list]
&lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;Python 列表推导式: &lt;span class=&quot;hljs-subst&quot;&gt;{time.time() - start:&lt;span class=&quot;hljs-number&quot;&gt;.4&lt;/span&gt;f}&lt;/span&gt;秒&quot;&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# NumPy 数组&lt;/span&gt;
numpy_array = np.array(python_list)

start = time.time()
result = numpy_array * &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;NumPy 向量化运算: &lt;span class=&quot;hljs-subst&quot;&gt;{time.time() - start:&lt;span class=&quot;hljs-number&quot;&gt;.4&lt;/span&gt;f}&lt;/span&gt;秒&quot;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在我的电脑上，Python 列表推导式耗时约 0.08 秒，而 NumPy 仅需 0.001 秒——80 倍的性能差距！这是因为 NumPy 的运算是在 C 层面执行的，避免了 Python 的解释器开销。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-20&quot;&gt;4.1.2 Pandas：数据处理的艺术&lt;/h4&gt;
&lt;p&gt;如果说 NumPy 是数组计算的利器，Pandas 就是数据处理的瑞士军刀。它提供了 DataFrame 和 Series 两种数据结构，让数据清洗、转换、分析变得异常简单。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; pandas &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; pd

&lt;span class=&quot;hljs-comment&quot;&gt;# 读取数据&lt;/span&gt;
df = pd.read_csv(&lt;span class=&quot;hljs-string&quot;&gt;&#39;sales_data.csv&#39;&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# 数据清洗&lt;/span&gt;
df = df.dropna()  &lt;span class=&quot;hljs-comment&quot;&gt;# 删除缺失值&lt;/span&gt;
df = df[df[&lt;span class=&quot;hljs-string&quot;&gt;&#39;price&#39;&lt;/span&gt;] &amp;gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;]  &lt;span class=&quot;hljs-comment&quot;&gt;# 过滤异常值&lt;/span&gt;
df[&lt;span class=&quot;hljs-string&quot;&gt;&#39;date&#39;&lt;/span&gt;] = pd.to_datetime(df[&lt;span class=&quot;hljs-string&quot;&gt;&#39;date&#39;&lt;/span&gt;])  &lt;span class=&quot;hljs-comment&quot;&gt;# 类型转换&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# 数据转换&lt;/span&gt;
df[&lt;span class=&quot;hljs-string&quot;&gt;&#39;revenue&#39;&lt;/span&gt;] = df[&lt;span class=&quot;hljs-string&quot;&gt;&#39;price&#39;&lt;/span&gt;] * df[&lt;span class=&quot;hljs-string&quot;&gt;&#39;quantity&#39;&lt;/span&gt;]
df[&lt;span class=&quot;hljs-string&quot;&gt;&#39;month&#39;&lt;/span&gt;] = df[&lt;span class=&quot;hljs-string&quot;&gt;&#39;date&#39;&lt;/span&gt;].dt.month

&lt;span class=&quot;hljs-comment&quot;&gt;# 分组聚合&lt;/span&gt;
monthly_sales = df.groupby(&lt;span class=&quot;hljs-string&quot;&gt;&#39;month&#39;&lt;/span&gt;).agg({
    &lt;span class=&quot;hljs-string&quot;&gt;&#39;revenue&#39;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;sum&#39;&lt;/span&gt;,
    &lt;span class=&quot;hljs-string&quot;&gt;&#39;quantity&#39;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;mean&#39;&lt;/span&gt;
}).reset_index()

&lt;span class=&quot;hljs-comment&quot;&gt;# 透视表&lt;/span&gt;
pivot = df.pivot_table(
    values=&lt;span class=&quot;hljs-string&quot;&gt;&#39;revenue&#39;&lt;/span&gt;,
    index=&lt;span class=&quot;hljs-string&quot;&gt;&#39;category&#39;&lt;/span&gt;,
    columns=&lt;span class=&quot;hljs-string&quot;&gt;&#39;month&#39;&lt;/span&gt;,
    aggfunc=&lt;span class=&quot;hljs-string&quot;&gt;&#39;sum&#39;&lt;/span&gt;
)

&lt;span class=&quot;hljs-comment&quot;&gt;# 合并数据&lt;/span&gt;
merged = pd.merge(df, customer_df, on=&lt;span class=&quot;hljs-string&quot;&gt;&#39;customer_id&#39;&lt;/span&gt;, how=&lt;span class=&quot;hljs-string&quot;&gt;&#39;left&#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码如果用 JavaScript 实现，需要多少行？lodash 可以处理数组，但没有原生的 DataFrame 概念。D3.js 可以做数据转换，但学习曲线陡峭。Pandas 的链式操作让复杂的数据处理变得可读、可维护。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-21&quot;&gt;4.1.3 数据可视化&lt;/h4&gt;
&lt;p&gt;Matplotlib 和 Seaborn 是 Python 数据可视化的主力军：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; plt
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; seaborn &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; sns

&lt;span class=&quot;hljs-comment&quot;&gt;# 设置样式&lt;/span&gt;
sns.set_style(&lt;span class=&quot;hljs-string&quot;&gt;&quot;whitegrid&quot;&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# 创建图表&lt;/span&gt;
fig, axes = plt.subplots(&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, figsize=(&lt;span class=&quot;hljs-number&quot;&gt;12&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;10&lt;/span&gt;))

&lt;span class=&quot;hljs-comment&quot;&gt;# 折线图&lt;/span&gt;
df.groupby(&lt;span class=&quot;hljs-string&quot;&gt;&#39;date&#39;&lt;/span&gt;)[&lt;span class=&quot;hljs-string&quot;&gt;&#39;revenue&#39;&lt;/span&gt;].&lt;span class=&quot;hljs-built_in&quot;&gt;sum&lt;/span&gt;().plot(ax=axes[&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;], title=&lt;span class=&quot;hljs-string&quot;&gt;&#39;Daily Revenue&#39;&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# 柱状图&lt;/span&gt;
df[&lt;span class=&quot;hljs-string&quot;&gt;&#39;category&#39;&lt;/span&gt;].value_counts().plot(kind=&lt;span class=&quot;hljs-string&quot;&gt;&#39;bar&#39;&lt;/span&gt;, ax=axes[&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;], title=&lt;span class=&quot;hljs-string&quot;&gt;&#39;Category Distribution&#39;&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# 散点图&lt;/span&gt;
axes[&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;].scatter(df[&lt;span class=&quot;hljs-string&quot;&gt;&#39;price&#39;&lt;/span&gt;], df[&lt;span class=&quot;hljs-string&quot;&gt;&#39;quantity&#39;&lt;/span&gt;], alpha=&lt;span class=&quot;hljs-number&quot;&gt;0.5&lt;/span&gt;)
axes[&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;].set_title(&lt;span class=&quot;hljs-string&quot;&gt;&#39;Price vs Quantity&#39;&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# 热力图&lt;/span&gt;
corr = df[[&lt;span class=&quot;hljs-string&quot;&gt;&#39;price&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;quantity&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;revenue&#39;&lt;/span&gt;]].corr()
sns.heatmap(corr, annot=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;, ax=axes[&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;], title=&lt;span class=&quot;hljs-string&quot;&gt;&#39;Correlation Matrix&#39;&lt;/span&gt;)

plt.tight_layout()
plt.savefig(&lt;span class=&quot;hljs-string&quot;&gt;&#39;analysis.png&#39;&lt;/span&gt;, dpi=&lt;span class=&quot;hljs-number&quot;&gt;300&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前端可能会说：&quot;这些用 D3.js 也能做，而且交互性更强。&quot;没错，D3.js 的交互能力是 Matplotlib 无法比拟的。但 Matplotlib 的优势在于快速探索和静态报告——数据分析不需要为每个图表写 200 行 D3 代码。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-22&quot;&gt;4.2 前端的数据觉醒&lt;/h3&gt;
&lt;p&gt;幸运的是，前端世界正在觉醒。TensorFlow.js、ONNX.js、Apache Arrow JS——这些项目正在把数据科学的能力带入浏览器。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-23&quot;&gt;4.2.1 TensorFlow.js&lt;/h4&gt;
&lt;p&gt;TensorFlow.js 让机器学习模型可以在浏览器中运行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; * &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; tf &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;@tensorflow/tfjs&#39;&lt;/span&gt;;

&lt;span class=&quot;hljs-comment&quot;&gt;// 创建一个简单的神经网络&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; model = tf.&lt;span class=&quot;hljs-title function_&quot;&gt;sequential&lt;/span&gt;({
    &lt;span class=&quot;hljs-attr&quot;&gt;layers&lt;/span&gt;: [
        tf.&lt;span class=&quot;hljs-property&quot;&gt;layers&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;dense&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;inputShape&lt;/span&gt;: [&lt;span class=&quot;hljs-number&quot;&gt;784&lt;/span&gt;], &lt;span class=&quot;hljs-attr&quot;&gt;units&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;32&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;activation&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;relu&#39;&lt;/span&gt; }),
        tf.&lt;span class=&quot;hljs-property&quot;&gt;layers&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;dense&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;units&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;activation&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;softmax&#39;&lt;/span&gt; })
    ]
});

model.&lt;span class=&quot;hljs-title function_&quot;&gt;compile&lt;/span&gt;({
    &lt;span class=&quot;hljs-attr&quot;&gt;optimizer&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;adam&#39;&lt;/span&gt;,
    &lt;span class=&quot;hljs-attr&quot;&gt;loss&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;categoricalCrossentropy&#39;&lt;/span&gt;,
    &lt;span class=&quot;hljs-attr&quot;&gt;metrics&lt;/span&gt;: [&lt;span class=&quot;hljs-string&quot;&gt;&#39;accuracy&#39;&lt;/span&gt;]
});

&lt;span class=&quot;hljs-comment&quot;&gt;// 训练模型（在浏览器中）&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; model.&lt;span class=&quot;hljs-title function_&quot;&gt;fit&lt;/span&gt;(xs, ys, {
    &lt;span class=&quot;hljs-attr&quot;&gt;epochs&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;10&lt;/span&gt;,
    &lt;span class=&quot;hljs-attr&quot;&gt;batchSize&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;32&lt;/span&gt;,
    &lt;span class=&quot;hljs-attr&quot;&gt;callbacks&lt;/span&gt;: {
        &lt;span class=&quot;hljs-attr&quot;&gt;onEpochEnd&lt;/span&gt;: &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;epoch, logs&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
            &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;`Epoch &lt;span class=&quot;hljs-subst&quot;&gt;${epoch}&lt;/span&gt;: loss = &lt;span class=&quot;hljs-subst&quot;&gt;${logs.loss}&lt;/span&gt;`&lt;/span&gt;);
        }
    }
});

&lt;span class=&quot;hljs-comment&quot;&gt;// 预测&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; prediction = model.&lt;span class=&quot;hljs-title function_&quot;&gt;predict&lt;/span&gt;(newImage);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这意味着前端可以在用户设备上运行机器学习模型，无需服务器参与。隐私保护、低延迟、离线可用——这些是服务端推理无法比拟的优势。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-24&quot;&gt;4.2.2 Apache Arrow JS&lt;/h4&gt;
&lt;p&gt;Apache Arrow 是一种跨语言的列式内存格式，Arrow JS 让 JavaScript 可以高效地处理大规模数据：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { &lt;span class=&quot;hljs-title class_&quot;&gt;Table&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;FloatVector&lt;/span&gt; } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;apache-arrow&#39;&lt;/span&gt;;

&lt;span class=&quot;hljs-comment&quot;&gt;// 创建 Arrow 表&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; table = &lt;span class=&quot;hljs-title class_&quot;&gt;Table&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;new&lt;/span&gt;(
    [
        &lt;span class=&quot;hljs-title class_&quot;&gt;FloatVector&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;from&lt;/span&gt;([&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;5&lt;/span&gt;]),
        &lt;span class=&quot;hljs-title class_&quot;&gt;FloatVector&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;from&lt;/span&gt;([&lt;span class=&quot;hljs-number&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;20&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;30&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;40&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;50&lt;/span&gt;])
    ],
    [&lt;span class=&quot;hljs-string&quot;&gt;&#39;x&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;y&#39;&lt;/span&gt;]
);

&lt;span class=&quot;hljs-comment&quot;&gt;// 高效查询&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; sum = table.&lt;span class=&quot;hljs-title function_&quot;&gt;getColumn&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;y&#39;&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;toArray&lt;/span&gt;().&lt;span class=&quot;hljs-title function_&quot;&gt;reduce&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) =&amp;gt;&lt;/span&gt; a + b, &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Arrow 的列式存储格式让数据在 Python、JavaScript、R、Julia 之间零拷贝传输成为可能。这对于前后端数据交互是一个革命性的改进。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-25&quot;&gt;4.3 前端的数据科学学习路径&lt;/h3&gt;
&lt;p&gt;但更深层的思考是：前端是否应该掌握数据科学的能力？&lt;/p&gt;
&lt;p&gt;我的答案是肯定的。现代前端不再是简单的页面展示，而是数据驱动的交互应用。理解数据处理、理解机器学习的基本原理，将成为高级前端的必备技能。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-26&quot;&gt;Web 开发的殊途同归&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-27&quot;&gt;5.1 Django vs Express：两种架构哲学&lt;/h3&gt;
&lt;p&gt;Django 是 Python Web 开发的旗舰框架，它的哲学是&quot;约定优于配置&quot;。ORM、表单处理、认证系统、管理后台——Django 提供了一站式解决方案。这种&quot;全功能框架&quot;的思路，让开发者可以快速搭建复杂的 Web 应用。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Django 模型定义&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; django.db &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; models
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; django.contrib.auth.models &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; User

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Article&lt;/span&gt;(models.Model):
    title = models.CharField(max_length=&lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;)
    
    &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Meta&lt;/span&gt;:
        ordering = [&lt;span class=&quot;hljs-string&quot;&gt;&#39;-created_at&#39;&lt;/span&gt;]

&lt;span class=&quot;hljs-comment&quot;&gt;# Django 视图&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; django.shortcuts &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; render, get_object_or_404
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; rest_framework &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; viewsets
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; rest_framework.decorators &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; action

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;ArticleViewSet&lt;/span&gt;(viewsets.ModelViewSet):
    queryset = Article.objects.&lt;span class=&quot;hljs-built_in&quot;&gt;all&lt;/span&gt;()
    serializer_class = ArticleSerializer
    
&lt;span class=&quot;hljs-meta&quot;&gt;    @action(&lt;span class=&quot;hljs-params&quot;&gt;detail=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;, methods=[&lt;span class=&quot;hljs-string&quot;&gt;&#39;post&#39;&lt;/span&gt;]&lt;/span&gt;)&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;publish&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, request, pk=&lt;span class=&quot;hljs-literal&quot;&gt;None&lt;/span&gt;&lt;/span&gt;):
        article = self.get_object()
        article.publish()
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; Response({&lt;span class=&quot;hljs-string&quot;&gt;&#39;status&#39;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;published&#39;&lt;/span&gt;})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Django 的优势在于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;快速开发&lt;/strong&gt;：内置的 admin 界面让 CRUD 操作无需额外代码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全性&lt;/strong&gt;：内置 CSRF 保护、SQL 注入防护、XSS 过滤&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可扩展性&lt;/strong&gt;：丰富的第三方应用生态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但 Django 也有其局限性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;灵活性不足&lt;/strong&gt;：Django 的&quot;全功能&quot;意味着你必须按照它的方式做事&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;学习曲线陡峭&lt;/strong&gt;：需要理解 ORM、视图、模板、中间件等多个概念&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能开销&lt;/strong&gt;：大而全的框架必然带来性能损耗&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Express.js 则是 Node.js 世界的微框架代表：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; express = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;express&#39;&lt;/span&gt;);
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; mongoose = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;mongoose&#39;&lt;/span&gt;);

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; app = &lt;span class=&quot;hljs-title function_&quot;&gt;express&lt;/span&gt;();
app.&lt;span class=&quot;hljs-title function_&quot;&gt;use&lt;/span&gt;(express.&lt;span class=&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;());

&lt;span class=&quot;hljs-comment&quot;&gt;// 模型定义（使用 Mongoose）&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; articleSchema = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; mongoose.&lt;span class=&quot;hljs-title class_&quot;&gt;Schema&lt;/span&gt;({
    &lt;span class=&quot;hljs-attr&quot;&gt;title&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;,
    &lt;span class=&quot;hljs-attr&quot;&gt;content&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;,
    &lt;span class=&quot;hljs-attr&quot;&gt;author&lt;/span&gt;: { &lt;span class=&quot;hljs-attr&quot;&gt;type&lt;/span&gt;: mongoose.&lt;span class=&quot;hljs-property&quot;&gt;Schema&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;Types&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;ObjectId&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;ref&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;User&#39;&lt;/span&gt; },
    &lt;span class=&quot;hljs-attr&quot;&gt;createdAt&lt;/span&gt;: { &lt;span class=&quot;hljs-attr&quot;&gt;type&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;default&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;now&lt;/span&gt; }
});

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Article&lt;/span&gt; = mongoose.&lt;span class=&quot;hljs-title function_&quot;&gt;model&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;Article&#39;&lt;/span&gt;, articleSchema);

&lt;span class=&quot;hljs-comment&quot;&gt;// 路由&lt;/span&gt;
app.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;/api/articles&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; (req, res) =&amp;gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; articles = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Article&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;find&lt;/span&gt;().&lt;span class=&quot;hljs-title function_&quot;&gt;sort&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;createdAt&lt;/span&gt;: -&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt; });
    res.&lt;span class=&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;(articles);
});

app.&lt;span class=&quot;hljs-title function_&quot;&gt;post&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;/api/articles&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; (req, res) =&amp;gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; article = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Article&lt;/span&gt;(req.&lt;span class=&quot;hljs-property&quot;&gt;body&lt;/span&gt;);
    &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; article.&lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;();
    res.&lt;span class=&quot;hljs-title function_&quot;&gt;status&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;201&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;(article);
});

app.&lt;span class=&quot;hljs-title function_&quot;&gt;listen&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;3000&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Express 的优势在于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;灵活性&lt;/strong&gt;：只提供基础功能，其他由你选择&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;学习曲线平缓&lt;/strong&gt;：理解中间件概念后即可上手&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能&lt;/strong&gt;：轻量级框架，开销小&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但 Express 的灵活性也带来了问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;选择困难症&lt;/strong&gt;：ORM 用 Sequelize、TypeORM 还是 Prisma？验证用 Joi、Yup 还是 class-validator？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;项目结构不一致&lt;/strong&gt;：每个 Express 项目的结构都可能不同&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重复造轮子&lt;/strong&gt;：很多功能需要自己实现或选择第三方库&lt;/li&gt;
&lt;/ul&gt;


















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;Django&lt;/th&gt;&lt;th&gt;Express.js&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;架构风格&lt;/td&gt;&lt;td&gt;全功能框架（&quot; batteries included &quot;）&lt;/td&gt;&lt;td&gt;微框架&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ORM&lt;/td&gt;&lt;td&gt;内置 Django ORM&lt;/td&gt;&lt;td&gt;需额外选择（Sequelize/TypeORM/Prisma）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;认证授权&lt;/td&gt;&lt;td&gt;内置&lt;/td&gt;&lt;td&gt;需额外实现（Passport.js 等）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;管理后台&lt;/td&gt;&lt;td&gt;内置 admin&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;中小型项目、API 服务&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 data-id=&quot;heading-28&quot;&gt;5.2 FastAPI：Python 的现代答案&lt;/h3&gt;
&lt;p&gt;如果说 Django 是 Python 的 Spring，那么 FastAPI 就是 Python 的 NestJS。FastAPI 采用声明式编程、依赖注入、类型注解，它的设计哲学与 TypeScript 生态高度契合。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; fastapi &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; FastAPI, HTTPException, Depends
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; pydantic &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; BaseModel
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; typing &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;, &lt;span class=&quot;hljs-type&quot;&gt;Optional&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; sqlalchemy &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; create_engine, Column, Integer, String
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; sqlalchemy.ext.declarative &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; declarative_base
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; sqlalchemy.orm &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; sessionmaker, Session

&lt;span class=&quot;hljs-comment&quot;&gt;# 数据库设置&lt;/span&gt;
SQLALCHEMY_DATABASE_URL = &lt;span class=&quot;hljs-string&quot;&gt;&quot;sqlite:///./test.db&quot;&lt;/span&gt;
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=&lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;, autoflush=&lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;, bind=engine)
Base = declarative_base()

&lt;span class=&quot;hljs-comment&quot;&gt;# 数据库模型&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;UserDB&lt;/span&gt;(&lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;Base&lt;/span&gt;):
    __tablename__ = &lt;span class=&quot;hljs-string&quot;&gt;&quot;users&quot;&lt;/span&gt;
    &lt;span class=&quot;hljs-built_in&quot;&gt;id&lt;/span&gt; = Column(Integer, primary_key=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;, index=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;)
    name = Column(String, index=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;)
    email = Column(String, unique=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;, index=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# Pydantic 模型（用于 API 验证）&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;UserBase&lt;/span&gt;(&lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;BaseModel&lt;/span&gt;):
    name: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;
    email: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;UserCreate&lt;/span&gt;(&lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;UserBase&lt;/span&gt;):
    &lt;span class=&quot;hljs-keyword&quot;&gt;pass&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;(&lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;UserBase&lt;/span&gt;):
    &lt;span class=&quot;hljs-built_in&quot;&gt;id&lt;/span&gt;: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;
    
    &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Config&lt;/span&gt;:
        orm_mode = &lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# FastAPI 应用&lt;/span&gt;
app = FastAPI(title=&lt;span class=&quot;hljs-string&quot;&gt;&quot;User API&quot;&lt;/span&gt;, version=&lt;span class=&quot;hljs-string&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# 依赖注入：数据库会话&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;get_db&lt;/span&gt;():
    db = SessionLocal()
    &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt;:
        &lt;span class=&quot;hljs-keyword&quot;&gt;yield&lt;/span&gt; db
    &lt;span class=&quot;hljs-keyword&quot;&gt;finally&lt;/span&gt;:
        db.close()

&lt;span class=&quot;hljs-comment&quot;&gt;# 路由&lt;/span&gt;
&lt;span class=&quot;hljs-meta&quot;&gt;@app.post(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;/users/&quot;&lt;/span&gt;, response_model=User&lt;/span&gt;)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;create_user&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user: UserCreate, db: Session = Depends(&lt;span class=&quot;hljs-params&quot;&gt;get_db&lt;/span&gt;)&lt;/span&gt;):
    db_user = UserDB(**user.&lt;span class=&quot;hljs-built_in&quot;&gt;dict&lt;/span&gt;())
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; db_user

&lt;span class=&quot;hljs-meta&quot;&gt;@app.get(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;/users/&quot;&lt;/span&gt;, response_model=&lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;[User]&lt;/span&gt;)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;read_users&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;skip: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;, limit: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; = &lt;span class=&quot;hljs-number&quot;&gt;100&lt;/span&gt;, db: Session = Depends(&lt;span class=&quot;hljs-params&quot;&gt;get_db&lt;/span&gt;)&lt;/span&gt;):
    users = db.query(UserDB).offset(skip).limit(limit).&lt;span class=&quot;hljs-built_in&quot;&gt;all&lt;/span&gt;()
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; users

&lt;span class=&quot;hljs-meta&quot;&gt;@app.get(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;/users/{user_id}&quot;&lt;/span&gt;, response_model=User&lt;/span&gt;)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;read_user&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user_id: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;, db: Session = Depends(&lt;span class=&quot;hljs-params&quot;&gt;get_db&lt;/span&gt;)&lt;/span&gt;):
    user = db.query(UserDB).&lt;span class=&quot;hljs-built_in&quot;&gt;filter&lt;/span&gt;(UserDB.&lt;span class=&quot;hljs-built_in&quot;&gt;id&lt;/span&gt; == user_id).first()
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; user &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;None&lt;/span&gt;:
        &lt;span class=&quot;hljs-keyword&quot;&gt;raise&lt;/span&gt; HTTPException(status_code=&lt;span class=&quot;hljs-number&quot;&gt;404&lt;/span&gt;, detail=&lt;span class=&quot;hljs-string&quot;&gt;&quot;User not found&quot;&lt;/span&gt;)
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; user
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码的优雅让我惊叹：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;类型安全&lt;/strong&gt;：Pydantic 模型自动验证请求和响应&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动文档&lt;/strong&gt;：访问 &lt;code&gt;/docs&lt;/code&gt; 即可获得 Swagger UI 文档&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异步支持&lt;/strong&gt;：原生支持 &lt;code&gt;async/await&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;依赖注入&lt;/strong&gt;：&lt;code&gt;Depends&lt;/code&gt; 让代码解耦、可测试&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对比 TypeScript 的 NestJS：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-typescript&quot; lang=&quot;typescript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// NestJS 对比&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { &lt;span class=&quot;hljs-title class_&quot;&gt;Controller&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;Get&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;Post&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;Body&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;Param&lt;/span&gt; } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;@nestjs/common&#39;&lt;/span&gt;;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { &lt;span class=&quot;hljs-title class_&quot;&gt;InjectRepository&lt;/span&gt; } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;@nestjs/typeorm&#39;&lt;/span&gt;;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { &lt;span class=&quot;hljs-title class_&quot;&gt;Repository&lt;/span&gt; } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;typeorm&#39;&lt;/span&gt;;

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;CreateUserDto&lt;/span&gt; {
    &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;: &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;;
    &lt;span class=&quot;hljs-attr&quot;&gt;email&lt;/span&gt;: &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;;
}

&lt;span class=&quot;hljs-meta&quot;&gt;@Controller&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;users&#39;&lt;/span&gt;)
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;UserController&lt;/span&gt; {
    &lt;span class=&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;
        &lt;span class=&quot;hljs-meta&quot;&gt;@InjectRepository&lt;/span&gt;(User)
        &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; userRepository: Repository&amp;lt;User&amp;gt;
    &lt;/span&gt;) {}
    
    &lt;span class=&quot;hljs-meta&quot;&gt;@Post&lt;/span&gt;()
    &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;create&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;@Body&lt;/span&gt;() createUserDto: CreateUserDto&lt;/span&gt;) {
        &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; user = &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;userRepository&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;create&lt;/span&gt;(createUserDto);
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;userRepository&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;(user);
    }
    
    &lt;span class=&quot;hljs-meta&quot;&gt;@Get&lt;/span&gt;()
    &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;findAll&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;userRepository&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;find&lt;/span&gt;();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;FastAPI 和 NestJS 的设计如此相似——装饰器路由、依赖注入、DTO 验证、ORM 集成。这或许预示着 Web 开发的未来：语言的边界正在模糊，好的设计理念会被跨语言借鉴。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-29&quot;&gt;5.3 性能对比：Node.js vs Python&lt;/h3&gt;
&lt;p&gt;让我们做一个简单的性能测试，对比 Node.js 和 Python 处理 HTTP 请求的能力：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Node.js (Express)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; express = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;express&#39;&lt;/span&gt;);
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; app = &lt;span class=&quot;hljs-title function_&quot;&gt;express&lt;/span&gt;();

app.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;/api/data&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;req, res&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
    &lt;span class=&quot;hljs-comment&quot;&gt;// 模拟数据库查询&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; data = &lt;span class=&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;from&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;length&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1000&lt;/span&gt; }, &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;_, i&lt;/span&gt;) =&amp;gt;&lt;/span&gt; ({
        &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;: i,
        &lt;span class=&quot;hljs-attr&quot;&gt;value&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;random&lt;/span&gt;()
    }));
    res.&lt;span class=&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;(data);
});

app.&lt;span class=&quot;hljs-title function_&quot;&gt;listen&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;3000&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Python (FastAPI)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; fastapi &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; FastAPI
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; random

app = FastAPI()

&lt;span class=&quot;hljs-meta&quot;&gt;@app.get(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;/api/data&quot;&lt;/span&gt;&lt;/span&gt;)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;get_data&lt;/span&gt;():
    data = [
        {&lt;span class=&quot;hljs-string&quot;&gt;&quot;id&quot;&lt;/span&gt;: i, &lt;span class=&quot;hljs-string&quot;&gt;&quot;value&quot;&lt;/span&gt;: random.random()}
        &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;range&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;1000&lt;/span&gt;)
    ]
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; data

&lt;span class=&quot;hljs-comment&quot;&gt;# 使用 uvicorn 运行：uvicorn main:app --workers 4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 &lt;code&gt;wrk&lt;/code&gt; 进行压力测试：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Node.js&lt;/span&gt;
wrk -t12 -c400 -d30s http://localhost:3000/api/data
&lt;span class=&quot;hljs-comment&quot;&gt;# Requests/sec:  15000&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# Python (单 worker)&lt;/span&gt;
wrk -t12 -c400 -d30s http://localhost:8000/api/data
&lt;span class=&quot;hljs-comment&quot;&gt;# Requests/sec:   8000&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# Python (4 workers)&lt;/span&gt;
wrk -t12 -c400 -d30s http://localhost:8000/api/data
&lt;span class=&quot;hljs-comment&quot;&gt;# Requests/sec:  25000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果挺让人惊讶的：在单 worker 模式下，Node.js 的性能是 Python 的 2 倍。但当 Python 使用多 worker（利用多核 CPU）时，性能反超 Node.js。这说明：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;单线程性能&lt;/strong&gt;：Node.js 的 V8 引擎优于 Python 的解释器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多核利用&lt;/strong&gt;：Python 的多进程模型可以充分利用多核 CPU&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;场景选择&lt;/strong&gt;：I/O 密集型任务两者差距不大，CPU 密集型任务需要多进程&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-30&quot;&gt;融会贯通&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-31&quot;&gt;6.1 语言只是工具，思维才是核心&lt;/h3&gt;
&lt;p&gt;深入 Python 之后，我越来越确信一个观点：&lt;strong&gt;编程语言的差异，远不如编程思维的差异重要&lt;/strong&gt;。无论是 JavaScript 还是 Python，优秀的代码都遵循相同的原则：&lt;/p&gt;
&lt;h4 data-id=&quot;heading-32&quot;&gt;6.1.1 SOLID 原则&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;单一职责原则（Single Responsibility Principle）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# 不好的设计：一个类做太多事&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;UserManager&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;create_user&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, data&lt;/span&gt;): ...
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;send_email&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, user&lt;/span&gt;): ...
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;generate_report&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;/span&gt;): ...

&lt;span class=&quot;hljs-comment&quot;&gt;# 好的设计：职责分离&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;UserService&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;create_user&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, data&lt;/span&gt;): ...

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;EmailService&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;send_email&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, user&lt;/span&gt;): ...

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;ReportService&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;generate_report&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;/span&gt;): ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;开闭原则（Open/Closed Principle）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; abc &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; ABC, abstractmethod

&lt;span class=&quot;hljs-comment&quot;&gt;# 抽象基类&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;PaymentProcessor&lt;/span&gt;(&lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;ABC&lt;/span&gt;):
&lt;span class=&quot;hljs-meta&quot;&gt;    @abstractmethod&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;process&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, amount: &lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;bool&lt;/span&gt;:
        &lt;span class=&quot;hljs-keyword&quot;&gt;pass&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# 具体实现&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AlipayProcessor&lt;/span&gt;(&lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;PaymentProcessor&lt;/span&gt;):
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;process&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, amount: &lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;bool&lt;/span&gt;:
        &lt;span class=&quot;hljs-comment&quot;&gt;# 支付宝支付逻辑&lt;/span&gt;
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;WechatProcessor&lt;/span&gt;(&lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;PaymentProcessor&lt;/span&gt;):
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;process&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, amount: &lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;bool&lt;/span&gt;:
        &lt;span class=&quot;hljs-comment&quot;&gt;# 微信支付逻辑&lt;/span&gt;
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# 使用&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;PaymentService&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;__init__&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, processor: PaymentProcessor&lt;/span&gt;):
        self.processor = processor
    
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;pay&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, amount: &lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;bool&lt;/span&gt;:
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; self.processor.process(amount)

&lt;span class=&quot;hljs-comment&quot;&gt;# 新增支付方式无需修改现有代码&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;StripeProcessor&lt;/span&gt;(&lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;PaymentProcessor&lt;/span&gt;):
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;process&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, amount: &lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;bool&lt;/span&gt;:
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-id=&quot;heading-33&quot;&gt;6.1.2 设计模式&lt;/h4&gt;
&lt;p&gt;设计模式是跨语言的。无论是 JavaScript 还是 Python，观察者模式、工厂模式、策略模式等都有相似的实现：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Python 观察者模式&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; typing &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;, &lt;span class=&quot;hljs-type&quot;&gt;Callable&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;EventEmitter&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;__init__&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self&lt;/span&gt;):
        self._listeners: &lt;span class=&quot;hljs-built_in&quot;&gt;dict&lt;/span&gt;[&lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;, &lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;[&lt;span class=&quot;hljs-type&quot;&gt;Callable&lt;/span&gt;]] = {}
    
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;on&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, event: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;, callback: &lt;span class=&quot;hljs-type&quot;&gt;Callable&lt;/span&gt;&lt;/span&gt;):
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; event &lt;span class=&quot;hljs-keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; self._listeners:
            self._listeners[event] = []
        self._listeners[event].append(callback)
    
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;emit&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, event: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;, *args, **kwargs&lt;/span&gt;):
        &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; callback &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; self._listeners.get(event, []):
            callback(*args, **kwargs)

&lt;span class=&quot;hljs-comment&quot;&gt;# 使用&lt;/span&gt;
emitter = EventEmitter()
emitter.on(&lt;span class=&quot;hljs-string&quot;&gt;&#39;user_created&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;lambda&lt;/span&gt; user: &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;User created: &lt;span class=&quot;hljs-subst&quot;&gt;{user}&lt;/span&gt;&quot;&lt;/span&gt;))
emitter.emit(&lt;span class=&quot;hljs-string&quot;&gt;&#39;user_created&#39;&lt;/span&gt;, {&lt;span class=&quot;hljs-string&quot;&gt;&#39;name&#39;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;Alice&#39;&lt;/span&gt;})
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// JavaScript 观察者模式（几乎相同）&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;EventEmitter&lt;/span&gt; {
    &lt;span class=&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
        &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;listeners&lt;/span&gt; = {};
    }
    
    &lt;span class=&quot;hljs-title function_&quot;&gt;on&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;event, callback&lt;/span&gt;) {
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!&lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;listeners&lt;/span&gt;[event]) {
            &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;listeners&lt;/span&gt;[event] = [];
        }
        &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;listeners&lt;/span&gt;[event].&lt;span class=&quot;hljs-title function_&quot;&gt;push&lt;/span&gt;(callback);
    }
    
    &lt;span class=&quot;hljs-title function_&quot;&gt;emit&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;event, ...args&lt;/span&gt;) {
        (&lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;listeners&lt;/span&gt;[event] || []).&lt;span class=&quot;hljs-title function_&quot;&gt;forEach&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;cb&lt;/span&gt; =&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;cb&lt;/span&gt;(...args));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-id=&quot;heading-34&quot;&gt;6.1.3 编程思维的培养&lt;/h4&gt;
&lt;p&gt;掌握多门语言的价值，不在于&quot;技多不压身&quot;，而在于从不同视角理解这些原则，形成更全面的技术判断力。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JavaScript 教会我&lt;/strong&gt;：异步编程、函数式思维、事件驱动&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python 教会我&lt;/strong&gt;：简洁优雅、实用主义、科学计算&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript 教会我&lt;/strong&gt;：类型安全、接口设计、静态分析&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-id=&quot;heading-35&quot;&gt;6.2 未来的融合趋势&lt;/h3&gt;
&lt;p&gt;技术发展的趋势是融合而非对立。我们看到 Python 和 JavaScript 都在做的事情：&lt;/p&gt;
&lt;h4 data-id=&quot;heading-36&quot;&gt;6.2.1 WebAssembly 的崛起&lt;/h4&gt;
&lt;p&gt;WebAssembly（Wasm）让 Python 可以在浏览器中运行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# 使用 Pyodide 在浏览器中运行 Python&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; micropip
&lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; micropip.install(&lt;span class=&quot;hljs-string&quot;&gt;&#39;numpy&#39;&lt;/span&gt;)

&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; numpy &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; np
arr = np.array([&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;5&lt;/span&gt;])
result = arr * &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这意味着前端可以在浏览器中使用 Python 的数据处理能力，而无需服务器参与。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-37&quot;&gt;6.2.2 PyScript 的革命&lt;/h4&gt;
&lt;p&gt;PyScript 让 Python 可以直接嵌入 HTML：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-html&quot; lang=&quot;html&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;&amp;lt;!DOCTYPE &lt;span class=&quot;hljs-keyword&quot;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;head&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;rel&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;href&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://pyscript.net/latest/pyscript.css&quot;&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://pyscript.net/latest/pyscript.js&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;head&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;body&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;output&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;
    
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;py-script&lt;/span&gt;&amp;gt;&lt;/span&gt;
        from js import document
        import numpy as np
        
        arr = np.array([1, 2, 3, 4, 5])
        result = arr * 2
        
        output = document.getElementById(&#39;output&#39;)
        output.innerHTML = f&quot;Result: {result.tolist()}&quot;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;py-script&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;body&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-id=&quot;heading-38&quot;&gt;6.2.3 跨语言借鉴&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 的 &lt;code&gt;worker_threads&lt;/code&gt; 借鉴了 Python 的多进程模型&lt;/li&gt;
&lt;li&gt;TypeScript 的类型系统影响了 Python 的类型注解设计&lt;/li&gt;
&lt;li&gt;Rust 的所有权系统正在影响 JavaScript 和 Python 的内存管理思路&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-id=&quot;heading-39&quot;&gt;6.2.4 全栈工程师的新定义&lt;/h4&gt;
&lt;p&gt;未来的工程师，可能不再被&quot;前端&quot;或&quot;后端&quot;的标签所限制。他们会根据场景选择最合适的工具：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 &lt;strong&gt;Python&lt;/strong&gt; 处理数据、训练模型、编写自动化脚本&lt;/li&gt;
&lt;li&gt;用 &lt;strong&gt;JavaScript/TypeScript&lt;/strong&gt; 构建用户界面、实现交互逻辑&lt;/li&gt;
&lt;li&gt;用 &lt;strong&gt;Rust&lt;/strong&gt; 编写高性能模块、系统级工具&lt;/li&gt;
&lt;li&gt;用 &lt;strong&gt;Go&lt;/strong&gt; 构建微服务、高并发后端&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这不是&quot;全栈&quot;的泛化，而是技术能力的深化。真正的技术专家，不是掌握最多语言的人，而是知道何时使用哪门语言的人。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-40&quot;&gt;结语&lt;/h2&gt;
&lt;p&gt;写完这篇文章，我想起了一个古老的比喻：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;如果你手里只有一把锤子，那么所有问题看起来都像钉子。&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;JavaScript 是我手中的第一把锤子，它帮助我构建了无数精彩的 Web 应用。从简单的页面交互到复杂的单页应用，从 jQuery 到 React，从回调地狱到 async/await——JavaScript 陪伴我走过了前端技术的每一个阶段。&lt;/p&gt;
&lt;p&gt;但 Python 让我看到了另一片天空：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据科学的深邃&lt;/strong&gt;：NumPy、Pandas、Scikit-learn 让数据处理变得优雅而高效&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动化的便捷&lt;/strong&gt;：几行 Python 脚本可以替代 hours of manual work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;科学计算的严谨&lt;/strong&gt;：从物理模拟到金融建模，Python 是科学家的首选语言&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web 开发的简洁&lt;/strong&gt;：FastAPI 的设计哲学让我重新审视&quot;好的代码&quot;的定义&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两门语言不是竞争对手，而是互补的伙伴。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;技术的深度，来自于对一门语言的精通；技术的广度，来自于对多门语言的理解。而技术的智慧，来自于知道何时使用哪一门语言，加油，奥利给！&lt;/p&gt;
&lt;/blockquote&gt;</description><link>https://juejin.cn/post/7630689270854713353</link><guid isPermaLink="false">https://juejin.cn/post/7630689270854713353</guid><pubDate>Mon, 20 Apr 2026 09:21:53 GMT</pubDate><author>老王以为</author><category></category><category>前端</category><category>后端</category><category>Python</category></item><item><title>为什么我不建议普通前端盲目卷全栈？</title><description>&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/72cfbc2b0caf4f0d953b0452047f1bf7~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgRXJwYW5PbWVy:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1777262418&amp;amp;x-signature=ZlpBkU%2FvQG9iJ5Rd9yIKVMj%2BwZ0%3D&quot; alt=&quot;Gemini_Generated_Image_s62j8ys62j8ys62j (1).png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;周末，一个半年前从我们组离职去当 &lt;strong&gt;独立开发者&lt;/strong&gt; 的小伙子，突然约我出来喝了顿大酒。&lt;/p&gt;
&lt;p&gt;半年前他提离职的时候，眼里是有光的。当时他手里拿着一个用 &lt;code&gt;Next.js + Node + MongoDB&lt;/code&gt; 拼凑出来的 &lt;code&gt;AI&lt;/code&gt; 翻译 &lt;code&gt;SaaS&lt;/code&gt; 雏形，满脸兴奋地跟我说，现在有了 AI 辅助，前端搞全栈简直易如反掌，他马上就要去赚美金、做数字游民了😊。&lt;/p&gt;
&lt;p&gt;半年后的饭桌上，他头发肉眼可见地稀疏了，整个人透着一股被掏空的疲惫😢。&lt;/p&gt;
&lt;p&gt;我问他产品跑得怎么样? 他倒了一堆苦水：
上线第二周，因为忘了配置 &lt;code&gt;MongoDB&lt;/code&gt; 的白名单，数据库被黑产端了，留了个比特币勒索地址；
换了云数据库后，上个月被海外羊毛党用并发脚本刷爆了注册接口，由于 &lt;code&gt;Node.js&lt;/code&gt; 端没做事务锁和限流，&lt;code&gt;AI&lt;/code&gt; 服务的 &lt;code&gt;Token&lt;/code&gt; 余额一夜之间被刷欠费了两千多刀。&lt;/p&gt;
&lt;p&gt;他长叹一口气：老大，写后端真他妈不是人干的活😖😖😖。&lt;/p&gt;
&lt;p&gt;这两年，前端圈有一股极其狂热的风气，大厂在逼着前端转全栈，各类博主在教你怎么用 &lt;code&gt;Cursor&lt;/code&gt; 一键生成后端 &lt;code&gt;API&lt;/code&gt;，似乎只要会写几句 &lt;code&gt;JS&lt;/code&gt;，连上个数据库，你就能凭一己之力抗下整个商业闭环。&lt;/p&gt;
&lt;p&gt;但作为一个写了 9 年代码、搞过出海独立站、也无数次给新人擦过屁股的老兵，我今天必须把这层窗户纸捅破：
&lt;strong&gt;绝大多数普通前端理解的全栈，根本就是个一戳就破的纸老虎。我不建议你盲目去卷全栈🤷‍♂️。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-0&quot;&gt;你以为的全栈，只是在写玩具后端&lt;/h3&gt;
&lt;p&gt;很多前端对后端的认知，还停留在用 &lt;code&gt;Express&lt;/code&gt; 或者 &lt;code&gt;NestJS&lt;/code&gt; 写一个 &lt;code&gt;router.get(&#39;/api/user&#39;)&lt;/code&gt;，然后调用一下 &lt;code&gt;ORM&lt;/code&gt; 查查数据库。代码能跑通，能返回 &lt;code&gt;JSON&lt;/code&gt;，就觉得自己是全栈了。&lt;/p&gt;
&lt;p&gt;这是巨大的错觉。&lt;/p&gt;
&lt;p&gt;真实的后端工程，最难的从来不是业务逻辑（&lt;code&gt;CRUD&lt;/code&gt;），而是&lt;strong&gt;高并发下的数据一致性、资源隔离与灾备防御。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;举个最经典的例子。很多刚转全栈的前端，在处理 &lt;strong&gt;用户消耗积分调用 AI&lt;/strong&gt; 这个逻辑时，代码往往是这么写的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 前端思维写出来的后端代码&lt;/span&gt;
app.&lt;span class=&quot;hljs-title function_&quot;&gt;post&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;/api/generate&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; (req, res) =&amp;gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; user = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;findById&lt;/span&gt;(req.&lt;span class=&quot;hljs-property&quot;&gt;userId&lt;/span&gt;);
  
  &lt;span class=&quot;hljs-comment&quot;&gt;// 判断余额&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (user.&lt;span class=&quot;hljs-property&quot;&gt;points&lt;/span&gt; &amp;lt; &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; res.&lt;span class=&quot;hljs-title function_&quot;&gt;status&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;403&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;send&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;积分不足&#39;&lt;/span&gt;);
  }
  
  &lt;span class=&quot;hljs-comment&quot;&gt;// 扣减积分并保存&lt;/span&gt;
  user.&lt;span class=&quot;hljs-property&quot;&gt;points&lt;/span&gt; -= &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;
  &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; user.&lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;();
  
  &lt;span class=&quot;hljs-comment&quot;&gt;// 调用 AI 接口...&lt;/span&gt;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本地单步调试，毫无问题。
但只要把它扔到线上，稍微遇到点网络延迟，或者有黑客同时发来 10 个并发请求。这 10 个请求会同时读到 &lt;code&gt;user.points === 1&lt;/code&gt;，然后各自往下执行，最终用户的 1 个积分被成功扣减了 1 次，但你的 AI 接口被免费调用了 10 次。&lt;/p&gt;
&lt;p&gt;在真正的后端视野里，这叫竞态条件（&lt;code&gt;Race Condition&lt;/code&gt;）。解法是利用数据库层面的原子更新（比如 &lt;code&gt;MongoDB&lt;/code&gt; 的 &lt;code&gt;$inc&lt;/code&gt;），或者是加分布式锁。&lt;/p&gt;
&lt;p&gt;但很多前端根本不懂什么是事务隔离级别，什么是乐观锁悲观锁，什么是慢查询引发的连接池打满。他们拿着一套写 UI 的心智模型去搞后端，最后搭出来的系统，防得住正人君子，防不住任何一次稍微猛烈的流量冲击。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-1&quot;&gt;2026 年独立开发者真实生存状况&lt;/h3&gt;
&lt;p&gt;现在的年轻人动不动就想搞独立 &lt;code&gt;SaaS&lt;/code&gt;，觉得有个好点子就能变现。
我带你看一眼 2026 年前端做独立开发者的真实时间线：&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/5b482679b5f84553bb84a601bfbcffe1~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgRXJwYW5PbWVy:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1777262418&amp;amp;x-signature=dEQNzpm4dM4ZgTZr76ff8CN9Qlc%3D&quot; alt=&quot;Gemini_Generated_Image_xi8phxi8phxi8phx.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一周：&lt;/strong&gt; 激情澎湃，花 5 天时间用 &lt;code&gt;Tailwind CSS&lt;/code&gt; 把落地页的动效调得丝滑无比，深色模式完美适配，觉得自己真是个产品天才。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二周：&lt;/strong&gt; 开始搭后端环境。在 &lt;code&gt;Docker、Nginx&lt;/code&gt; 配置、&lt;code&gt;SSL&lt;/code&gt; 证书续签里痛苦挣扎。为了省几十块钱服务器钱，买了个廉价 &lt;code&gt;VPS&lt;/code&gt;，每天提心吊胆怕宕机。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第四周：&lt;/strong&gt; 产品终于上线了。发到 &lt;code&gt;Product Hunt&lt;/code&gt; 和 &lt;code&gt;V2EX&lt;/code&gt; 上，迎来了 500 个独立访客。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第五周：&lt;/strong&gt; 被俄罗斯或者印度的 &lt;code&gt;Bot&lt;/code&gt; 盯上了。恶意脚本疯狂轰炸你的登录接口，你那单节点的 &lt;code&gt;Node.js&lt;/code&gt; 进程直接 &lt;code&gt;CPU&lt;/code&gt; 飙到 &lt;code&gt;100% OOM&lt;/code&gt; 死机。你大半夜爬起来看日志，临时去搜 &lt;code&gt;Node.js&lt;/code&gt; 怎么做 &lt;code&gt;IP&lt;/code&gt; 频控。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二个月：&lt;/strong&gt; 热情耗尽，服务器吃灰，域名到期不续费🤷‍♂️。。。&lt;/p&gt;
&lt;p&gt;这才是赤裸裸的真相。
很多前端做独立开发，90% 的精力消耗在了配环境、查后端 &lt;code&gt;Bug&lt;/code&gt;、修服务器配置上，真正花在打磨核心产品功能和做营销推广上的时间，连 10% 都不到。&lt;/p&gt;
&lt;p&gt;你以为你是产品 &lt;code&gt;CEO&lt;/code&gt;，其实你只是个免费的初级兼职运维。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-2&quot;&gt;普通前端该怎么破局？学会借力，而不是造轮子&lt;/h3&gt;
&lt;p&gt;说了这么多，难道前端就只能老老实实切图，彻底告别独立开发和全栈了吗？&lt;/p&gt;
&lt;p&gt;错❌❌❌。&lt;/p&gt;
&lt;p&gt;我的核心观点是：&lt;strong&gt;放弃传统后端的玩法，拥抱 Serverless 和 BaaS（后端即服务）。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2026 年了，前端的护城河绝对不是去学怎么配置 &lt;code&gt;K8s&lt;/code&gt; 集群，也不是去死磕如何调优 &lt;code&gt;MySQL&lt;/code&gt; 索引。你的核心价值是 &lt;strong&gt;极速交付业务逻辑&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;要做全栈，就把脏活累活全甩给成熟的云基础设施。
比如这两年我在搞出海项目时，几乎抛弃了所有传统的自建 &lt;code&gt;Node&lt;/code&gt; 服务器部署 (比如 &lt;code&gt;Render&lt;/code&gt;, &lt;code&gt;fly.io&lt;/code&gt;)，全盘转向了 &lt;code&gt;Cloudflare Workers + D1（Serverless SQLite）&lt;/code&gt; 或者 &lt;code&gt;Supabase&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;不用管服务器运维，不用管 &lt;code&gt;Nginx&lt;/code&gt; 负载均衡，自带企业级防 &lt;code&gt;DDOS&lt;/code&gt;，把代码推到边缘节点（&lt;code&gt;Edge&lt;/code&gt;），全球毫秒级生效。&lt;/p&gt;
&lt;p&gt;给你看一眼在 &lt;code&gt;Cloudflare Workers&lt;/code&gt; 里，如何用极简的代码实现极其硬核的 &lt;code&gt;IP 频控（Rate Limit）&lt;/code&gt;，这在传统后端里要搭一套 &lt;code&gt;Redis&lt;/code&gt; 才能搞定：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 基于 Cloudflare 的现代前端全栈玩法&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;request, env&lt;/span&gt;) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; ip = request.&lt;span class=&quot;hljs-property&quot;&gt;headers&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;cf-connecting-ip&#39;&lt;/span&gt;);
    
    &lt;span class=&quot;hljs-comment&quot;&gt;// 调用平台自带的限流服务，一行代码解决防刷问题&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { success } = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; env.&lt;span class=&quot;hljs-property&quot;&gt;RATE_LIMITER&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;limit&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;key&lt;/span&gt;: ip });
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!success) {
      &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Response&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;请求过于频繁，请稍后再试&#39;&lt;/span&gt;, { &lt;span class=&quot;hljs-attr&quot;&gt;status&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;429&lt;/span&gt; });
    }

    &lt;span class=&quot;hljs-comment&quot;&gt;// 处理核心业务逻辑...&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Response&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;业务处理成功&#39;&lt;/span&gt;);
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现了吗？这种工程维度的跨越，才是前端走向全栈的正确姿势。
你不需要去理解底层的流量网关是怎么实现的，你只需要站在巨人的肩膀上，把 &lt;code&gt;API&lt;/code&gt; 串起来，把精力留在如何优化用户的产品体验上。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-3&quot;&gt;别被技术焦虑绑架&lt;/h3&gt;
&lt;p&gt;很多技术社区都在制造焦虑，好像你不懂点微服务、不懂点高并发，你就不配做一个现代的前端。&lt;/p&gt;
&lt;p&gt;但真实的世界是：&lt;strong&gt;没有任何一个商业项目，是因为用了多牛逼的后端架构才成功的；绝大部分死掉的项目，都是因为产品根本没人用，或者在早期就被无意义的基础设施消耗拖垮了团队🤔。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果你是一个前端，有极强的业务嗅觉，想自己做点东西。
那就用熟你手里的 &lt;code&gt;Vue&lt;/code&gt; 或 &lt;code&gt;React&lt;/code&gt;，用好 &lt;code&gt;Tailwind&lt;/code&gt; 快速构建 &lt;code&gt;UI&lt;/code&gt;，把后端托管给 &lt;code&gt;Supabase&lt;/code&gt; 或者 &lt;code&gt;Firebase&lt;/code&gt;，把边缘逻辑交给 &lt;code&gt;Cloudflare&lt;/code&gt;。用两周时间把 &lt;code&gt;MVP&lt;/code&gt;（最小可行性产品）跑通，直接推向市场验证🫡。&lt;/p&gt;
&lt;p&gt;不要去盲目卷传统后端。
把时间留给产品，留给用户，留给真正的商业闭环。这才是 2026 年，一个有独立思考能力的前端老兵，最该具备的技术品味。&lt;/p&gt;
&lt;p&gt;你们说是不是？😊&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/47e9cb8683a94c13a1e6a8f61588e155~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgRXJwYW5PbWVy:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1777262418&amp;amp;x-signature=J4UDO0954Sq8xXOdZGrcqzbIok4%3D&quot; alt=&quot;Suggestion (2).gif&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;</description><link>https://juejin.cn/post/7630290760560295974</link><guid isPermaLink="false">https://juejin.cn/post/7630290760560295974</guid><pubDate>Mon, 20 Apr 2026 04:00:18 GMT</pubDate><author>ErpanOmer</author><category></category><category>前端</category><category>JavaScript</category><category>程序员</category></item><item><title>如何写一个自己的skill</title><description>&lt;h2 data-id=&quot;heading-0&quot;&gt;1. 前言&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;为什么同样用 Claude，你的效率差这么多，本文将会带大家了解 Skill，不管你是什么语言，没写过的情况下，你都可以学会他&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-id=&quot;heading-1&quot;&gt;2. 背景&lt;/h2&gt;
&lt;p&gt;如果你用过 &lt;code&gt;Claude Code&lt;/code&gt; ,大概率遇到过这样的场景: 每次让 &lt;code&gt;Claude Code&lt;/code&gt; 写代码，让部分代码风格保持一致, 你会告诉他项目的编码风格，或者是一段样本代码，或者是指定某个文件；&lt;/p&gt;
&lt;p&gt;也就是，重复描述具体的某一流程，为了解决这个问题，那么就要引出今天我们要说的 &lt;code&gt;Skill&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心速览:&lt;/strong&gt;  &lt;code&gt;Skill&lt;/code&gt; 的本质就是一个文件夹里面放了一个 &lt;code&gt;SKILL.md&lt;/code&gt; 文件，通过编写重复的指令，&lt;code&gt;Claude&lt;/code&gt; 就会识别这个文件，执行特定的功能&lt;/p&gt;
&lt;h2 data-id=&quot;heading-2&quot;&gt;3. 初始 Skill&lt;/h2&gt;
&lt;p&gt;在开始讨论 &lt;code&gt;Skill&lt;/code&gt; 之前，我们要区分一个事情，它和&lt;code&gt;CLAUDE.md&lt;/code&gt; 的区别是什么，&lt;code&gt;CLAUDE.md&lt;/code&gt; 里的内容每次对话都会加载，而 &lt;code&gt;Skill&lt;/code&gt; 只在需要时才加载，不用的时候几乎不占上下文。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Skill&lt;/code&gt; 遵循的规范 (&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fagentskills.io%2Fspecification&quot; target=&quot;_blank&quot; title=&quot;https://agentskills.io/specification&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;Specification - Agent Skills&lt;/a&gt;)。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-3&quot;&gt;3.1 Skill 的位置存放&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;位置&lt;/th&gt;&lt;th&gt;路径&lt;/th&gt;&lt;th&gt;适用范围&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;个人&lt;/td&gt;&lt;td&gt;&lt;code&gt;~/.claude/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&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;code&gt;.claude/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&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;code&gt;&amp;lt;plugin&amp;gt;/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;启用了该插件的地方&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 data-id=&quot;heading-4&quot;&gt;3.2 一个最小的 Skill&lt;/h3&gt;
&lt;p&gt;接下来我们就按项目级别的 &lt;code&gt;Skill&lt;/code&gt; 来给大家说一下，如何实现，这也是你能看完本文快速能落地的方案，聪明的你，就知道如何去提取 &lt;code&gt;Skill&lt;/code&gt; 然后省下自己的token，并且在简历猛吹，自研xxxSkill
这是我的路径： .claude\skills\my-skill\SKILL.md&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;my-skill/&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;└──&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;SKILL.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SKILL.md&lt;/code&gt; 由两部分组成——YAML 头部（frontmatter）和 Markdown 正文：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;description:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;一个示例&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;skill，用于演示基本结构&lt;/span&gt;
&lt;span class=&quot;hljs-meta&quot;&gt;---
&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;当用户打招呼时，用中文回复，并附上一个冷笑话。&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建之后，你可以通过 &lt;code&gt;/hello-world&lt;/code&gt; 手动调用，也可以让 &lt;code&gt;Claude&lt;/code&gt; 在匹配到 &lt;strong&gt;description&lt;/strong&gt; 时自动调用。&lt;/p&gt;
&lt;p&gt;你执行的时候 在对话框输入 &lt;code&gt;/&lt;/code&gt; ，就可以得到下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/3b083b7f8e2b41c5a61ef83070bc0512~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgc2FrYW5h:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1777094512&amp;amp;x-signature=2eVrQ%2BwiPjoegbucvYOm1lwETA4%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt; 如果没有，记得创建好上面的步骤，重启你的 &lt;code&gt;Claude&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;最终执行，我们将得到：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/ff73a5ac0fb14643ac64ac5a97aea232~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgc2FrYW5h:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1777094512&amp;amp;x-signature=ZJHnAxC%2FbVJZsBQt6%2BsGfhmdjA4%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常用 frontmatter 字段速查：&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;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;&lt;td&gt;名称，也是 &lt;code&gt;/hello-world&lt;/code&gt; 的名字&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;最重要的字段&lt;/strong&gt;，Claude 根据它决定是否加载&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;disable-model-invocation&lt;/code&gt;&lt;/td&gt;&lt;td&gt;设为 &lt;code&gt;true&lt;/code&gt; 则只能手动调用，Claude 不会自动触发&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;allowed-tools&lt;/code&gt;&lt;/td&gt;&lt;td&gt;该 skill 激活时允许 Claude 免确认使用的工具&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;context&lt;/code&gt;&lt;/td&gt;&lt;td&gt;设为 &lt;code&gt;fork&lt;/code&gt; 可在子代理中隔离运行&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;具体的东西，可以在上面的规范中去查询，值得注意是 &lt;code&gt;description&lt;/code&gt; 你尽量要写的通俗易懂，上图的内容，你可以再去看看，就知道指的是哪里&lt;/p&gt;
&lt;h2 data-id=&quot;heading-5&quot;&gt;4. 如何高效写出一个 Skill&lt;/h2&gt;
&lt;p&gt;掌握基本结构之后，关键在于写得 &quot;好用&quot;。以下是从官方文档和实践中总结的核心要点：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一，description 是触发器，不是摘要。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Claude&lt;/code&gt; 决定是否加载一个 &lt;code&gt;Skill&lt;/code&gt;，完全依赖 &lt;code&gt;name&lt;/code&gt; + &lt;code&gt;description&lt;/code&gt;。不要写 &quot;这是一个帮助生成代码的工具&quot;，而要写 &quot;生成 Vue 3 组件。当用户要求创建组件、页面或 Vue 文件时使用&quot;。把触发词和使用场景写进去，&lt;code&gt;Claude&lt;/code&gt; 才能准确匹配。说白了，把 &lt;code&gt;Claude&lt;/code&gt; 当成你的合伙人，他要阅读这个说明书，才能懂你意思。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二，指令用命令语气，按步骤编号。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;写 &quot;提取颜色主题&quot;，不要写 &quot;你应该提取颜色主题&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三，SKILL.md 控制在 500 行以内。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当你的项目特别庞大的时候，此时你的内容也会越来越多，我们可以把详细的参考资料、模板、示例拆到同目录下的独立文件里，在 &lt;code&gt;SKILL.md&lt;/code&gt; 中引用它们：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;my-skill/&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;├──&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;SKILL.md&lt;/span&gt;           &lt;span class=&quot;hljs-comment&quot;&gt;# 主指令（必须）&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;├──&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;template.vue&lt;/span&gt;       &lt;span class=&quot;hljs-comment&quot;&gt;# 组件模板&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;├──&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;examples/&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;│&lt;/span&gt;   &lt;span class=&quot;hljs-string&quot;&gt;└──&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;sample.vue&lt;/span&gt;     &lt;span class=&quot;hljs-comment&quot;&gt;# 示例输出&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;└──&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;scripts/&lt;/span&gt;
    &lt;span class=&quot;hljs-string&quot;&gt;└──&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;validate.sh&lt;/span&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;# 辅助脚本&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;SKILL.md&lt;/code&gt; 中这样引用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-markdown&quot; lang=&quot;markdown&quot;&gt;&lt;span class=&quot;hljs-section&quot;&gt;## 参考资料&lt;/span&gt;
&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; 组件模板见 [&lt;span class=&quot;hljs-string&quot;&gt;template.vue&lt;/span&gt;](&lt;span class=&quot;hljs-link&quot;&gt;template.vue&lt;/span&gt;)
&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; 输出示例见 [&lt;span class=&quot;hljs-string&quot;&gt;examples/sample.vue&lt;/span&gt;](&lt;span class=&quot;hljs-link&quot;&gt;examples/sample.vue&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第四，善用动态注入。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;!`command`&lt;/code&gt; 语法可以在 skill 加载前执行 shell 命令，把输出注入到提示词中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;pr-review&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;description:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;审查当前&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;PR&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;的代码变更&lt;/span&gt;
&lt;span class=&quot;hljs-meta&quot;&gt;---
&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;## 当前变更&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;!`git&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;diff&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;--stat`&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;## 变更详情&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;!`git&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;diff`&lt;/span&gt;

&lt;span class=&quot;hljs-string&quot;&gt;请审查以上代码变更，关注潜在的&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;bug&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;和规范问题。&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Claude 看到的不是命令本身，而是命令的输出结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第五，用 &lt;code&gt;$ARGUMENTS&lt;/code&gt; 接收参数。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;fix-issue&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;description:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;修复&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;GitHub&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;issue&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;disable-model-invocation:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;hljs-meta&quot;&gt;---
&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;修复&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;GitHub&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;issue&lt;/span&gt; &lt;span class=&quot;hljs-comment&quot;&gt;#$ARGUMENTS：&lt;/span&gt;
&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;阅读&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;issue&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;描述&lt;/span&gt;
&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;实现修复&lt;/span&gt;
&lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;编写测试&lt;/span&gt;
&lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;创建&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;commit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调用 &lt;code&gt;/fix-issue 42&lt;/code&gt; 时，&lt;code&gt;$ARGUMENTS&lt;/code&gt; 会被替换为 &lt;code&gt;42&lt;/code&gt;。也支持 &lt;code&gt;$0&lt;/code&gt;、&lt;code&gt;$1&lt;/code&gt; 按位置取参数。&lt;/p&gt;
&lt;p&gt;编号可以让 &lt;code&gt;Claude&lt;/code&gt; 按顺序执行，减少遗漏。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第六，区分&quot;谁来调用&quot;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有副作用的操作（部署、发消息）加 &lt;code&gt;disable-model-invocation: true&lt;/code&gt;，防止 &lt;code&gt;Claude&lt;/code&gt; 自作主张。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-6&quot;&gt;5. 总结&lt;/h2&gt;
&lt;p&gt;建议从你日常最重复的流程开始操作，把它封装成第一个 &lt;code&gt;Skill&lt;/code&gt;。写好之后提交到项目的 &lt;code&gt;.claude/skills/&lt;/code&gt; 里，这样团队成员都能受益。（前提不是古法编程）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;code&gt;description&lt;/code&gt; 写成触发条件而非摘要；指令用命令语气并编号；大文件拆分引用；善用动态注入和参数传递。&lt;/p&gt;</description><link>https://juejin.cn/post/7629641479674478642</link><guid isPermaLink="false">https://juejin.cn/post/7629641479674478642</guid><pubDate>Sat, 18 Apr 2026 05:03:18 GMT</pubDate><author>sakana</author><category></category><category>前端</category></item><item><title>Android CLI ，谷歌为 Android 开发者专研的 AI Agent，提速三倍</title><description>&lt;p&gt;Android CLI ，虽然是 CLI ，但是不要误解，它不是一个全新的 AI CLI ，因为 Android CLI 不带模型，它更多是一个通用的 CLI 工具，负责被 Agent 调用的 agentic workflow，另外 Android CLI 天然就包含之前我们聊的 &lt;a href=&quot;https://juejin.cn/post/7628587639852630052&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/post/7628587639852630052&quot;&gt;android skills&lt;/a&gt; ，而目的是让你在 AI 时代脱离 Android Studio 也可以进行一些 AI 工作，并且更精准节能。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/48337b84e6704d3c8336d06047d6294e~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1777001326&amp;amp;x-signature=MByBzb05436lAHYlLzCAUaodlmI%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;简单来说，Android CLI 只负责被 Agent 调用，它会通过 &lt;code&gt;android skills&lt;/code&gt; 和 &lt;code&gt;android docs&lt;/code&gt; 来 “ grounding” Agent，让 Agent 严格按照 Google 最新最佳实践执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最简单的就是，新的 Android CLI 支持提供&lt;strong&gt;环境设置、项目创建和设备管理的命令&lt;/strong&gt;，&lt;em&gt;create 命令可以在几秒钟内创建一个 Android 应用项目&lt;/em&gt; ，并且最重要的是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;使用 Android CLI 场景 Token 使用量减少 70% 以上，因为它改进了项目和环境设置，&lt;strong&gt;连带着任务完成速度也比只使用标准工具集来处理的 Agnet 快 3 倍&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在 Android CLI 场景，可以使用的主要功能包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SDK 管理： 使用 &lt;code&gt;android sdk install&lt;/code&gt; 只下载项目需要的特定组件，确保精简的开发环境&lt;/li&gt;
&lt;li&gt;快速创建项目： &lt;code&gt;android create&lt;/code&gt; 命令从官方模板生成新项目，确保从第一行代码开始就是官方推荐的架构和最佳实践&lt;/li&gt;
&lt;li&gt;快速创建和部署：使用 &lt;code&gt;android emulator&lt;/code&gt; 创建和管理虚拟设备，使用 &lt;code&gt;android run&lt;/code&gt; 部署应用&lt;/li&gt;
&lt;li&gt;更新： 运行 &lt;code&gt;android update&lt;/code&gt; ，确保拥有最新的功能支持&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Android CLI 可以完成整个流程，它不仅能增强 Agent 开发流程，还能简化 CI、维护以及脚本自动化流程。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/1241606fb1c148e3a9d68a8f55e44905~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1777001326&amp;amp;x-signature=ZANkAqZM1pdjOWazYDBohIGhe3A%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;另外，配合 &lt;code&gt;android skills &lt;/code&gt;可以更快速和精准完成，比如通过 &lt;code&gt;android skills&lt;/code&gt; 实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Navigation 3 设置和迁移&lt;/li&gt;
&lt;li&gt;edge-to-edge 适配迁移&lt;/li&gt;
&lt;li&gt;AGP 9 和 XML 到 Compose 的迁移&lt;/li&gt;
&lt;li&gt;R8 配置分析&lt;/li&gt;
&lt;li&gt;····&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/35af90b88a7f460893ae991320fa50b6~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1777001326&amp;amp;x-signature=6xOY%2FtJ2bbsj6FFNs79ovSJET6k%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;android skills add&lt;/code&gt; 默认只会安装 &lt;code&gt;android-cli&lt;/code&gt; skill，如果不加 &lt;code&gt;--all&lt;/code&gt; 或 &lt;code&gt;--skill&lt;/code&gt;，并不是所有 skills 都自动带上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;另外一个就是 &lt;code&gt;android docs&lt;/code&gt; ，目前和这个知识库包含在最新版本的 Android Studio ，提供最新版本的 Android API ，通过更新的知识库，而 Android CLI 也可以暴露这个文档能力，Agent 可以根据 Android 开发者文档、Firebase、Google Developers 和 Kotlin 文档中的最新信息来提供响应，让你的 AI 不使用过期代码：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/5d94f613199b4d498cd4177575c5a9b2~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1777001326&amp;amp;x-signature=yvzr3aV2ujC7mpf89pHQ2X7AEGM%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;那其实说了那么多，Android CLI 有啥场合？其实目前来看的话，这就是给 Agent 一个“不会乱用的 adb/gradle 封装” ，让 Agent 永远遵循官方最新 Knowledge Base，不会给你过时的或错误的命令，提供 Android 开发“操作系统级”接口，让任何大模型都能 3 倍速、更进准地开发 Android App 。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;例如在 Gemini CLI 或者 Claude Code 里：Gemini CLI 负责思考，Android CLI 负责高效执行 Android 具体操作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最后，想体验的话，可以通过下方安装测试：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code class=&quot;hljs language-ruby&quot; lang=&quot;ruby&quot;&gt;curl -fsSL &lt;span class=&quot;hljs-symbol&quot;&gt;https:&lt;/span&gt;/&lt;span class=&quot;hljs-regexp&quot;&gt;/dl.google.com/android&lt;/span&gt;&lt;span class=&quot;hljs-regexp&quot;&gt;/cli/latest&lt;/span&gt;&lt;span class=&quot;hljs-regexp&quot;&gt;/linux_x86_64/install&lt;/span&gt;.sh |&lt;span class=&quot;hljs-params&quot;&gt; bash
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Mac OS&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code class=&quot;hljs language-ruby&quot; lang=&quot;ruby&quot;&gt;curl -fsSL &lt;span class=&quot;hljs-symbol&quot;&gt;https:&lt;/span&gt;/&lt;span class=&quot;hljs-regexp&quot;&gt;/dl.google.com/android&lt;/span&gt;&lt;span class=&quot;hljs-regexp&quot;&gt;/cli/latest&lt;/span&gt;&lt;span class=&quot;hljs-regexp&quot;&gt;/darwin_arm64/install&lt;/span&gt;.sh |&lt;span class=&quot;hljs-params&quot;&gt; bash
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code class=&quot;hljs language-ruby&quot; lang=&quot;ruby&quot;&gt;curl.exe -fsSL &lt;span class=&quot;hljs-symbol&quot;&gt;https:&lt;/span&gt;/&lt;span class=&quot;hljs-regexp&quot;&gt;/dl.google.com/android&lt;/span&gt;&lt;span class=&quot;hljs-regexp&quot;&gt;/cli/latest&lt;/span&gt;&lt;span class=&quot;hljs-regexp&quot;&gt;/windows_x86_64/install&lt;/span&gt;.cmd -o &lt;span class=&quot;hljs-string&quot;&gt;&quot;%TEMP%\i.cmd&quot;&lt;/span&gt; &amp;amp;&amp;amp; &lt;span class=&quot;hljs-string&quot;&gt;&quot;%TEMP%\i.cmd&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者直接下载地址：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux: &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fdl.google.com%2Fandroid%2Fcli%2Flatest%2Flinux_x86_64%2Fandroid&quot; target=&quot;_blank&quot; title=&quot;https://dl.google.com/android/cli/latest/linux_x86_64/android&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;dl.google.com/android/cli…&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Windows: &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fdl.google.com%2Fandroid%2Fcli%2Flatest%2Fwindows_x86_64%2Fandroid.exe&quot; target=&quot;_blank&quot; title=&quot;https://dl.google.com/android/cli/latest/windows_x86_64/android.exe&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;dl.google.com/android/cli…&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mac: &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fdl.google.com%2Fandroid%2Fcli%2Flatest%2Fdarwin_arm64%2Fandroid&quot; target=&quot;_blank&quot; title=&quot;https://dl.google.com/android/cli/latest/darwin_arm64/android&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;dl.google.com/android/cli…&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-id=&quot;heading-0&quot;&gt;参考链接&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fandroid-developers.googleblog.com%2F2026%2F04%2Fbuild-android-apps-3x-faster-using-any-agent.html&quot; target=&quot;_blank&quot; title=&quot;https://android-developers.googleblog.com/2026/04/build-android-apps-3x-faster-using-any-agent.html&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;android-developers.googleblog.com/2026/04/bui…&lt;/a&gt;&lt;/p&gt;</description><link>https://juejin.cn/post/7629374592915128383</link><guid isPermaLink="false">https://juejin.cn/post/7629374592915128383</guid><pubDate>Fri, 17 Apr 2026 03:28:46 GMT</pubDate><author>恋猫de小郭</author><category></category><category>前端</category><category>Android</category><category>Flutter</category></item><item><title>同事偷偷给我介绍私活，说1万报酬全给我，结果甲方私下告诉我说，同事在当中白拿了2万，我觉得被耍了，媳妇却让我要知足，说我一点不亏</title><description>&lt;p&gt;最近例行逛职场社区的时候，看到一个很有意思的讨论帖。&lt;/p&gt;
&lt;p&gt;意思大概是这样的：&lt;/p&gt;
&lt;p&gt;同事私下偷偷给帖主介绍了单私活，说报酬有一万全部给他，结果后来不知什么原因或者机缘巧合，甲方私下告诉帖主，同事从中间吃了两万。&lt;/p&gt;
&lt;p&gt;他感觉自己像个傻子，被耍得团团转，但他媳妇听后却不这么认为，反倒劝他要知足，如果没有这位同事的介绍，连这一万也挣不到。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/021442f8208e435dbc090ce3ff4db295~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgQ29kZVNoZWVw:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776992921&amp;amp;x-signature=%2Fiv408X7rybz1Ait2Z4LL3PHYF4%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;关于这个帖子，我相信有不少同学也刷到过，我第一眼刷到的感觉是：&lt;/p&gt;
&lt;p&gt;啥？接私活？这题我熟啊，这里面的坑我可就更熟了（手动 doge）。&lt;/p&gt;
&lt;p&gt;帖子讨论的声音有很多。有人义愤填膺，骂同事不地道，也有人同意帖主媳妇的观点，认为职场本就如此，当然还有一些人在感慨，这就是技术人情怀与现实的碰撞。&lt;/p&gt;
&lt;p&gt;作为一个在代码世界和职场江湖里摸爬滚打过的人，今天这篇文章我也想就这个话题，和大家聊聊我的一些思考。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;首先，在程序员的预设里，往往会默认觉得同事的主动示好是一种善意，是一种基于同事情谊的帮助。&lt;/p&gt;
&lt;p&gt;他接受这个项目，是出于对同事的信任。&lt;/p&gt;
&lt;p&gt;然而，真相却是，这份善意背后有时会隐藏着一定的利益获取。&lt;/p&gt;
&lt;p&gt;所以，&lt;strong&gt;这就是一个典型的职场信息差案例&lt;/strong&gt;，将程序员置于了一个信息孤岛之中。&lt;/p&gt;
&lt;p&gt;因此站在帖主的角度，有这种被耍感再正常不过了。&lt;/p&gt;
&lt;p&gt;这种情绪的根源，并不仅仅是因为那 2 万块钱的差价，更深层的原因，还是源于信任的崩塌和信息的不对称。&lt;/p&gt;
&lt;p&gt;而对于程序员媳妇的知足论，乍一听，这话似乎有些冷血，甚至有点阿 Q 精神，但是在剥离了情感色彩后，我们也不得不承认，她的话也揭示了商业世界里一条冷酷却真实的法则，那就是价值决定收益。&lt;/p&gt;
&lt;p&gt;这几年我自己也和朋友运营着一个项目小团队，接活做活，这种感受愈发明显。&lt;/p&gt;
&lt;p&gt;要知道，在商业合作中，&lt;strong&gt;分配规则往往不是按谁更辛苦来划分，而是按谁掌握稀缺资源来决定&lt;/strong&gt;，这个道理相信大家谁都清楚。&lt;/p&gt;
&lt;p&gt;程序员提供的，是技术能力，算是一种相对标准化的生产要素吧，在市场上有大致的价格锚点。&lt;/p&gt;
&lt;p&gt;而那位同事提供的，则是客户资源、项目机会以及信任背书。&lt;/p&gt;
&lt;p&gt;在这位程序员尚未建立起自己的客户网络时，这些资源对他而言，是稀缺且难以获取的。&lt;/p&gt;
&lt;p&gt;同事表面看起来没做什么事情，就介绍了一下，但是他作为中间商，解决了流通和信息不对称的问题，其实也是创造了价值，人家从中间赚多少钱那是人家自己的本事。&lt;/p&gt;
&lt;p&gt;没有他，程序员确实连这 1 万也赚不到。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;所以结合上面这两点的分析，可以提炼出这几点对我们有益的启示。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于帖子中的程序员来说，事情既然已成定局，我觉得过多的纠结与内耗是最没有用的。&lt;/p&gt;
&lt;p&gt;这个问题的终极解法，我个人觉得还是在于程序员自身的觉醒和成长。&lt;/p&gt;
&lt;p&gt;我们不能因为一次所谓的被坑，就对人性彻底失望，从此变得愤世嫉俗，当然也不能因为赚到了钱，就可以自我麻痹，而放弃准则和追求。&lt;/p&gt;
&lt;p&gt;这个事情应该让我们看清的是职场的复杂性，&lt;strong&gt;同时更要明白信息和资源的重要性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;所以与其把精力消耗在对同事的道德批判上，不如把这次经历转化为自我提升的动力。&lt;/p&gt;
&lt;p&gt;成长不都是这么走出来的么。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;首先，程序员应该要建立自己的防火墙。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在接私活或进行任何形式的合作时，还是要坚持事前约定，透明沟通的原则。&lt;/p&gt;
&lt;p&gt;要我说这事有时候挺简单，挣自己认知以内的钱，给的这些报酬你自己权衡，觉得合适就接，不合适就拒，别人能从中赚多少钱那是别人的本事。&lt;/p&gt;
&lt;p&gt;没必要内耗，更不要因为是熟人介绍就不好意思谈钱（好多程序员在刚开始接私活的时候往往会这样）。&lt;/p&gt;
&lt;p&gt;报酬谈好之后，接下来在实施过程中会面临的：需求、边界、责任、风险，一个都不能少，关键要素有一个算一个，全部要在事前就要明确定下来，不能糊里糊涂就答应下来。&lt;/p&gt;
&lt;p&gt;要知道，清晰的界限才是健康关系的基础。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;其次，要主动构建自己的护城河。&lt;/strong&gt;&lt;/p&gt;
&lt;p&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;p&gt;职场中金钱的得失是暂时的，但个人能力的成长和认知的升级却是永久的。&lt;/p&gt;
&lt;p&gt;这 2 万块钱的差价，可以把它看作是为这次认知升级所支付的学费。&lt;/p&gt;
&lt;p&gt;这笔学费的确不便宜，但是如果自己能从中学会如何更成熟、更智慧地处理复杂的人际关系和商业合作，那么它就是值得的，大家觉得呢？&lt;/p&gt;
&lt;p&gt;那关于这个问题，你的看法是什么呢，如果有不同的见解，也欢迎一起来分享交流~&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注：本文在GitHub开源仓库「编程之路」 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Frd2coding%2FRoad2Coding&quot; target=&quot;_blank&quot; title=&quot;https://github.com/rd2coding/Road2Coding&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github.com/rd2coding/R…&lt;/a&gt; 中已经收录，里面有我整理的6大编程方向(岗位)的自学路线+知识点大梳理、面试考点、我的简历、几本硬核pdf笔记，以及程序员生活和感悟，欢迎star。&lt;/p&gt;
&lt;/blockquote&gt;</description><link>https://juejin.cn/post/7629261406288379940</link><guid isPermaLink="false">https://juejin.cn/post/7629261406288379940</guid><pubDate>Fri, 17 Apr 2026 01:08:41 GMT</pubDate><author>CodeSheep</author><category></category><category>前端</category><category>后端</category><category>程序员</category></item><item><title>JetBrains Amper 0.10 ，期待它未来替代 Gradle</title><description>&lt;p&gt;最近 JetBrains 发布了 &lt;code&gt;Amper 0.10&lt;/code&gt; ，做为一个面向 Kotlin / Java 的实验性构建和配置工具，他的目标是通过更简单的 &lt;code&gt;YAML&lt;/code&gt; 配置去支撑 Kotlin Multiplatform、Android、iOS、JVM 等项目管理，然后再通过 Amper CLI 完成构建、运行和工具集成。&lt;/p&gt;
&lt;p&gt;这一次 &lt;code&gt;Amper 0.10&lt;/code&gt; 更新之所以要聊聊，是因为它经过几年发展，它已经相对变得完善了不少，这次 0.10 主要包括了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JDK provisioning，可以自动下载和安装匹配的 JDK，默认是 JDK 21，支持在 &lt;code&gt;module.yaml&lt;/code&gt; 里声明需要的版本&lt;/li&gt;
&lt;li&gt;Maven to Amper 转换，可以读取现有 &lt;code&gt;pom.xml&lt;/code&gt;，生成 &lt;code&gt;project.yaml&lt;/code&gt; / &lt;code&gt;module.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;支持第三方 Kotlin compiler plugins&lt;/li&gt;
&lt;li&gt;默认工具链版本包含 Android &lt;code&gt;minSdk 23&lt;/code&gt;、Kotlin 2.3.20、Compose 1.10.3、KSP 2.3.6&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那 Amper 有什么用？简答来说，通过 Amper 可以替代现在烦人的 Gradle ，AGP 如今的体验可以说是又臭又长，特别是现在 KMP 时代，&lt;code&gt;build.gradle.kts&lt;/code&gt; 往往越来越厚 ，而 Amper 的目的是通过 &lt;code&gt;YAML&lt;/code&gt; 来让 IDE 更容易理解项目结构、做自动补全、特别是针对 KMP 用 &lt;code&gt;YAML&lt;/code&gt; 来声明模块、平台、依赖和平台特定配置。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;看 2024–2026 的更新，Amper 不断补充的是 &lt;em&gt;project file tooling、Compose resources、KSP2、Android release builds、run/test UX&lt;/em&gt; 这些能力，可以看出来在一步一步地侵蚀 Gradle 原本的场景。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;特别是，现在对于 Amper 来说，standalone version （脱离 Gradle）已经变成主要发展方向，Gradle 版本逐步弱化，而对 Android 来说，Amper 可以让项目配置更省心：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JDK 自动准备好，可以灵活配置&lt;/li&gt;
&lt;li&gt;模块定义更直观&lt;/li&gt;
&lt;li&gt;KMP/Compose/Android 放在同一套声明模型下&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/04b749d0584348e8bb6aa5a626a3488c~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776992403&amp;amp;x-signature=fkIVPERLyv0rz4Q235Hzydo8RqI%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;虽然一开始 Amper 是建立在 Gradle 上的配置层，但是后来它自己也逐步发展出 standalone CLI、独立命令体系和自己的项目文件，也就是它既可以在 Gradle 生态逐步发展，也可以独立出自己的运行模式，只是目前越来越脱离 Gradle，大概类似于：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Amper = JetBrains 在 Kotlin/KMP/Android 构建体验上的新抽象层和新入口&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/eab6b111969a437d81c13526d55aa642~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776992403&amp;amp;x-signature=C4kPpqkgDUYMleAiOCjecmnpSGo%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;因为 Gradle 本质是一个可编程 DSL，所以它既可以写逻辑，又可以写条件，还可以 hook 生命周期，所以老项目的结果往往就是，配置文件逐渐变成“代码工程本身的一部分”。&lt;/p&gt;
&lt;p&gt;而这个问题在 KMP 上变得更明显，因为多平台以来之后，环境，脚本和 CI 也变得更加复杂，但是 Amper 不一样，Amper 的官方定义是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一个面向 Kotlin 和 Java 的构建工具，使用 YAML 进行声明式配置，并提供 CLI 和 IDE 集成。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;例如在之前 Gradle 常见写法是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-scss&quot; lang=&quot;scss&quot;&gt;kotlin {
 &amp;nbsp; &amp;nbsp;&lt;span class=&quot;hljs-built_in&quot;&gt;android&lt;/span&gt;()
 &amp;nbsp; &amp;nbsp;&lt;span class=&quot;hljs-built_in&quot;&gt;ios&lt;/span&gt;()

 &amp;nbsp; &amp;nbsp;sourceSets {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;val commonMain by getting {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;dependencies {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span class=&quot;hljs-built_in&quot;&gt;implementation&lt;/span&gt;(&quot;xxx&quot;)
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;  }
 &amp;nbsp; &amp;nbsp; &amp;nbsp;  }
 &amp;nbsp;  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而在 Amper 里，写法就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;product:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;type:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;app&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;platforms:&lt;/span&gt;
  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;android&lt;/span&gt;
  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ios&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;dependencies:&lt;/span&gt;
  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;xxx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/7f47b01305b74ff0a5f9ca6cd125903f~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776992403&amp;amp;x-signature=NRU2e2FosRHWQyVYc5UAkCXq%2F%2FU%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/91e569cf190d467aab97c38bbd95808c~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776992403&amp;amp;x-signature=WNfi15bK2ww7cH7dWc3uEt7j%2FRM%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;那为什么说它会变得更好，因为它的集成度和全自动化，例如 Amper 可以自动下载 JDK 和自动匹配版本，用官方的话说就是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;开发者可以在不手动安装任何东西的情况下就能运行项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/0b2b141175314a7bae4156a5dde005ff~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776992403&amp;amp;x-signature=BfPD8LFTY5UHpSZ1LE6uQOR8QuY%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;也就是，用 Amper 的目的是它自己就可以负责“环境一致性”，特别是前面我们说过， 2024–2026 的各种更新内容有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;project model（项目结构）&lt;/li&gt;
&lt;li&gt;Compose resources（资源处理）&lt;/li&gt;
&lt;li&gt;KSP2（代码生成）&lt;/li&gt;
&lt;li&gt;Android release builds（发布构建）&lt;/li&gt;
&lt;li&gt;run / test UX（运行与测试）&lt;/li&gt;
&lt;li&gt;compiler plugins（编译扩展）&lt;/li&gt;
&lt;li&gt;JDK provisioning（环境管理）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从这些更新，可以看到一个非常清晰的覆盖路径，路径几乎就是 Gradle 在 Android 项目里的核心职责。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/3f74be6dbff44246adca8ed8eb0e1286~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776992403&amp;amp;x-signature=YyH8ugRrfp7%2BxhTqwZdWiqdp1Lg%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;另外 Amper 的角度， 构建工具不只是单纯的“工具”，而是带 toolchain、默认版本、项目模型的一体化生态产品，并且 Declarative 的 YAML/TOML 也在逐步替代 Gradle DSL ，因为 AI 时代：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;DSL 太强，IDE 太难理解，特别是多平台下，必须要一个更简单的模型。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;还有个适配小技巧，&lt;strong&gt;复制 gradle 然后粘贴到 module.yaml 时，它会自动转换为 Amper 适配的模式&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/dc39d6fdfadc4623b5d5c17f3f3faaf7~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776992403&amp;amp;x-signature=nB4cSs2wqEzg0YNvEHDfEA16mo4%3D&quot; alt=&quot;ezgif-42d705d72a6e066c&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;所以，&lt;strong&gt;目前虽然 &lt;code&gt;Amper 0.10&lt;/code&gt; 还不是正式的 Gradle 的替代者，但它已经在系统性接管 Gradle 的职责&lt;/strong&gt;，至少在 Kotlin/KMP/Android 的 JetBrains 生态里，Amper 已经不只是一个实验配置层，而是在被持续推进成为新的构建入口。&lt;/p&gt;
&lt;p&gt;所以可以预期，未来 Amper 完全替代 Gradle 的哪一天，就可以不再面对这个又臭又大的 AGP 了。&lt;/p&gt;</description><link>https://juejin.cn/post/7629261406288298020</link><guid isPermaLink="false">https://juejin.cn/post/7629261406288298020</guid><pubDate>Fri, 17 Apr 2026 01:00:03 GMT</pubDate><author>恋猫de小郭</author><category></category><category>前端</category><category>Android</category><category>Flutter</category></item><item><title>面试官问我：“AI 写代码比你快 100 倍，你的价值在哪？”</title><description>&lt;p&gt;面试官是一个看着很资深的技术总监。他喝了口水，突然抛出了一个非常尖锐的问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“你平时用 AI 写代码吗？现在 AI 敲代码的速度比你快 100 倍，错误率还比你低。在这种背景下，你觉得程序员的核心价值到底在哪？未来的出路又在哪？”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个问题其实我私下里思考过无数次。当 ChatGPT 刚出来能手写红黑树的时候，我也曾后背发凉，甚至怀疑过自己是不是马上要失业了。&lt;/p&gt;
&lt;p&gt;面对面试官犀利的眼神，我深吸了一口气，给出了我的回答。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-0&quot;&gt;认清现实：编码的边际价值正在归零&lt;/h2&gt;
&lt;p&gt;我回答面试官的第一句话是：“&lt;strong&gt;AI 确实让‘写代码’这门手艺变得前所未有的廉价，但这绝对不等于‘做软件’变简单了。&lt;/strong&gt;”&lt;/p&gt;
&lt;p&gt;以前，咱们程序员的护城河是熟练掌握一门语言的语法、精通某个框架的边角料特性。&lt;/p&gt;
&lt;p&gt;但今天，这些“执行层”的技能正在被大模型瞬间填平。&lt;/p&gt;
&lt;p&gt;当“编码”本身的边际价值无限趋近于零时，我们需要重新审视自己的价值。&lt;/p&gt;
&lt;p&gt;我跟面试官总结了三点在 AI 时代反而变得更值钱的能力：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;结合现实商业语境的需求洞察与博弈&lt;/strong&gt;：现在 AI 确实能通过多轮对话帮你把一句话需求细化成几十页的 PRD，但它不懂“老板真正的意图”，不懂跨部门的利益拉扯，更不懂业务妥协。能结合公司现状，把模糊需求转化为各方都能接受、且技术可落地的系统边界，这是人的本事。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基于有限资源的架构权衡（Trade-off）&lt;/strong&gt;：AI 当然能给你一套完美的千万级并发高可用架构图，但它不知道你们公司服务器预算只有 5000 块，不知道你们运维团队根本 hold 不住 K8s，也不知道那个祖传的屎山数据库坚决不能动。基于团队现实的妥协与决策，AI 永远给不了“正确”答案。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最终的系统兜底与“担责”（Accountability）&lt;/strong&gt;：这是最致命的一点。现在 AI 的 Code Review 能力确实很强，能查出大部分内存泄漏和死锁。但是，代码一旦合并，线上业务崩了、用户数据泄露了，谁来背锅？AI 没法被开除，没法去坐牢。人类作为最终节点的确认者和兜底者，提供的是无可替代的“信任背书”。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;“所以，”我看着面试官说，“&lt;strong&gt;咱们的生路非常清晰：把‘体力活’通通外包给 AI，逼着自己去做那些更靠脑力、更需要承担责任的系统决策。&lt;/strong&gt;”&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-1&quot;&gt;打造护城河：未来 5 年的 5 大杀手锏&lt;/h2&gt;
&lt;p&gt;面试官听完点了点头，追问道：“思路不错，那落实到具体的个人成长上，你觉得未来几年程序员该死磕哪些能力？”&lt;/p&gt;
&lt;p&gt;我抛出了我总结的 5 大护城河：&lt;/p&gt;
&lt;h3 data-id=&quot;heading-2&quot;&gt;1. 从“调包侠”进化为“架构师”&lt;/h3&gt;
&lt;p&gt;AI 最擅长的是“局部执行”，最烂的是“全局决策”。别再只盯着业务代码怎么调用 API，必须强迫自己从 0 到 1 思考系统设计。去学领域驱动设计（DDD），去懂网络底层，去学会在高可用、低延迟和开发成本之间做权衡。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-3&quot;&gt;2. 把 AI 当“黑奴”，而不是“拐杖”&lt;/h3&gt;
&lt;p&gt;很多人用 AI 是浅尝辄止，拿到跑不通的代码就抱怨 AI 不行。真正的高手会给 AI 喂极度精准的背景上下文（Context），设定严格的代码规范，通过多轮追问（Prompt Engineering）让 AI 迭代，甚至让 AI 先输出设计思路再写代码。建立自己的 Prompt 军火库，能让单兵作战能力飙升 10 倍。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-4&quot;&gt;3. 练就火眼金睛的 Code Review 能力&lt;/h3&gt;
&lt;p&gt;以后，&lt;strong&gt;谁能给 AI 写的代码“擦屁股”，谁就能拿高薪。&lt;/strong&gt; AI 生成的代码往往隐藏着致命逻辑漏洞或极差的可维护性（精致的屎山）。你需要整理一套属于自己的 Code Review 清单：边界值、并发安全、数据库索引命中率等，把质量把控做成绝对壁垒。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-5&quot;&gt;4. 懂点业务，长点“产品脑”&lt;/h3&gt;
&lt;p&gt;纯技术宅的生存空间会越来越窄。技术实现成本变低后，&lt;strong&gt;懂业务的工程师将极度稀缺。&lt;/strong&gt; 我们得变成技术与业务的桥梁，理解商业模式，提出直击灵魂的澄清问题，甚至用 AI 快速糊出原型去验证业务方向。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-6&quot;&gt;5. 用 AI 搞定实际问题，别光谈理论&lt;/h3&gt;
&lt;p&gt;真正的创新不是去手搓一个大模型，而是**“用现成的 AI 能力，解决公司陈年老垢的业务痛点”**。比如，用 LLM 改造难用的内部知识库搜索，或者用 Agent 自动化排查繁琐的运维报警。把 AI 落地变成真金白银。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-7&quot;&gt;破局之路：不同阶段的突围策略&lt;/h2&gt;
&lt;p&gt;面试官笑了笑：“你说的这些都挺对，但对不同工作经验的人来说，挑战是不一样的。”&lt;/p&gt;
&lt;p&gt;我顺势接话：“没错，所以对不同阶段的程序员，我的建议是不同的。”&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;刚入行（0-3 年）&lt;/strong&gt;：最怕掉进“语法陷阱”。过度依赖 AI 补全，连基本排错能力都不学了。应该利用 AI 快速跨过“死记硬背语法”的痛苦期，把时间砸在学习设计模式、数据结构和深入理解业务上。学 AI 解决问题的“思路”，而不是盲目接受“结果”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中坚力量（3-7 年）&lt;/strong&gt;：这是最危险的分水岭。熟练掌握的增删改查和组件使用，恰恰是 AI 最易替代的。必须向上突破，主导复杂系统设计，带新人，深耕一个垂直领域（如电商交易链路、音视频底层等），让“行业踩坑经验”不可替代。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;技术老鸟（7 年以上）&lt;/strong&gt;：别吃老本了，“最佳实践”正在被固化到 AI 工具里。要升维到“技术战略”层面。思考怎么引入 AI 工具提升部门研发效能？引入大模型算力的 ROI 划算吗？或者去做专门解决极端疑难杂症的“定海神针”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;懂业务的跨界老炮&lt;/strong&gt;：这是你们的黄金时代！“深度的行业认知 + 用 AI 快速实现想法的能力” = 降维打击。去当业务合伙人、做独立开发者，或者搞垂直领域的 AI 创新产品。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-8&quot;&gt;结语：思维的跃迁才是真正的出路&lt;/h2&gt;
&lt;p&gt;面试官听完，沉默了一会儿，然后对我微微一笑。&lt;/p&gt;
&lt;p&gt;“说得很好，”他说，“很多人还沉浸在被 AI 替代的恐慌中，你已经想清楚怎么利用它了。”&lt;/p&gt;
&lt;p&gt;兄弟们，在 AI 时代，&lt;strong&gt;最值钱的程序员，永远不是敲代码最快的那个人。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最值钱的，是能一眼看透问题本质的人；是能设计出优雅且抗造系统的人；是能把 AI 当成最强副手，并有能力为最终结果兜底的人。&lt;/p&gt;
&lt;p&gt;千万别把 AI 当成来抢饭碗的阶级敌人，它不过是你职业生涯里遇到过的、最听话、最不知疲倦的实习生罢了。&lt;/p&gt;
&lt;p&gt;去指挥它，去审核它，然后狠狠地踩在它的肩膀上，去解决那些真正能赚大钱、有高价值的难题。&lt;/p&gt;
&lt;p&gt;咱们的出路，就在思维的跃迁里。干就完了！大家一起加油！💪&lt;/p&gt;</description><link>https://juejin.cn/post/7629213925721145379</link><guid isPermaLink="false">https://juejin.cn/post/7629213925721145379</guid><pubDate>Thu, 16 Apr 2026 23:57:04 GMT</pubDate><author>雨夜寻晴天</author><category>人工智能</category><category>前端</category><category>AI编程</category><category>面试</category></item><item><title>踩坑分享：Vite Plus 最佳实践</title><description>&lt;h2 data-id=&quot;heading-0&quot;&gt;写在前面&lt;/h2&gt;
&lt;p&gt;掘金的同学们大家好呀，作者是 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Fvarlet&quot; target=&quot;_blank&quot; title=&quot;https://github.com/varletjs/varlet&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;Varlet UI&lt;/a&gt; 的作者。掘金文章已经一年没更新了，去年跳槽到了一家创业公司负责前端架构工作，写文章这件事就一直搁置了。最近稍微缓过来了一点点（其实还是压力很大...），但不妨碍今天来给大家分享一下我们最新的开源项目 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail&quot; target=&quot;_blank&quot; title=&quot;https://github.com/varletjs/rattail&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;rattail&lt;/a&gt;。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-1&quot;&gt;先聊聊 Vite Plus&lt;/h3&gt;
&lt;p&gt;上个月 &lt;code&gt;VoidZero&lt;/code&gt; 正式以 &lt;code&gt;MIT&lt;/code&gt; 协议开源了 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fviteplus.dev&quot; target=&quot;_blank&quot; title=&quot;https://viteplus.dev&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;Vite+&lt;/a&gt;，它把 &lt;code&gt;Vite&lt;/code&gt;、&lt;code&gt;Vitest&lt;/code&gt;、&lt;code&gt;Oxlint&lt;/code&gt;、&lt;code&gt;Oxfmt&lt;/code&gt;、&lt;code&gt;Rolldown&lt;/code&gt;、&lt;code&gt;tsdown&lt;/code&gt; 统一收拢到了一个 &lt;code&gt;vp&lt;/code&gt; 命令下面，一套工具链覆盖 &lt;code&gt;dev&lt;/code&gt;、&lt;code&gt;build&lt;/code&gt;、&lt;code&gt;test&lt;/code&gt;、&lt;code&gt;lint&lt;/code&gt;、&lt;code&gt;fmt&lt;/code&gt;、&lt;code&gt;pack&lt;/code&gt; 等所有工程化环节。作者第一时间就把 &lt;code&gt;varlet&lt;/code&gt; 周边的项目迁移到了 &lt;code&gt;Vite+&lt;/code&gt; 上面试试水，迁移下来发现效果特别好。以前那些散落在各处的 &lt;code&gt;eslint&lt;/code&gt; 配置、&lt;code&gt;prettier&lt;/code&gt; 配置、&lt;code&gt;lint-staged&lt;/code&gt; 配置、&lt;code&gt;commitlint&lt;/code&gt; 配置可以统一收拢到一个 &lt;code&gt;vite.config.ts&lt;/code&gt; 里面，项目根目录一下子干净了不少（以前打开根目录看到十几个 &lt;code&gt;.xxxrc&lt;/code&gt; 文件的日子终于结束了）。而且因为工具链统一了，AI Agent 在理解项目配置的时候幻觉也少了很多。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-2&quot;&gt;公司项目迁移&lt;/h3&gt;
&lt;p&gt;既然体验这么好，作者就决定把公司内部的前端项目也都迁移到 &lt;code&gt;Vite+&lt;/code&gt; 上。迁移的过程中也让作者重新审视了一下 &lt;code&gt;rattail&lt;/code&gt;，我们在 &lt;code&gt;varlet&lt;/code&gt; 生态里积累了大量的工具函数、请求库、校验规则工厂、CLI 工具链，之前一直是分散在各个包里的，正好借这次机会做一次大整合，于是就有了 &lt;code&gt;rattail 2.0&lt;/code&gt;——一个面向 &lt;code&gt;Vite+&lt;/code&gt;、对 &lt;code&gt;AI Agent&lt;/code&gt; 非常友好的前端工具链。140+ 工具函数、渐进式请求库、链式校验规则工厂、CLI 工具链、类型安全枚举，&lt;code&gt;pnpm add rattail&lt;/code&gt; 一条命令全部拉齐。&lt;/p&gt;
&lt;p&gt;目前作者也在公司项目中全面使用了 &lt;code&gt;Vite+&lt;/code&gt; + &lt;code&gt;rattail&lt;/code&gt; 这套技术栈，体验下来非常舒服。另外值得一提的是，这次 &lt;code&gt;rattail 2.0&lt;/code&gt; 的迁移和开发过程中，作者大量使用了 &lt;code&gt;AI&lt;/code&gt; 辅助编程，包括工具函数的编写、单元测试的补全、文档的生成等等，效率提升非常明显。配合 &lt;code&gt;rattail&lt;/code&gt; 提供的 &lt;code&gt;Agent Skills&lt;/code&gt;，AI Agent 能够很好的理解项目上下文并正确使用 &lt;code&gt;rattail&lt;/code&gt; 的 API，整个工作流跑下来还是相当丝滑的。后续在业务开发中也明显感觉到，因为 &lt;code&gt;rattail&lt;/code&gt; 把工具函数、请求库、校验规则这些东西 all in one 了，AI 在生成代码的时候幻觉变得特别少，而且很会按照规范做事。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-3&quot;&gt;相关链接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;官方文档: &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Frattail.varletjs.org&quot; target=&quot;_blank&quot; title=&quot;https://rattail.varletjs.org&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;rattail.varletjs.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;仓库地址: &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail&quot; target=&quot;_blank&quot; title=&quot;https://github.com/varletjs/rattail&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github.com/varletjs/ra…&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-id=&quot;heading-4&quot;&gt;特性一览&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;⚙️ 面向 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fviteplus.dev&quot; target=&quot;_blank&quot; title=&quot;https://viteplus.dev&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;Vite+&lt;/a&gt; 的开箱即用配置预设&lt;/li&gt;
&lt;li&gt;🔧 CLI 工具链，支持发布、日志、Git Hooks、Commit Lint、API 生成&lt;/li&gt;
&lt;li&gt;🧰 140+ 工具函数，覆盖通用、字符串、数字、数组、对象、数学等场景&lt;/li&gt;
&lt;li&gt;🚀 基于 axios 的渐进式请求工具，支持 Vue 组合式 API&lt;/li&gt;
&lt;li&gt;📏 链式校验规则工厂，适配任意 UI 框架&lt;/li&gt;
&lt;li&gt;🏷️ 类型安全的枚举工具&lt;/li&gt;
&lt;li&gt;🤖 提供 Agent Skills，帮助 AI 编程助手理解和使用 Rattail&lt;/li&gt;
&lt;li&gt;🌲 可 Tree-shake，轻量，TypeScript 完整类型支持&lt;/li&gt;
&lt;li&gt;💪 90%+ 单元测试覆盖率&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面作者挑几个有意思的能力展开聊聊。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-5&quot;&gt;Vite+ 配置预设&lt;/h2&gt;
&lt;p&gt;做过前端工程化的同学应该都有体会，每次新项目光配 &lt;code&gt;eslint&lt;/code&gt; 和 &lt;code&gt;prettier&lt;/code&gt; 这些东西就够喝一壶的了，配好了还得处理各种冲突。&lt;code&gt;rattail&lt;/code&gt; 内置了面向 &lt;code&gt;Vite+&lt;/code&gt; 的开箱即用预设，一个 &lt;code&gt;vite.config.ts&lt;/code&gt; 搞定 &lt;code&gt;lint&lt;/code&gt;、&lt;code&gt;format&lt;/code&gt;、&lt;code&gt;staged&lt;/code&gt;、&lt;code&gt;git hooks&lt;/code&gt; 等所有工程化配置。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-typescript&quot; lang=&quot;typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { lint, fmt, staged, clean, hook, defineConfig } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;rattail/vite-plus&#39;&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;defineConfig&lt;/span&gt;({
  &lt;span class=&quot;hljs-attr&quot;&gt;lint&lt;/span&gt;: &lt;span class=&quot;hljs-title function_&quot;&gt;lint&lt;/span&gt;(),

  &lt;span class=&quot;hljs-attr&quot;&gt;fmt&lt;/span&gt;: &lt;span class=&quot;hljs-title function_&quot;&gt;fmt&lt;/span&gt;(),

  &lt;span class=&quot;hljs-attr&quot;&gt;staged&lt;/span&gt;: &lt;span class=&quot;hljs-title function_&quot;&gt;staged&lt;/span&gt;(),

  &lt;span class=&quot;hljs-attr&quot;&gt;rattail&lt;/span&gt;: {
    &lt;span class=&quot;hljs-attr&quot;&gt;clean&lt;/span&gt;: &lt;span class=&quot;hljs-title function_&quot;&gt;clean&lt;/span&gt;(),

    &lt;span class=&quot;hljs-attr&quot;&gt;hook&lt;/span&gt;: &lt;span class=&quot;hljs-title function_&quot;&gt;hook&lt;/span&gt;(),
    
    &lt;span class=&quot;hljs-attr&quot;&gt;api&lt;/span&gt;: {},

    &lt;span class=&quot;hljs-attr&quot;&gt;release&lt;/span&gt;: {},

    &lt;span class=&quot;hljs-attr&quot;&gt;changelog&lt;/span&gt;: {}
  },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之前作者为了配这些东西写了好几个配置文件，现在一个文件就够了（少写代码是第一生产力）。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-6&quot;&gt;CLI 工具链&lt;/h2&gt;
&lt;p&gt;安装 &lt;code&gt;rattail&lt;/code&gt; 后会注册一个 &lt;code&gt;rt&lt;/code&gt; 命令，覆盖了作者日常开发中最常用的几个场景。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-shell&quot; lang=&quot;shell&quot;&gt;&lt;span class=&quot;hljs-meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;bash&quot;&gt;清理产物&lt;/span&gt;
rt clean
&lt;span class=&quot;hljs-meta prompt_&quot;&gt;
# &lt;/span&gt;&lt;span class=&quot;bash&quot;&gt;安装 git hooks&lt;/span&gt;
rt hook
&lt;span class=&quot;hljs-meta prompt_&quot;&gt;
# &lt;/span&gt;&lt;span class=&quot;bash&quot;&gt;发布&lt;/span&gt;
rt release
&lt;span class=&quot;hljs-meta prompt_&quot;&gt;
# &lt;/span&gt;&lt;span class=&quot;bash&quot;&gt;生成 changelog&lt;/span&gt;
rt changelog
&lt;span class=&quot;hljs-meta prompt_&quot;&gt;
# &lt;/span&gt;&lt;span class=&quot;bash&quot;&gt;从 OpenAPI 生成 API 模块&lt;/span&gt;
rt api
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些命令都支持通过 &lt;code&gt;vite.config.ts&lt;/code&gt; 中的 &lt;code&gt;rattail&lt;/code&gt; 字段进行配置，也就是说项目根目录不需要再多出一堆 &lt;code&gt;.xxxrc&lt;/code&gt; 文件了。这一点作者是比较在意的，毕竟谁也不想打开项目根目录看到十几个配置文件吧（有些项目根目录比 &lt;code&gt;node_modules&lt;/code&gt; 还热闹）。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-7&quot;&gt;140+ 工具函数&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;lodash&lt;/code&gt; 大家都耳熟能详了，&lt;code&gt;rattail&lt;/code&gt; 里的工具函数覆盖的场景和 &lt;code&gt;lodash&lt;/code&gt; 类似，包括&lt;code&gt;类型判断&lt;/code&gt;、&lt;code&gt;数组&lt;/code&gt;、&lt;code&gt;对象&lt;/code&gt;、&lt;code&gt;字符串&lt;/code&gt;、&lt;code&gt;数学&lt;/code&gt;、&lt;code&gt;函数&lt;/code&gt;、&lt;code&gt;集合&lt;/code&gt;、&lt;code&gt;文件&lt;/code&gt;等分类，用法就不逐个列举了。和 &lt;code&gt;lodash&lt;/code&gt; 不同的是，这些函数从第一天就是用 &lt;code&gt;TypeScript&lt;/code&gt; 写的，类型推导是第一优先级，全部可 &lt;code&gt;Tree-shake&lt;/code&gt;。除了 &lt;code&gt;lodash&lt;/code&gt; 风格的工具函数以外，&lt;code&gt;rattail&lt;/code&gt; 还内置了一些前端项目中常用的实用工具，比如 &lt;code&gt;sumHash&lt;/code&gt; 计算哈希、&lt;code&gt;uuid&lt;/code&gt; 生成唯一 ID、&lt;code&gt;mitt&lt;/code&gt; 事件总线、&lt;code&gt;duration&lt;/code&gt; 时间格式化、&lt;code&gt;storage&lt;/code&gt; / &lt;code&gt;cookieStorage&lt;/code&gt; 存储封装、&lt;code&gt;copyText&lt;/code&gt; 复制文本、&lt;code&gt;download&lt;/code&gt; 文件下载等等，省得同学们每次都要单独装一堆小包。更多的可以去&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Frattail.varletjs.org&quot; target=&quot;_blank&quot; title=&quot;https://rattail.varletjs.org&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;文档&lt;/a&gt;里查看完整的 API 列表。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-8&quot;&gt;类型安全的枚举工具&lt;/h2&gt;
&lt;p&gt;这个是作者个人比较喜欢的一个工具。前端项目里到处都是枚举值，比如订单状态、用户角色之类的。一般我们用 &lt;code&gt;enum&lt;/code&gt; 或者常量对象来管理它们，但是 &lt;code&gt;label&lt;/code&gt;、&lt;code&gt;description&lt;/code&gt; 这些配套信息就只能另外维护了。&lt;code&gt;enumOf&lt;/code&gt; 把值和它的元信息放在一起管理，并且类型推导是完备的。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-typescript&quot; lang=&quot;typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { enumOf } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;rattail&#39;&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Status&lt;/span&gt; = &lt;span class=&quot;hljs-title function_&quot;&gt;enumOf&lt;/span&gt;({
  &lt;span class=&quot;hljs-title class_&quot;&gt;Pending&lt;/span&gt;: { &lt;span class=&quot;hljs-attr&quot;&gt;value&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;label&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;待处理&#39;&lt;/span&gt; },
  &lt;span class=&quot;hljs-title class_&quot;&gt;Active&lt;/span&gt;: { &lt;span class=&quot;hljs-attr&quot;&gt;value&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;label&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;进行中&#39;&lt;/span&gt; },
  &lt;span class=&quot;hljs-title class_&quot;&gt;Done&lt;/span&gt;: { &lt;span class=&quot;hljs-attr&quot;&gt;value&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;label&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;已完成&#39;&lt;/span&gt; },
})

&lt;span class=&quot;hljs-title class_&quot;&gt;Status&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;Pending&lt;/span&gt;        &lt;span class=&quot;hljs-comment&quot;&gt;// 0&lt;/span&gt;
&lt;span class=&quot;hljs-title class_&quot;&gt;Status&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;Active&lt;/span&gt;         &lt;span class=&quot;hljs-comment&quot;&gt;// 1&lt;/span&gt;
&lt;span class=&quot;hljs-title class_&quot;&gt;Status&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;values&lt;/span&gt;()       &lt;span class=&quot;hljs-comment&quot;&gt;// [0, 1, 2]&lt;/span&gt;
&lt;span class=&quot;hljs-title class_&quot;&gt;Status&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;labels&lt;/span&gt;()       &lt;span class=&quot;hljs-comment&quot;&gt;// [&#39;待处理&#39;, &#39;进行中&#39;, &#39;已完成&#39;]&lt;/span&gt;
&lt;span class=&quot;hljs-title class_&quot;&gt;Status&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;label&lt;/span&gt;(&lt;span class=&quot;hljs-title class_&quot;&gt;Status&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;Pending&lt;/span&gt;) &lt;span class=&quot;hljs-comment&quot;&gt;// &#39;待处理&#39;&lt;/span&gt;
&lt;span class=&quot;hljs-title class_&quot;&gt;Status&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;options&lt;/span&gt;()      &lt;span class=&quot;hljs-comment&quot;&gt;// [{ value: 0, label: &#39;待处理&#39; }, ...]&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;// 直接丢给 select 组件的 options，再也不用手动维护了&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前端项目里到处都需要枚举值和它对应的文案，以前每次都要写个 &lt;code&gt;map&lt;/code&gt; 或者 &lt;code&gt;switch&lt;/code&gt;，现在一个 &lt;code&gt;enumOf&lt;/code&gt; 就够了。另外 &lt;code&gt;enumOf&lt;/code&gt; 的 &lt;code&gt;label&lt;/code&gt; 和 &lt;code&gt;description&lt;/code&gt; 支持传入一个 getter 函数，配合 &lt;code&gt;vue-i18n&lt;/code&gt; 之类的国际化方案可以很方便的实现多语言：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-typescript&quot; lang=&quot;typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Status&lt;/span&gt; = &lt;span class=&quot;hljs-title function_&quot;&gt;enumOf&lt;/span&gt;({
  &lt;span class=&quot;hljs-title class_&quot;&gt;Pending&lt;/span&gt;: { &lt;span class=&quot;hljs-attr&quot;&gt;value&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;label&lt;/span&gt;: &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;t&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;status.pending&#39;&lt;/span&gt;) },
  &lt;span class=&quot;hljs-title class_&quot;&gt;Active&lt;/span&gt;: { &lt;span class=&quot;hljs-attr&quot;&gt;value&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;label&lt;/span&gt;: &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;t&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;status.active&#39;&lt;/span&gt;) },
  &lt;span class=&quot;hljs-title class_&quot;&gt;Done&lt;/span&gt;: { &lt;span class=&quot;hljs-attr&quot;&gt;value&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;label&lt;/span&gt;: &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;t&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;status.done&#39;&lt;/span&gt;) },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-id=&quot;heading-9&quot;&gt;基于 axios 的渐进式请求工具&lt;/h2&gt;
&lt;p&gt;这个能力来自于作者之前开源的 &lt;code&gt;@varlet/axle&lt;/code&gt;，现在通过 &lt;code&gt;rattail/axle&lt;/code&gt; 直接引入。熟悉作者的同学可能看过之前介绍 &lt;code&gt;axle&lt;/code&gt; 的文章，它在兼容 &lt;code&gt;axios&lt;/code&gt; 的同时，天然支持 &lt;code&gt;Vue3 Composition API&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-typescript&quot; lang=&quot;typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { createAxle } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;rattail/axle&#39;&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { createUseAxle } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;rattail/axle/use&#39;&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; axle = &lt;span class=&quot;hljs-title function_&quot;&gt;createAxle&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;baseURL&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;/api&#39;&lt;/span&gt; })
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; useAxle = &lt;span class=&quot;hljs-title function_&quot;&gt;createUseAxle&lt;/span&gt;({ axle })

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; [users, getUsers, { loading, error }] = &lt;span class=&quot;hljs-title function_&quot;&gt;useAxle&lt;/span&gt;({
  &lt;span class=&quot;hljs-attr&quot;&gt;method&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;get&#39;&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;url&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;/user&#39;&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;params&lt;/span&gt;: { &lt;span class=&quot;hljs-attr&quot;&gt;current&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;pageSize&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;10&lt;/span&gt; },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;作者一直觉得前端请求库和 &lt;code&gt;Vue&lt;/code&gt; 的响应式系统应该有更好的结合方式，&lt;code&gt;axle&lt;/code&gt; 就是在这个方向上的一个尝试。如果你不喜欢 &lt;code&gt;axle&lt;/code&gt; 也完全没问题，&lt;code&gt;rattail&lt;/code&gt; 的其他能力和请求库是解耦的，换成你喜欢的方案就好。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-10&quot;&gt;OpenAPI 生成 API 模块&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;rt api&lt;/code&gt; 可以直接解析后端提供的 &lt;code&gt;OpenAPI&lt;/code&gt; / &lt;code&gt;Swagger&lt;/code&gt; schema 文件，自动生成类型安全的 API 调用代码，这个在实际项目里把工作流做通之后体验可太好了。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;vite.config.ts&lt;/code&gt; 里配置好 schema 路径和输出目录：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-typescript&quot; lang=&quot;typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { defineConfig } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;rattail/vite-plus&#39;&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;defineConfig&lt;/span&gt;({
  &lt;span class=&quot;hljs-attr&quot;&gt;rattail&lt;/span&gt;: {
    &lt;span class=&quot;hljs-attr&quot;&gt;api&lt;/span&gt;: {
      &lt;span class=&quot;hljs-attr&quot;&gt;input&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;./openapi.json&#39;&lt;/span&gt;
    },
  },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行 &lt;code&gt;rt api&lt;/code&gt; 后会自动生成这样的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-typescript&quot; lang=&quot;typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { api } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;@/request&#39;&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { &lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; paths } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;./_types&#39;&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUsers&lt;/span&gt; = paths[&lt;span class=&quot;hljs-string&quot;&gt;&#39;/users&#39;&lt;/span&gt;][&lt;span class=&quot;hljs-string&quot;&gt;&#39;get&#39;&lt;/span&gt;]
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;ApiCreateUser&lt;/span&gt; = paths[&lt;span class=&quot;hljs-string&quot;&gt;&#39;/users&#39;&lt;/span&gt;][&lt;span class=&quot;hljs-string&quot;&gt;&#39;post&#39;&lt;/span&gt;]
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUser&lt;/span&gt; = paths[&lt;span class=&quot;hljs-string&quot;&gt;&#39;/users/{uuid}&#39;&lt;/span&gt;][&lt;span class=&quot;hljs-string&quot;&gt;&#39;get&#39;&lt;/span&gt;]
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;ApiUpdateUser&lt;/span&gt; = paths[&lt;span class=&quot;hljs-string&quot;&gt;&#39;/users/{uuid}&#39;&lt;/span&gt;][&lt;span class=&quot;hljs-string&quot;&gt;&#39;put&#39;&lt;/span&gt;]
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;ApiDeleteUser&lt;/span&gt; = paths[&lt;span class=&quot;hljs-string&quot;&gt;&#39;/users/{uuid}&#39;&lt;/span&gt;][&lt;span class=&quot;hljs-string&quot;&gt;&#39;delete&#39;&lt;/span&gt;]

&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUsersQuery&lt;/span&gt; = &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUsers&lt;/span&gt;[&lt;span class=&quot;hljs-string&quot;&gt;&#39;parameters&#39;&lt;/span&gt;][&lt;span class=&quot;hljs-string&quot;&gt;&#39;query&#39;&lt;/span&gt;]
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUsersRequestBody&lt;/span&gt; = &lt;span class=&quot;hljs-literal&quot;&gt;undefined&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUsersResponseBody&lt;/span&gt; = &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUsers&lt;/span&gt;[&lt;span class=&quot;hljs-string&quot;&gt;&#39;responses&#39;&lt;/span&gt;][&lt;span class=&quot;hljs-string&quot;&gt;&#39;200&#39;&lt;/span&gt;][&lt;span class=&quot;hljs-string&quot;&gt;&#39;content&#39;&lt;/span&gt;][&lt;span class=&quot;hljs-string&quot;&gt;&#39;application/json&#39;&lt;/span&gt;]
&lt;span class=&quot;hljs-comment&quot;&gt;// ... 其他类型同理&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; apiGetUsers = api&amp;lt;
  &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUsersResponseBody&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUsersQuery&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUsersRequestBody&lt;/span&gt;&amp;gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;/users&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;get&#39;&lt;/span&gt;)
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; apiCreateUser = api&amp;lt;
  &lt;span class=&quot;hljs-title class_&quot;&gt;ApiCreateUserResponseBody&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;ApiCreateUserQuery&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;ApiCreateUserRequestBody&lt;/span&gt;&amp;gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;/users&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;post&#39;&lt;/span&gt;)
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; apiGetUser = api&amp;lt;
  &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUserResponseBody&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUserQuery&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;ApiGetUserRequestBody&lt;/span&gt;&amp;gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;/users/:uuid&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;get&#39;&lt;/span&gt;)
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; apiUpdateUser = api&amp;lt;
  &lt;span class=&quot;hljs-title class_&quot;&gt;ApiUpdateUserResponseBody&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;ApiUpdateUserQuery&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;ApiUpdateUserRequestBody&lt;/span&gt;&amp;gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;/users/:uuid&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;put&#39;&lt;/span&gt;)
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; apiDeleteUser = api&amp;lt;
  &lt;span class=&quot;hljs-title class_&quot;&gt;ApiDeleteUserResponseBody&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;ApiDeleteUserQuery&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;ApiDeleteUserRequestBody&lt;/span&gt;&amp;gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;/users/:uuid&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;delete&#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请求类型、响应类型全部从 &lt;code&gt;schema&lt;/code&gt; 里提取，不需要手写。后端接口变了，重新跑一遍 &lt;code&gt;rt api&lt;/code&gt; 就行，前后端的类型始终保持同步。这个工作流对 AI Agent 也特别友好，AI 可以直接基于生成的类型去写业务代码，不会出现参数类型对不上的问题。甚至 AI Agent 可以通过 api 定义的变化，推测出你接下来要写什么业务。默认使用 &lt;code&gt;axle&lt;/code&gt;，也支持 &lt;code&gt;axios&lt;/code&gt; 的预设，同时支持 &lt;code&gt;自定义输出&lt;/code&gt;。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-11&quot;&gt;链式校验规则工厂&lt;/h2&gt;
&lt;p&gt;做表单的同学应该都写过类似 &lt;code&gt;required&lt;/code&gt;、&lt;code&gt;min&lt;/code&gt;、&lt;code&gt;max&lt;/code&gt; 这些校验规则。不同的 UI 框架校验规则的格式还不一样，每个项目都要适配一遍。&lt;code&gt;rattail&lt;/code&gt; 提供了一个链式校验规则工厂，写起来很流畅，并且可以适配任意 UI 框架。这种内联的声明式写法和 &lt;code&gt;TailwindCSS&lt;/code&gt; 的思路类似，可读性和可迁移性都非常好，对 AI 也特别友好，AI 可以直接从模板里读懂校验意图，生成和修改规则的准确度很高。&lt;/p&gt;
&lt;p&gt;以 &lt;code&gt;Naive UI&lt;/code&gt; 和 &lt;code&gt;Element Plus&lt;/code&gt; 为例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-html&quot; lang=&quot;html&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- Naive UI --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;setup&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;lang&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;ts&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;javascript&quot;&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; type { &lt;span class=&quot;hljs-title class_&quot;&gt;FormItemRule&lt;/span&gt; } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;naive-ui&#39;&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { rulerFactory } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;rattail/ruler&#39;&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; r = rulerFactory&amp;lt;&lt;span class=&quot;hljs-title class_&quot;&gt;FormItemRule&lt;/span&gt;&amp;gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;validator, params = {}&lt;/span&gt;) =&amp;gt;&lt;/span&gt; ({
  &lt;span class=&quot;hljs-attr&quot;&gt;trigger&lt;/span&gt;: [&lt;span class=&quot;hljs-string&quot;&gt;&#39;blur&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;change&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;input&#39;&lt;/span&gt;],
  &lt;span class=&quot;hljs-attr&quot;&gt;validator&lt;/span&gt;: &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;_, value&lt;/span&gt;) =&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;validator&lt;/span&gt;(value),
  ...params,
}))
&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;template&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;n-form&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;:model&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;n-form-item&lt;/span&gt; 
      &lt;span class=&quot;hljs-attr&quot;&gt;path&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt; 
      &lt;span class=&quot;hljs-attr&quot;&gt;label&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;姓名&quot;&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;:rule&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;r().required(&#39;必填&#39;).min(2, &#39;长度不正确&#39;).done()&quot;&lt;/span&gt;
    &amp;gt;&lt;/span&gt;
      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;n-input&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;v-model:value&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;model.name&quot;&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;n-form-item&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;n-form&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;template&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-html&quot; lang=&quot;html&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- Element Plus --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;setup&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;lang&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;ts&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;javascript&quot;&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; type { &lt;span class=&quot;hljs-title class_&quot;&gt;FormItemRule&lt;/span&gt; } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;element-plus&#39;&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { rulerFactory } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;rattail/ruler&#39;&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; r = rulerFactory&amp;lt;&lt;span class=&quot;hljs-title class_&quot;&gt;FormItemRule&lt;/span&gt;&amp;gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;validator, params&lt;/span&gt;) =&amp;gt;&lt;/span&gt; ({
  &lt;span class=&quot;hljs-title function_&quot;&gt;validator&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;_, value, callback&lt;/span&gt;) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; e = &lt;span class=&quot;hljs-title function_&quot;&gt;validator&lt;/span&gt;(value)
    e ? &lt;span class=&quot;hljs-title function_&quot;&gt;callback&lt;/span&gt;(e) : &lt;span class=&quot;hljs-title function_&quot;&gt;callback&lt;/span&gt;()
  },
  &lt;span class=&quot;hljs-attr&quot;&gt;trigger&lt;/span&gt;: [&lt;span class=&quot;hljs-string&quot;&gt;&#39;blur&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;change&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;input&#39;&lt;/span&gt;],
  ...params,
}))
&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;template&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;el-form&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;:model&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;el-form-item&lt;/span&gt; 
      &lt;span class=&quot;hljs-attr&quot;&gt;prop&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;email&quot;&lt;/span&gt; 
      &lt;span class=&quot;hljs-attr&quot;&gt;label&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;邮箱&quot;&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;:rules&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;r().email(&#39;必须是邮箱格式&#39;).done()&quot;&lt;/span&gt;
    &amp;gt;&lt;/span&gt;
      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;el-input&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;v-model&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;model.email&quot;&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;el-form-item&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;el-form&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;template&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-id=&quot;heading-12&quot;&gt;AI Agent Skills&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;rattail&lt;/code&gt; 提供了一套 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail%2Ftree%2Fmain%2Fskills&quot; target=&quot;_blank&quot; title=&quot;https://github.com/varletjs/rattail/tree/main/skills&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;Agent Skills&lt;/a&gt;，说白了就是给 AI 写了一份&quot;说明书&quot;，让 AI Agent 知道 &lt;code&gt;rattail&lt;/code&gt; 有哪些能力、怎么用，不用你每次都手动告诉 AI。作者觉得未来的开源库都应该考虑对 AI Agent 的友好度。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-13&quot;&gt;写在最后&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;rattail&lt;/code&gt; 的工具函数和能力大多来自前端社区的通用实践。感谢同学们能看到这里，但是希望 &lt;code&gt;rattail&lt;/code&gt; 能够帮助到大家。项目基于 &lt;code&gt;MIT&lt;/code&gt; 协议。如果在使用的过程中遇到任何问题，欢迎在 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail%2Fissues&quot; target=&quot;_blank&quot; title=&quot;https://github.com/varletjs/rattail/issues&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;issue&lt;/a&gt; 里反馈给我们，同时也十分欢迎对项目有兴趣的同学给我们发 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail%2Fpulls&quot; target=&quot;_blank&quot; title=&quot;https://github.com/varletjs/rattail/pulls&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;pull request&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;支持我们的话留下一个 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail%2Fstargazers&quot; target=&quot;_blank&quot; title=&quot;https://github.com/varletjs/rattail/stargazers&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;star&lt;/a&gt; 就好～&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;官方文档: &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Frattail.varletjs.org&quot; target=&quot;_blank&quot; title=&quot;https://rattail.varletjs.org&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;rattail.varletjs.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;仓库地址: &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail&quot; target=&quot;_blank&quot; title=&quot;https://github.com/varletjs/rattail&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github.com/varletjs/ra…&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><link>https://juejin.cn/post/7629261406287577124</link><guid isPermaLink="false">https://juejin.cn/post/7629261406287577124</guid><pubDate>Thu, 16 Apr 2026 16:17:40 GMT</pubDate><author>耗子君QAQ</author><category></category><category>前端</category><category>JavaScript</category><category>Vue.js</category></item><item><title>深入理解 JavaScript 的 AbortController：从底层原理到跨语言设计哲学</title><description>&lt;h2 data-id=&quot;heading-0&quot;&gt;引言&lt;/h2&gt;
&lt;p&gt;在目前的现代异步编程中，取消操作是一个看似简单却极其复杂的问题。JavaScript 的 &lt;code&gt;AbortController&lt;/code&gt; API 作为 Web 标准和 Node.js 环境中的统一解决方案，不只是解决了异步操作的可取消性难题，更体现了一种深刻的设计哲学：协作式取消（Cooperative Cancellation）。&lt;/p&gt;
&lt;p&gt;今天我们从底层原理出发，深入剖析 &lt;code&gt;AbortController&lt;/code&gt; 的工作机制，对比浏览器与 Node.js 的实现差异，并横向对比其他编程语言的中断机制设计，最终揭示这一 API 背后的语言特性与设计思想。那我们开始吧！&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-1&quot;&gt;第一部分：AbortController 的底层原理&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-2&quot;&gt;1.1 核心架构：信号-控制器分离模式&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;AbortController&lt;/code&gt; 的设计遵循信号-控制器分离模式（Signal-Controller Separation Pattern）。这种设计将&quot;控制&quot;与&quot;监听&quot;两个职责进行分离：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 核心架构示意&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt; {
  &lt;span class=&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class=&quot;hljs-comment&quot;&gt;// 控制器持有信号对象的引用&lt;/span&gt;
    &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt; = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortSignal&lt;/span&gt;();
  }

  &lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;reason&lt;/span&gt;) {
    &lt;span class=&quot;hljs-comment&quot;&gt;// 控制器触发信号的中止状态&lt;/span&gt;
    &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;_abort&lt;/span&gt;(reason);
  }
}

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortSignal&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;EventTarget&lt;/span&gt; {
  &lt;span class=&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class=&quot;hljs-variable language_&quot;&gt;super&lt;/span&gt;();
    &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;aborted&lt;/span&gt; = &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;
    &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;reason&lt;/span&gt; = &lt;span class=&quot;hljs-literal&quot;&gt;undefined&lt;/span&gt;;
  }

  &lt;span class=&quot;hljs-title function_&quot;&gt;_abort&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;reason&lt;/span&gt;) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (&lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;aborted&lt;/span&gt;) &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt;; &lt;span class=&quot;hljs-comment&quot;&gt;// 幂等性保证&lt;/span&gt;

    &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;aborted&lt;/span&gt; = &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;;
    &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;reason&lt;/span&gt; = reason ?? &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;DOMException&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Aborted&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;AbortError&quot;&lt;/span&gt;);

    &lt;span class=&quot;hljs-comment&quot;&gt;// 触发中止事件，通知所有监听器&lt;/span&gt;
    &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;dispatchEvent&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Event&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;abort&quot;&lt;/span&gt;));
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么这样设计？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;单一职责原则：控制器负责&quot;触发&quot;，信号负责&quot;传播&quot;。这种分离使得一个控制器可以控制多个信号，或者多个消费者可以共享同一个信号。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不可变性保证：&lt;code&gt;signal&lt;/code&gt; 对象一旦创建，其引用关系就固定下来。消费者只能监听信号，无法重新赋值或篡改控制器的状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;传播语义清晰：信号作为 &lt;code&gt;EventTarget&lt;/code&gt; 的子类，天然支持事件订阅机制，符合 JavaScript 的异步编程范式。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-id=&quot;heading-3&quot;&gt;1.2 事件驱动机制：从信号到执行中断&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;AbortSignal&lt;/code&gt; 继承自 &lt;code&gt;EventTarget&lt;/code&gt;，这意味着它使用事件驱动模型来传播取消信号。当调用 &lt;code&gt;controller.abort()&lt;/code&gt; 时，内部执行以下步骤：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/0086e49591214f53a7eb74a4e31126a4~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6ICB546L5Lul5Li6:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776960580&amp;amp;x-signature=hgy%2F2oSA2EIppQCW41%2FRBP4jSGA%3D&quot; alt=&quot;Image from Nlark&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;关键设计点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;幂等性：多次调用 &lt;code&gt;abort()&lt;/code&gt; 不会产生副作用，确保信号状态的一致性。&lt;/li&gt;
&lt;li&gt;同步触发：&lt;code&gt;abort()&lt;/code&gt; 的调用是同步的，事件处理也是同步执行的，这保证了取消信号的即时性。&lt;/li&gt;
&lt;li&gt;不可撤销：一旦信号被中止，就无法&quot;恢复&quot;，这符合&quot;取消&quot;的语义——取消是一个不可逆的操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-id=&quot;heading-4&quot;&gt;1.3 底层资源释放：从信号到系统调用&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;AbortController&lt;/code&gt; 的真正威力在于它能够触发底层资源的释放。以 &lt;code&gt;fetch&lt;/code&gt; 请求为例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; controller = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt;();
&lt;span class=&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;/api/data&quot;&lt;/span&gt;, { &lt;span class=&quot;hljs-attr&quot;&gt;signal&lt;/span&gt;: controller.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt; });

&lt;span class=&quot;hljs-comment&quot;&gt;// 触发取消&lt;/span&gt;
controller.&lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当 &lt;code&gt;abort()&lt;/code&gt; 被调用时，浏览器会执行以下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;TCP 连接中断：浏览器向服务器发送 RST（Reset）包，强制关闭 TCP 连接。这不是&quot;忽略响应&quot;，而是真正意义上的连接终止。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;资源回收：释放与该请求相关的内存缓冲区、文件描述符、事件监听器等资源。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Promise 拒绝：&lt;code&gt;fetch&lt;/code&gt; 返回的 Promise 被 reject，抛出 &lt;code&gt;AbortError&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/a0d86406daa64eb2a1290cbbdb4d1014~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6ICB546L5Lul5Li6:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776960580&amp;amp;x-signature=hLUW2cWIhHNBzjZwmQRZPR0HN0g%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;这种&lt;strong&gt;分层取消机制&lt;/strong&gt;确保了从应用层到系统层的完整资源释放，避免了内存泄漏和资源耗尽问题。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-5&quot;&gt;1.4 AbortSignal.any()：信号组合的设计智慧&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;AbortSignal.any()&lt;/code&gt; 是 AbortController API 的一个重要扩展，它允许将多个信号组合成一个 &quot;或&quot; 关系的新信号：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; timeoutSignal = &lt;span class=&quot;hljs-title class_&quot;&gt;AbortSignal&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;timeout&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;5000&lt;/span&gt;);
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; userCancelSignal = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt;().&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt;;

&lt;span class=&quot;hljs-comment&quot;&gt;// 任一信号触发，组合信号就触发&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; combinedSignal = &lt;span class=&quot;hljs-title class_&quot;&gt;AbortSignal&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;any&lt;/span&gt;([timeoutSignal, userCancelSignal]);

&lt;span class=&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;/api/data&quot;&lt;/span&gt;, { &lt;span class=&quot;hljs-attr&quot;&gt;signal&lt;/span&gt;: combinedSignal });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实现原理：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 简化版实现示意&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortSignal&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;any&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;signals&lt;/span&gt;) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; controller = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt;();

    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; signal &lt;span class=&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; signals) {
      &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (signal.&lt;span class=&quot;hljs-property&quot;&gt;aborted&lt;/span&gt;) {
        &lt;span class=&quot;hljs-comment&quot;&gt;// 如果任一信号已中止，立即触发&lt;/span&gt;
        controller.&lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;(signal.&lt;span class=&quot;hljs-property&quot;&gt;reason&lt;/span&gt;);
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; controller.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt;;
      }

      &lt;span class=&quot;hljs-comment&quot;&gt;// 监听每个信号的 abort 事件&lt;/span&gt;
      signal.&lt;span class=&quot;hljs-title function_&quot;&gt;addEventListener&lt;/span&gt;(
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;abort&quot;&lt;/span&gt;,
        &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
          controller.&lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;(signal.&lt;span class=&quot;hljs-property&quot;&gt;reason&lt;/span&gt;);
        },
        { &lt;span class=&quot;hljs-attr&quot;&gt;once&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt; },
      );
    }

    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; controller.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设计要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;竞态处理：如果传入的信号中已经有一个是 &lt;code&gt;aborted&lt;/code&gt; 状态，立即触发新信号的中止。&lt;/li&gt;
&lt;li&gt;原因传递：触发时传递原始信号的 &lt;code&gt;reason&lt;/code&gt;，保持错误信息的完整性。&lt;/li&gt;
&lt;li&gt;内存管理：使用 &lt;code&gt;{ once: true }&lt;/code&gt; 确保事件监听器在触发后自动清理，避免内存泄漏。&lt;/li&gt;
&lt;li&gt;WeakRef 优化：实际实现中使用 &lt;code&gt;WeakRef&lt;/code&gt; 和 &lt;code&gt;FinalizationRegistry&lt;/code&gt; 来管理信号之间的依赖关系，防止循环引用。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-6&quot;&gt;第二部分：Node.js 与 Web 实现的异同&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-7&quot;&gt;2.1 实现层面的差异&lt;/h3&gt;
&lt;p&gt;虽然 Node.js 的 &lt;code&gt;AbortController&lt;/code&gt; 遵循与浏览器相同的 WHATWG DOM 标准，但在底层实现上存在显著差异：&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;特性&lt;/th&gt;&lt;th&gt;浏览器（Blink/V8）&lt;/th&gt;&lt;th&gt;Node.js (libuv/V8)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;事件循环&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;基于渲染事件循环&lt;/td&gt;&lt;td&gt;基于 libuv 事件循环&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;网络层&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Chromium Network Stack&lt;/td&gt;&lt;td&gt;libuv + 系统调用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;信号传播&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;通过 Blink 的绑定层&lt;/td&gt;&lt;td&gt;通过 Node.js 的 C++ 绑定&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;文件系统&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;受限的 File System Access API&lt;/td&gt;&lt;td&gt;完整的 fs 模块支持&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;子进程&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;不支持&lt;/td&gt;&lt;td&gt;支持 &lt;code&gt;child_process&lt;/code&gt; 模块&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Worker 线程&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Web Workers&lt;/td&gt;&lt;td&gt;Worker Threads&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 data-id=&quot;heading-8&quot;&gt;2.2 Node.js 特有的扩展&lt;/h3&gt;
&lt;p&gt;Node.js 对 &lt;code&gt;AbortController&lt;/code&gt; 进行了多项扩展，使其更适用于服务端场景：&lt;/p&gt;
&lt;h4 data-id=&quot;heading-9&quot;&gt;2.2.1 定时器支持&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { &lt;span class=&quot;hljs-built_in&quot;&gt;setTimeout&lt;/span&gt; } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;node:timers/promises&quot;&lt;/span&gt;;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; controller = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt;();

&lt;span class=&quot;hljs-built_in&quot;&gt;setTimeout&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;1000&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;value&quot;&lt;/span&gt;, { &lt;span class=&quot;hljs-attr&quot;&gt;signal&lt;/span&gt;: controller.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt; })
  .&lt;span class=&quot;hljs-title function_&quot;&gt;then&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;value&lt;/span&gt;) =&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(value))
  .&lt;span class=&quot;hljs-title function_&quot;&gt;catch&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;err&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (err.&lt;span class=&quot;hljs-property&quot;&gt;name&lt;/span&gt; === &lt;span class=&quot;hljs-string&quot;&gt;&quot;AbortError&quot;&lt;/span&gt;) {
      &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Timer aborted&quot;&lt;/span&gt;);
    }
  });

&lt;span class=&quot;hljs-comment&quot;&gt;// 5秒后取消&lt;/span&gt;
&lt;span class=&quot;hljs-built_in&quot;&gt;setTimeout&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; controller.&lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;(), &lt;span class=&quot;hljs-number&quot;&gt;500&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;底层实现：Node.js 的定时器模块内部维护了一个 &lt;code&gt;AbortSignal&lt;/code&gt; 到定时器句柄的映射。当信号触发时，调用 &lt;code&gt;clearTimeout()&lt;/code&gt; 清除定时器。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-10&quot;&gt;2.2.2 文件系统操作&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { readFile } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;node:fs&quot;&lt;/span&gt;;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; controller = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt;();

&lt;span class=&quot;hljs-title function_&quot;&gt;readFile&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;/path/to/file&quot;&lt;/span&gt;, { &lt;span class=&quot;hljs-attr&quot;&gt;signal&lt;/span&gt;: controller.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt; }, &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;err, data&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (err?.&lt;span class=&quot;hljs-property&quot;&gt;name&lt;/span&gt; === &lt;span class=&quot;hljs-string&quot;&gt;&quot;AbortError&quot;&lt;/span&gt;) {
    &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Read aborted&quot;&lt;/span&gt;);
  }
});

&lt;span class=&quot;hljs-comment&quot;&gt;// 取消读取&lt;/span&gt;
controller.&lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重要限制：根据 Node.js 文档，文件系统的取消不会中止底层的操作系统请求，而只是中止 Node.js 内部的缓冲操作。这意味着：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/e0b0de937279427eb4db820234bf02d3~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6ICB546L5Lul5Li6:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776960580&amp;amp;x-signature=MoFVDwWFrLOBRVQxzOh2suL32go%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;这与浏览器中 &lt;code&gt;fetch&lt;/code&gt; 的取消（可以终止 TCP 连接）有本质区别，反映了服务端 I/O 与客户端网络请求的不同特性。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-11&quot;&gt;2.2.3 子进程控制&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { spawn } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;node:child_process&quot;&lt;/span&gt;;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; controller = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt;();

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; child = &lt;span class=&quot;hljs-title function_&quot;&gt;spawn&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;node&quot;&lt;/span&gt;, [&lt;span class=&quot;hljs-string&quot;&gt;&quot;script.js&quot;&lt;/span&gt;], {
  &lt;span class=&quot;hljs-attr&quot;&gt;signal&lt;/span&gt;: controller.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt;,
});

child.&lt;span class=&quot;hljs-title function_&quot;&gt;on&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;error&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;err&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (err.&lt;span class=&quot;hljs-property&quot;&gt;name&lt;/span&gt; === &lt;span class=&quot;hljs-string&quot;&gt;&quot;AbortError&quot;&lt;/span&gt;) {
    &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Child process aborted&quot;&lt;/span&gt;);
  }
});

&lt;span class=&quot;hljs-comment&quot;&gt;// 终止子进程&lt;/span&gt;
controller.&lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实现机制：Node.js 在子进程模块中监听 &lt;code&gt;AbortSignal&lt;/code&gt; 的 &lt;code&gt;abort&lt;/code&gt; 事件，触发时向子进程发送 &lt;code&gt;SIGTERM&lt;/code&gt; 信号。如果子进程未在超时内退出，则发送 &lt;code&gt;SIGKILL&lt;/code&gt; 强制终止。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-12&quot;&gt;2.3 行为一致性与边界情况&lt;/h3&gt;
&lt;h4 data-id=&quot;heading-13&quot;&gt;2.3.1 事件触发时序&lt;/h4&gt;
&lt;p&gt;浏览器和 Node.js 在事件触发时序上保持一致：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; controller = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt;();
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; signal = controller.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt;;

&lt;span class=&quot;hljs-comment&quot;&gt;// 注册多个监听器&lt;/span&gt;
signal.&lt;span class=&quot;hljs-title function_&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;abort&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Listener 1&quot;&lt;/span&gt;));
signal.&lt;span class=&quot;hljs-title function_&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;abort&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Listener 2&quot;&lt;/span&gt;));

controller.&lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;();
&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;After abort&quot;&lt;/span&gt;);

&lt;span class=&quot;hljs-comment&quot;&gt;// 输出顺序：&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;// Listener 1&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;// Listener 2&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;// After abort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;事件监听器是同步执行的，这保证了取消操作的即时性。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-14&quot;&gt;2.3.2 已完成的操作&lt;/h4&gt;
&lt;p&gt;如果操作已经完成，取消信号会被忽略：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; controller = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt;();

&lt;span class=&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;/api/data&quot;&lt;/span&gt;, { &lt;span class=&quot;hljs-attr&quot;&gt;signal&lt;/span&gt;: controller.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt; }).&lt;span class=&quot;hljs-title function_&quot;&gt;then&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;response&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Request completed&quot;&lt;/span&gt;);
});

&lt;span class=&quot;hljs-comment&quot;&gt;// 延迟触发取消（假设请求已经完成）&lt;/span&gt;
&lt;span class=&quot;hljs-built_in&quot;&gt;setTimeout&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
  controller.&lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;(); &lt;span class=&quot;hljs-comment&quot;&gt;// 不会产生任何效果&lt;/span&gt;
}, &lt;span class=&quot;hljs-number&quot;&gt;1000&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种行为是协作式取消的核心体现：消费者决定如何响应取消信号，包括选择忽略它。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-15&quot;&gt;第三部分：跨语言对比——中断机制的设计哲学&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-16&quot;&gt;3.1 协作式取消 vs 抢占式取消&lt;/h3&gt;
&lt;p&gt;不同编程语言对&quot;取消操作&quot;的设计哲学可以分为两大类：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/ac9eeea2996e4dcaaff3081d7553c63e~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6ICB546L5Lul5Li6:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776960580&amp;amp;x-signature=wPY3vi37DKBhZMAuihUVip%2Bksuo%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;h3 data-id=&quot;heading-17&quot;&gt;3.2 Go：Context 模式&lt;/h3&gt;
&lt;p&gt;Go 语言的 &lt;code&gt;context&lt;/code&gt; 包提供了与 JavaScript &lt;code&gt;AbortController&lt;/code&gt; 类似的协作式取消机制：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-go&quot; lang=&quot;go&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// Go 的 Context 模式&lt;/span&gt;
ctx, cancel := context.WithCancel(context.Background())
&lt;span class=&quot;hljs-keyword&quot;&gt;defer&lt;/span&gt; cancel()

&lt;span class=&quot;hljs-comment&quot;&gt;// 启动 goroutine&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;go&lt;/span&gt; &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;hljs-params&quot;&gt;(ctx context.Context)&lt;/span&gt;&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;select&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;case&lt;/span&gt; &amp;lt;-ctx.Done():
        &lt;span class=&quot;hljs-comment&quot;&gt;// 收到取消信号&lt;/span&gt;
        fmt.Println(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Cancelled:&quot;&lt;/span&gt;, ctx.Err())
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;case&lt;/span&gt; &amp;lt;-time.After(&lt;span class=&quot;hljs-number&quot;&gt;5&lt;/span&gt; * time.Second):
        fmt.Println(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Work completed&quot;&lt;/span&gt;)
    }
}(ctx)

&lt;span class=&quot;hljs-comment&quot;&gt;// 触发取消&lt;/span&gt;
cancel()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;与 JavaScript 的对比&lt;/strong&gt;：&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;特性&lt;/th&gt;&lt;th&gt;Go Context&lt;/th&gt;&lt;th&gt;JavaScript AbortController&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;信号类型&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Channel（&lt;code&gt;&amp;lt;-ctx.Done()&lt;/code&gt;）&lt;/td&gt;&lt;td&gt;Event（&lt;code&gt;addEventListener&lt;/code&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;传播方式&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;显式传递 &lt;code&gt;ctx&lt;/code&gt; 参数&lt;/td&gt;&lt;td&gt;通过 &lt;code&gt;signal&lt;/code&gt; 属性传递&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;超时支持&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;context.WithTimeout()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;AbortSignal.timeout()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;值传递&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;支持 &lt;code&gt;ctx.Value()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;不支持（专用设计）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;组合能力&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;可以嵌套传递&lt;/td&gt;&lt;td&gt;&lt;code&gt;AbortSignal.any()&lt;/code&gt; 组合&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;设计差异分析&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;Go 的 &lt;code&gt;context&lt;/code&gt; 不仅是取消信号，还承担了&lt;strong&gt;请求作用域数据传递&lt;/strong&gt;的职责（通过 &lt;code&gt;ctx.Value()&lt;/code&gt;）。这种设计在微服务架构中非常有用，可以传递请求 ID、用户信息等。JavaScript 的 &lt;code&gt;AbortController&lt;/code&gt; 则专注于单一职责：取消信号传递。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-18&quot;&gt;3.3 C#：CancellationToken 模式&lt;/h3&gt;
&lt;p&gt;.NET 的 &lt;code&gt;CancellationToken&lt;/code&gt; 是一个成熟的协作式取消机制：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-csharp&quot; lang=&quot;csharp&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// C# 的 CancellationToken 模式&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; cts = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; CancellationTokenSource();
CancellationToken token = cts.Token;

&lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; Task.Run(&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; () =&amp;gt; {
        &lt;span class=&quot;hljs-keyword&quot;&gt;while&lt;/span&gt; (!token.IsCancellationRequested) {
            &lt;span class=&quot;hljs-comment&quot;&gt;// 执行任务&lt;/span&gt;
            &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; Task.Delay(&lt;span class=&quot;hljs-number&quot;&gt;100&lt;/span&gt;);
        }
    }, token);
} &lt;span class=&quot;hljs-keyword&quot;&gt;catch&lt;/span&gt; (OperationCanceledException) {
    Console.WriteLine(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Operation cancelled&quot;&lt;/span&gt;);
}

&lt;span class=&quot;hljs-comment&quot;&gt;// 触发取消&lt;/span&gt;
cts.Cancel();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键特性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;轮询与回调双模式：既可以通过 &lt;code&gt;IsCancellationRequested&lt;/code&gt; 属性轮询，也可以通过 &lt;code&gt;Register()&lt;/code&gt; 方法注册回调。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;链接令牌：&lt;code&gt;CreateLinkedTokenSource()&lt;/code&gt; 可以将多个令牌链接成一个，任一令牌取消都会触发整体取消。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;异常类型：取消时抛出 &lt;code&gt;OperationCanceledException&lt;/code&gt;，与 JavaScript 的 &lt;code&gt;AbortError&lt;/code&gt; 对应。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;与 JavaScript 的对比：&lt;/p&gt;
&lt;hr&gt;
&lt;h4 data-id=&quot;heading-19&quot;&gt;⚖️ 核心差异对照表&lt;/h4&gt;























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;对比维度&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;C# &lt;code&gt;CancellationToken&lt;/code&gt;&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;JS &lt;code&gt;AbortSignal&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;类型系统&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;code&gt;struct&lt;/code&gt;（值类型）&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;code&gt;class&lt;/code&gt;（引用类型）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;传递语义&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;按值复制（快照式）&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;按引用共享（同一实例）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;取消检测&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;轮询 &lt;code&gt;.IsCancellationRequested&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;监听 &lt;code&gt;&#39;abort&#39;&lt;/code&gt; 事件&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;异常类型&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;code&gt;OperationCanceledException&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;code&gt;DOMException(&quot;AbortError&quot;)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;资源释放&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;需手动 &lt;code&gt;.Dispose()&lt;/code&gt; CTS&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;GC 自动回收&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;超时内置&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;code&gt;cts.CancelAfter()&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;code&gt;AbortSignal.timeout()&lt;/code&gt; (ES2024)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;多信号合并&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;code&gt;CreateLinkedTokenSource()&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;code&gt;AbortSignal.any()&lt;/code&gt; (ES2024)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;与 fetch 集成&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;❌ 不适用&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅ 原生支持&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;与 async/await&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅ 原生支持&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅ 原生支持&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-20&quot;&gt;3.4 Java：Future.cancel() 与线程中断&lt;/h3&gt;
&lt;p&gt;Java 提供了两种取消机制：&lt;/p&gt;
&lt;h4 data-id=&quot;heading-21&quot;&gt;3.4.1 Future.cancel()（协作式）&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-java&quot; lang=&quot;java&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;ExecutorService&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;executor&lt;/span&gt; &lt;span class=&quot;hljs-operator&quot;&gt;=&lt;/span&gt; Executors.newSingleThreadExecutor();
Future&amp;lt;?&amp;gt; future = executor.submit(() -&amp;gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;while&lt;/span&gt; (!Thread.currentThread().isInterrupted()) {
        &lt;span class=&quot;hljs-comment&quot;&gt;// 执行任务&lt;/span&gt;
    }
});

&lt;span class=&quot;hljs-comment&quot;&gt;// 尝试取消&lt;/span&gt;
future.cancel(&lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// true = 允许中断运行中的线程&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-id=&quot;heading-22&quot;&gt;3.4.2 线程中断（抢占式）&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-java&quot; lang=&quot;java&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Thread&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;workerThread&lt;/span&gt; &lt;span class=&quot;hljs-operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Thread&lt;/span&gt;(() -&amp;gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt; {
        Thread.sleep(&lt;span class=&quot;hljs-number&quot;&gt;10000&lt;/span&gt;);
    } &lt;span class=&quot;hljs-keyword&quot;&gt;catch&lt;/span&gt; (InterruptedException e) {
        &lt;span class=&quot;hljs-comment&quot;&gt;// 收到中断信号&lt;/span&gt;
        Thread.currentThread().interrupt(); &lt;span class=&quot;hljs-comment&quot;&gt;// 重新设置中断标志&lt;/span&gt;
    }
});

workerThread.start();
workerThread.interrupt(); &lt;span class=&quot;hljs-comment&quot;&gt;// 发送中断信号&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键区别&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;Java 的 &lt;code&gt;Thread.interrupt()&lt;/code&gt; 并不会强制停止线程，而是设置一个&lt;strong&gt;中断标志&lt;/strong&gt;。线程需要主动检查这个标志（通过 &lt;code&gt;isInterrupted()&lt;/code&gt;）或在可中断的阻塞操作（如 &lt;code&gt;sleep()&lt;/code&gt;, &lt;code&gt;wait()&lt;/code&gt;）中捕获 &lt;code&gt;InterruptedException&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这与 JavaScript 的 &lt;code&gt;AbortController&lt;/code&gt; 非常相似，都是&lt;strong&gt;协作式&lt;/strong&gt;的。但 Java 还保留了 &lt;code&gt;Thread.stop()&lt;/code&gt;（已废弃）这样的抢占式方法，反映了早期 Java 设计中对抢占式取消的探索。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-23&quot;&gt;3.5 Kotlin：协程的取消机制&lt;/h3&gt;
&lt;p&gt;Kotlin 协程的取消是结构化并发（Structured Concurrency）的核心特性：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-kotlin&quot; lang=&quot;kotlin&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; kotlinx.coroutines.*

&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;hljs-params&quot;&gt;()&lt;/span&gt;&lt;/span&gt; = runBlocking {
    &lt;span class=&quot;hljs-keyword&quot;&gt;val&lt;/span&gt; job = launch {
        &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt; {
            repeat(&lt;span class=&quot;hljs-number&quot;&gt;1000&lt;/span&gt;) { i -&amp;gt;
                println(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Job: I&#39;m working &lt;span class=&quot;hljs-variable&quot;&gt;$i&lt;/span&gt; ...&quot;&lt;/span&gt;)
                delay(&lt;span class=&quot;hljs-number&quot;&gt;500L&lt;/span&gt;)
            }
        } &lt;span class=&quot;hljs-keyword&quot;&gt;finally&lt;/span&gt; {
            &lt;span class=&quot;hljs-comment&quot;&gt;// 清理资源&lt;/span&gt;
            println(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Job: I&#39;m running finally&quot;&lt;/span&gt;)
        }
    }

    delay(&lt;span class=&quot;hljs-number&quot;&gt;1300L&lt;/span&gt;)
    println(&lt;span class=&quot;hljs-string&quot;&gt;&quot;main: I&#39;m tired of waiting!&quot;&lt;/span&gt;)
    job.cancelAndJoin() &lt;span class=&quot;hljs-comment&quot;&gt;// 取消并等待完成&lt;/span&gt;
    println(&lt;span class=&quot;hljs-string&quot;&gt;&quot;main: Now I can quit.&quot;&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键特性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;挂起点的取消检查：Kotlin 协程只在挂起点（suspension points）检查取消状态。如果协程处于 CPU 密集型计算中，不会立即响应取消。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;异常传播：取消时抛出 &lt;code&gt;CancellationException&lt;/code&gt;，这是一种特殊的异常，不会被视为错误。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;父子关系：子协程的取消会传播给所有子协程，形成树状的取消传播。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;与 JavaScript 的对比：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/10067aa2c35748ac95e298a4c1b52222~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6ICB546L5Lul5Li6:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776960580&amp;amp;x-signature=LjALiv5axRjDRUtOAr1SeQHgKGo%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;h3 data-id=&quot;heading-24&quot;&gt;3.6 Python：asyncio.Task 的取消&lt;/h3&gt;
&lt;p&gt;Python 的 &lt;code&gt;asyncio&lt;/code&gt; 提供了任务取消机制：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; asyncio

&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;worker&lt;/span&gt;():
    &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt;:
        &lt;span class=&quot;hljs-keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;:
            &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Working...&quot;&lt;/span&gt;)
            &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; asyncio.sleep(&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;)
    &lt;span class=&quot;hljs-keyword&quot;&gt;except&lt;/span&gt; asyncio.CancelledError:
        &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Cancelled!&quot;&lt;/span&gt;)
        &lt;span class=&quot;hljs-keyword&quot;&gt;raise&lt;/span&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;# 必须重新抛出&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;main&lt;/span&gt;():
    task = asyncio.create_task(worker())
    &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; asyncio.sleep(&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;)
    task.cancel()

    &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt;:
        &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; task
    &lt;span class=&quot;hljs-keyword&quot;&gt;except&lt;/span&gt; asyncio.CancelledError:
        &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Task cancelled&quot;&lt;/span&gt;)

asyncio.run(main())
&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;p&gt;&lt;strong&gt;异常驱动&lt;/strong&gt;：取消通过抛出 &lt;code&gt;CancelledError&lt;/code&gt; 实现，任务需要捕获并重新抛出。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;异步清理&lt;/strong&gt;：&lt;code&gt;finally&lt;/code&gt; 块中可以执行异步清理操作（使用 &lt;code&gt;async&lt;/code&gt; 语法）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;取消传播&lt;/strong&gt;：父任务取消时，子任务会自动收到取消信号。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;与 JavaScript 的对比&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;Python 的 &lt;code&gt;asyncio.CancelledError&lt;/code&gt; 与 JavaScript 的 &lt;code&gt;AbortError&lt;/code&gt; 类似，都是异常驱动的取消机制。但 Python 的取消更依赖异常传播，而 JavaScript 更依赖事件监听。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-25&quot;&gt;3.7 Rust：异步取消与 Drop 语义&lt;/h3&gt;
&lt;p&gt;Rust 的异步取消机制与众不同，它利用了所有权和 &lt;code&gt;Drop&lt;/code&gt; trait：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-rust&quot; lang=&quot;rust&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;use&lt;/span&gt; tokio::time::{sleep, Duration};

&lt;span class=&quot;hljs-meta&quot;&gt;#[tokio::main]&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;main&lt;/span&gt;() {
    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hljs-variable&quot;&gt;handle&lt;/span&gt; = tokio::&lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;spawn&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; {
        &lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;sleep&lt;/span&gt;(Duration::&lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;from_secs&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;5&lt;/span&gt;)).&lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt;;
        &lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;println&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Task completed&quot;&lt;/span&gt;);
    });

    &lt;span class=&quot;hljs-comment&quot;&gt;// 取消任务&lt;/span&gt;
    handle.&lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;abort&lt;/span&gt;();

    &lt;span class=&quot;hljs-keyword&quot;&gt;match&lt;/span&gt; handle.&lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; {
        &lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;Ok&lt;/span&gt;(_) =&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;println!&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Task finished normally&quot;&lt;/span&gt;),
        &lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;Err&lt;/span&gt;(e) &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; e.&lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;is_cancelled&lt;/span&gt;() =&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;println!&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Task was cancelled&quot;&lt;/span&gt;),
        &lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;Err&lt;/span&gt;(e) =&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;println!&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Task panicked: {:?}&quot;&lt;/span&gt;, e),
    }
}
&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;p&gt;&lt;strong&gt;Future 的 Drop&lt;/strong&gt;：在 Rust 中，当一个 &lt;code&gt;Future&lt;/code&gt;（异步任务）被 drop（丢弃）时，任务就被取消了。这是通过所有权系统实现的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;取消安全性（Cancel Safety）&lt;/strong&gt;：Rust 强调&quot;取消安全性&quot;，即任务在被取消时不会留下不一致的状态。这通常要求使用特定的模式（如 &lt;code&gt;select!&lt;/code&gt; 宏）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Async Drop&lt;/strong&gt;：Rust 正在讨论引入 &lt;code&gt;AsyncDrop&lt;/code&gt; trait，允许在 drop 时执行异步清理操作。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;与 JavaScript 的对比&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/8808669de3a740109bdbcf5e0b7ba812~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6ICB546L5Lul5Li6:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776960580&amp;amp;x-signature=BJ8oDZDOgo4rZMIJYK%2F%2B0n37S6M%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-26&quot;&gt;第四部分：设计哲学与最佳实践&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-27&quot;&gt;4.1 为什么协作式取消是主流？&lt;/h3&gt;
&lt;p&gt;从上述跨语言对比可以看出，&lt;strong&gt;协作式取消&lt;/strong&gt;已成为现代异步编程的主流设计。原因如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;资源安全&lt;/strong&gt;：协作式取消允许任务在退出前执行清理操作（关闭文件、释放锁、回滚事务等），避免资源泄漏。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;状态一致性&lt;/strong&gt;：任务可以在安全点（挂起点或检查点）响应取消，确保数据结构处于一致状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;可预测性&lt;/strong&gt;：取消的时机和行为是确定的，不会出现抢占式取消的&quot;任意点中断&quot;问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;组合性&lt;/strong&gt;：多个取消信号可以组合（如 &lt;code&gt;AbortSignal.any()&lt;/code&gt;），形成复杂的取消策略。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-id=&quot;heading-28&quot;&gt;4.2 AbortController 的设计原则总结&lt;/h3&gt;
&lt;p&gt;根据 WHATWG DOM 规范和各实现的设计文档，&lt;code&gt;AbortController&lt;/code&gt; 遵循以下核心原则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;分离原则（Separation）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;控制器（Controller）负责触发&lt;/li&gt;
&lt;li&gt;信号（Signal）负责传播&lt;/li&gt;
&lt;li&gt;消费者（Consumer）决定如何响应&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;幂等性原则（Idempotency）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多次调用 abort() 无副作用&lt;/li&gt;
&lt;li&gt;信号一旦中止，状态不可变&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;即时性原则（Immediacy）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;abort() 调用是同步的&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;p&gt;不可撤销原则（Irreversibility）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;取消是不可逆的操作&lt;/li&gt;
&lt;li&gt;信号不能&quot;恢复&quot;或&quot;重置&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;组合性原则（Composability）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支持多个信号的组合（any, race）&lt;/li&gt;
&lt;li&gt;支持信号链的传播（dependent signals）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;资源安全原则（Resource Safety）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提供清理算法的注册机制&lt;/li&gt;
&lt;li&gt;支持自动解订阅（unsubscription）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-id=&quot;heading-29&quot;&gt;4.3 实际应用中的最佳实践&lt;/h3&gt;
&lt;h4 data-id=&quot;heading-30&quot;&gt;4.3.1 始终传递 Signal&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// ✅ 好的实践：函数接受 signal 参数&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;fetchData&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;url, options = {}&lt;/span&gt;) {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { signal } = options;

  &lt;span class=&quot;hljs-comment&quot;&gt;// 立即检查&lt;/span&gt;
  signal?.&lt;span class=&quot;hljs-title function_&quot;&gt;throwIfAborted&lt;/span&gt;();

  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; response = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(url, { signal });

  &lt;span class=&quot;hljs-comment&quot;&gt;// 中间检查&lt;/span&gt;
  signal?.&lt;span class=&quot;hljs-title function_&quot;&gt;throwIfAborted&lt;/span&gt;();

  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; response.&lt;span class=&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;();
}

&lt;span class=&quot;hljs-comment&quot;&gt;// ❌ 不好的实践：忽略 signal&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;fetchDataBad&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;url&lt;/span&gt;) {
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(url).&lt;span class=&quot;hljs-title function_&quot;&gt;then&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;r&lt;/span&gt;) =&amp;gt;&lt;/span&gt; r.&lt;span class=&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;()); &lt;span class=&quot;hljs-comment&quot;&gt;// 无法取消&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-id=&quot;heading-31&quot;&gt;4.3.2 正确清理事件监听器&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;someOperation&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;signal&lt;/span&gt;) {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; cleanup = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt;();

  &lt;span class=&quot;hljs-comment&quot;&gt;// 使用嵌套 signal 确保清理&lt;/span&gt;
  signal?.&lt;span class=&quot;hljs-title function_&quot;&gt;addEventListener&lt;/span&gt;(
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;abort&quot;&lt;/span&gt;,
    &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
      cleanup.&lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;();
    },
    { &lt;span class=&quot;hljs-attr&quot;&gt;once&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt; },
  );

  &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;doWork&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;signal&lt;/span&gt;: cleanup.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt; });
  } &lt;span class=&quot;hljs-keyword&quot;&gt;finally&lt;/span&gt; {
    &lt;span class=&quot;hljs-comment&quot;&gt;// 确保清理&lt;/span&gt;
    cleanup.&lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-id=&quot;heading-32&quot;&gt;4.3.3 区分取消错误与其他错误&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;robustFetch&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;url, signal&lt;/span&gt;) {
  &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(url, { signal });
  } &lt;span class=&quot;hljs-keyword&quot;&gt;catch&lt;/span&gt; (error) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (error.&lt;span class=&quot;hljs-property&quot;&gt;name&lt;/span&gt; === &lt;span class=&quot;hljs-string&quot;&gt;&quot;AbortError&quot;&lt;/span&gt;) {
      &lt;span class=&quot;hljs-comment&quot;&gt;// 取消是预期的行为，不需要上报&lt;/span&gt;
      &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Request cancelled&quot;&lt;/span&gt;);
      &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;;
    }
    &lt;span class=&quot;hljs-comment&quot;&gt;// 其他错误需要处理&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;throw&lt;/span&gt; error;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-id=&quot;heading-33&quot;&gt;4.3.4 使用 AbortSignal.timeout() 设置超时&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// ✅ 推荐：使用内置的超时信号&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; signal = &lt;span class=&quot;hljs-title class_&quot;&gt;AbortSignal&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;timeout&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;5000&lt;/span&gt;);

&lt;span class=&quot;hljs-comment&quot;&gt;// ❌ 不推荐：手动实现&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; controller = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt;();
&lt;span class=&quot;hljs-built_in&quot;&gt;setTimeout&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; controller.&lt;span class=&quot;hljs-title function_&quot;&gt;abort&lt;/span&gt;(), &lt;span class=&quot;hljs-number&quot;&gt;5000&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-id=&quot;heading-34&quot;&gt;4.3.5 组合多个取消条件&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 组合用户取消和超时&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; userController = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;AbortController&lt;/span&gt;();
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; timeoutSignal = &lt;span class=&quot;hljs-title class_&quot;&gt;AbortSignal&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;timeout&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;10000&lt;/span&gt;);

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; combinedSignal = &lt;span class=&quot;hljs-title class_&quot;&gt;AbortSignal&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;any&lt;/span&gt;([userController.&lt;span class=&quot;hljs-property&quot;&gt;signal&lt;/span&gt;, timeoutSignal]);

&lt;span class=&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;/api/data&quot;&lt;/span&gt;, { &lt;span class=&quot;hljs-attr&quot;&gt;signal&lt;/span&gt;: combinedSignal }).&lt;span class=&quot;hljs-title function_&quot;&gt;catch&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;err&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (err.&lt;span class=&quot;hljs-property&quot;&gt;name&lt;/span&gt; === &lt;span class=&quot;hljs-string&quot;&gt;&quot;AbortError&quot;&lt;/span&gt;) {
    &lt;span class=&quot;hljs-comment&quot;&gt;// 判断是哪种取消&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (timeoutSignal.&lt;span class=&quot;hljs-property&quot;&gt;aborted&lt;/span&gt;) {
      &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Timeout&quot;&lt;/span&gt;);
    } &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; {
      &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;User cancelled&quot;&lt;/span&gt;);
    }
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-35&quot;&gt;第五部分：深入思考——语言特性对设计的影响&lt;/h3&gt;
&lt;h3 data-id=&quot;heading-36&quot;&gt;5.1 JavaScript 的事件驱动本质&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;AbortController&lt;/code&gt; 的设计深深植根于 JavaScript 的事件驱动（Event-Driven）本质。JavaScript 作为单线程语言，无法使用抢占式中断（如线程信号），必须通过事件循环机制来传播信号。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/3aa22eac6c6e4359a753c0adb12718ef~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6ICB546L5Lul5Li6:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776960580&amp;amp;x-signature=aYfSMpSCzdHohyA6XgbmNXlGylE%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;这种设计使得 &lt;code&gt;AbortController&lt;/code&gt; 与 JavaScript 的异步模型（Promise、async/await、EventTarget）无缝集成。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-37&quot;&gt;5.2 单线程模型的限制与优势&lt;/h3&gt;
&lt;p&gt;JavaScript 的单线程模型限制了取消机制的设计空间：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无法强制中断：无法像操作系统信号那样强制中断执行中的代码。&lt;/li&gt;
&lt;li&gt;必须协作：任务必须主动检查信号并响应。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但这种限制也带来了优势：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;避免竞态条件：没有抢占式中断的&quot;任意点中断&quot;问题，状态一致性更容易保证。&lt;/li&gt;
&lt;li&gt;简化并发模型：单线程 + 事件循环使得取消信号的传播路径清晰可预测。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-id=&quot;heading-38&quot;&gt;5.3 对比其他语言的设计选择&lt;/h3&gt;
&lt;p&gt;不同语言的中断机制设计反映了它们的运行时特性：&lt;/p&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;语言&lt;/th&gt;&lt;th&gt;运行时模型&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;JavaScript&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;单线程 + 事件循环&lt;/td&gt;&lt;td&gt;&lt;code&gt;AbortController&lt;/code&gt;&lt;/td&gt;&lt;td&gt;事件驱动，协作式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Go&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;M:N 协程调度&lt;/td&gt;&lt;td&gt;&lt;code&gt;context.Context&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Channel 驱动，协作式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;C#&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;线程池 + Task&lt;/td&gt;&lt;td&gt;&lt;code&gt;CancellationToken&lt;/code&gt;&lt;/td&gt;&lt;td&gt;轮询 + 回调，协作式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Java&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;OS 线程&lt;/td&gt;&lt;td&gt;&lt;code&gt;Future.cancel()&lt;/code&gt; + 中断&lt;/td&gt;&lt;td&gt;混合式（协作为主）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Kotlin&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;协程（挂起/恢复）&lt;/td&gt;&lt;td&gt;&lt;code&gt;Job.cancel()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;挂起点检查，协作式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Rust&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;异步 Future + 轮询&lt;/td&gt;&lt;td&gt;&lt;code&gt;Drop&lt;/code&gt; 语义&lt;/td&gt;&lt;td&gt;所有权驱动，协作式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;事件循环 + 协程&lt;/td&gt;&lt;td&gt;&lt;code&gt;Task.cancel()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;异常驱动，协作式&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心点&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;所有现代语言都选择了&lt;strong&gt;协作式取消&lt;/strong&gt;，这不是偶然，而是对资源安全和状态一致性的共同追求。不同语言的实现方式反映了它们的&lt;strong&gt;核心抽象模型&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JavaScript 的 &lt;strong&gt;EventTarget&lt;/strong&gt; → 事件驱动&lt;/li&gt;
&lt;li&gt;Go 的 &lt;strong&gt;Channel&lt;/strong&gt; → 通信顺序进程（CSP）&lt;/li&gt;
&lt;li&gt;Rust 的 &lt;strong&gt;Ownership&lt;/strong&gt; → 编译时安全&lt;/li&gt;
&lt;li&gt;Kotlin 的 &lt;strong&gt;Structured Concurrency&lt;/strong&gt; → 父子作用域&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-39&quot;&gt;结论&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;AbortController&lt;/code&gt; 不仅是一个 API，更是 JavaScript 异步编程哲学的集中体现。它的设计遵循了以下核心思想：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;协作优于强制&lt;/strong&gt;：通过信号机制让任务自主决定如何响应取消，保证资源安全和状态一致性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分离优于耦合&lt;/strong&gt;：控制器与信号的分离使得取消逻辑可以灵活组合和传播。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;事件驱动优于轮询&lt;/strong&gt;：利用 JavaScript 的事件循环机制，实现即时、可靠的信号传播。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;组合优于继承&lt;/strong&gt;：&lt;code&gt;AbortSignal.any()&lt;/code&gt; 等组合操作使得复杂的取消策略可以用简单的原语构建。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;跨语言对比揭示了一个行业共识：&lt;strong&gt;协作式取消是现代异步编程的最佳实践&lt;/strong&gt;。无论是 Go 的 &lt;code&gt;Context&lt;/code&gt;、C# 的 &lt;code&gt;CancellationToken&lt;/code&gt;、Kotlin 的协程取消，还是 Rust 的 &lt;code&gt;Drop&lt;/code&gt; 语义，都在用各自语言的核心抽象表达同一个理念——&lt;strong&gt;让取消成为一等公民，但绝不以牺牲安全为代价&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;理解 &lt;code&gt;AbortController&lt;/code&gt; 的底层原理，不仅能帮助我们写出更健壮的异步代码，更能让我们洞察语言设计背后的深层思考：&lt;strong&gt;好的设计不是增加复杂性，而是在约束条件下找到最优雅的解决方案&lt;/strong&gt;。&lt;/p&gt;</description><link>https://juejin.cn/post/7629093024325025838</link><guid isPermaLink="false">https://juejin.cn/post/7629093024325025838</guid><pubDate>Thu, 16 Apr 2026 09:04:53 GMT</pubDate><author>老王以为</author><category></category><category>JavaScript</category><category>Node.js</category><category>设计模式</category></item><item><title>现在面试 AI 相关问题，不把底层原理扒得明明白白，真的分分钟被问麻😭 | 沸点周刊 4.16</title><description>&lt;h2 data-id=&quot;heading-0&quot;&gt;本周话题推荐&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/topic/7628070531568894003&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/topic/7628070531568894003&quot;&gt;五一计划抢先晒&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;四月即将过半，五一假期越来越近啦，你是早早做好攻略的出行卷王，还是在疯狂抄作业的佛系选手，亦或是打算安心宅家躺平？快来分享你的假期安排，出游路线、宅家日常、避坑心得都欢迎～&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/topic/7628064716983631882&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/topic/7628064716983631882&quot;&gt;万物皆可Skill&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同事.Skill刚火，程序员们开始疯狂整活：老板.Skill催命，前任.Skill扎心，甲方.Skill改稿，反蒸馏.Skill保命……还有什么不能炼成Skill？晒出你见过的Skill，或者聊聊你对蒸馏的看法吧&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-1&quot;&gt;一周“金”选&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/a6a521893deb44a8b067791b433c6de9~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5o6Y6YeR5LiA5ZGo:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776929635&amp;amp;x-signature=lACEvv1cUhzmMCOk5jSz6EltJJo%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;内容评审们会在过去的一周内对社区沸点进行挖掘和筛选，优质的技术、生活内容有机会出现在下方榜单中，排名不分先后。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-2&quot;&gt;AI &amp;amp; 大模型专区&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;上榜规则：&lt;/strong&gt; 发布在AGI交流圈、AICoding交流、Vibe 变成交流圈、AI聊天室、大模型生态圈内，与 AI &amp;amp; 大模型相关的热点资讯、技术分享、使用体验等深度技术内容。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7625829714864816182&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7625829714864816182&quot;&gt;vibe编码不是“甩手掌柜”，而是要在关键节点给AI决策方向&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/993614241737960&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/993614241737960&quot;&gt;lihaozecq&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626571229772775433&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626571229772775433&quot;&gt;高强度AI coding，差一天把cursor pro全部额度用完&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/826443840625992&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/826443840625992&quot;&gt;fengsong&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626733307849588779&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626733307849588779&quot;&gt;cursor 总是 Taking longer than expected，大佬们有什么解决方法啊&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/2101921964120296&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/2101921964120296&quot;&gt;沙参玉竹&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7625210636654411786&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7625210636654411786&quot;&gt;GLM-5 Turbo挺有实力呀，居然在某些方面略胜Claude Opus 4.6&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/502907612446611&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/502907612446611&quot;&gt;chlk123&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7625837999499853839&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7625837999499853839&quot;&gt;ai已经被训成这样了吗......&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/2664871917524200&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/2664871917524200&quot;&gt;前端big菜鸡&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7627257089388183561&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7627257089388183561&quot;&gt;大家都是用一个IDE一直写一个项目的吗？&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/3720403077312798&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/3720403077312798&quot;&gt;断桥残雪0513&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-id=&quot;heading-3&quot;&gt;技术交流专区&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;上榜规则：&lt;/strong&gt; 发布在服务端与架构、技术交流圈、前端开发圈、优秀开源项目内，掘友真实的技术体验分享、相关问题咨询、开源项目分享等深度技术内容。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626192815358525446&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626192815358525446&quot;&gt;从原来 8 秒优化到 100ms 内，总结几个 MySQL 实战心得...&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/2928754708978248&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/2928754708978248&quot;&gt;元宝骑士&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626379710195253257&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626379710195253257&quot;&gt;MyBatis-Plus 价格字段加密终于完美落地！&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/2928754708978248&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/2928754708978248&quot;&gt;元宝骑士&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626633916915417123&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626633916915417123&quot;&gt;最近深挖了一下 Docker 构建原理，把这些关系理顺了&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/749229647607641&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/749229647607641&quot;&gt;折腾派程序员&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-id=&quot;heading-4&quot;&gt;热门沸点 TOP5&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;上榜规则：&lt;/strong&gt; 除“树洞、理财、相亲、面试”圈子外，不限制沸点所在圈子。不存在恶意引战、两性话题、推广引流的内容，根据沸点热度和内容质量进行评选。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626665698594553891&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626665698594553891&quot;&gt;谢谢昨天送生日祝福的兄弟姐妹们&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/2594503171538136&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/2594503171538136&quot;&gt;彩券&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626633916915417123&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626633916915417123&quot;&gt;最近深挖了一下 Docker 构建原理，把这些关系理顺了&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/749229647607641&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/749229647607641&quot;&gt;折腾派程序员&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626638013532258347&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626638013532258347&quot;&gt;结婚也五年了，日子相对平淡，偶尔有些惊喜蛮好&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/1239904849504813&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/1239904849504813&quot;&gt;摸鱼担当&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626099525321261065&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626099525321261065&quot;&gt;jym,今天提车了，在想要不要搞车衣呢？&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/3857552838493854&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/3857552838493854&quot;&gt;lhaha&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626336868328325160&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626336868328325160&quot;&gt;在老家也不好，整个四月份周末都得回去摘茶&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/2280844067880115&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/2280844067880115&quot;&gt;Jerrry&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-id=&quot;heading-5&quot;&gt;优质创作者 TOP5&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;上榜规则：&lt;/strong&gt; 内容积极向上，且带有明确圈子、图片的完整沸点，根据沸点热度和内容质量进行评选。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7625574757921800207&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7625574757921800207&quot;&gt;不上班真花不了多少钱&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/2253326965020704&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/2253326965020704&quot;&gt;甜汤圆&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7625237255016529939&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7625237255016529939&quot;&gt;你一句春不晚，我就到了...&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/3773179639373127&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/3773179639373127&quot;&gt;关你peace&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626521779390332974&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626521779390332974&quot;&gt;我的拼多多店铺开始运营了，昨天出单了&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/3650034335756487&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/3650034335756487&quot;&gt;单纯_稀饭&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7625544419417047067&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7625544419417047067&quot;&gt;回家种地了&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/1425402008706439&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/1425402008706439&quot;&gt;优秀稳妥的金枪小魔王&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7625923696348758042&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7625923696348758042&quot;&gt;上海，还是相机拍的好看&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/2694238549573975&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/2694238549573975&quot;&gt;用户1782406924062&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-id=&quot;heading-6&quot;&gt;最近一次面试被问麻了吗 - 优质奖&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626210940012560438&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626210940012560438&quot;&gt;现在面试 AI 相关问题，真的不是会用工具就行&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/4378706456356627&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/4378706456356627&quot;&gt;uup&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7625825685426716723&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7625825685426716723&quot;&gt;虽然我主要是当面试官，我也想参加这个话题&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/1822351328809050&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/1822351328809050&quot;&gt;聆机李工&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7625920539075002378&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7625920539075002378&quot;&gt;昨天刚面完，现在人还在 CPU 降频状态&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/3685218708368519&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/3685218708368519&quot;&gt;不减20斤不改头像&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-id=&quot;heading-7&quot;&gt;AI 整活大赛，正式开擂！ - 优质奖&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7626247447086940179&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7626247447086940179&quot;&gt;感觉让家人多了很多参与感，还是蛮有意义的&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/3280598429082317&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/3280598429082317&quot;&gt;小怼子&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7625644656978444294&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7625644656978444294&quot;&gt;你这个冷漠无情的AI，我永远都不会原谅你&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/2999941451751527&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/2999941451751527&quot;&gt;伦伦吖&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/pin/7625574757921783823&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/7625574757921783823&quot;&gt;《清明上盒图》&lt;/a&gt;@&lt;a href=&quot;https://juejin.cn/user/2344586853496814&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/user/2344586853496814&quot;&gt;苏州第一深情&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-id=&quot;heading-8&quot;&gt;沸点活动日历&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/f05da1f27a6f475599bd04f97fda838f~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5o6Y6YeR5LiA5ZGo:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776929635&amp;amp;x-signature=kF8lJpN2geFQXLHqSeUBdIPPwkY%3D&quot; alt=&quot;开学季新学期可爱祝福banner (3).jpg&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;h5 data-id=&quot;heading-9&quot;&gt;活动日历&lt;/h5&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&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;a href=&quot;https://juejin.cn/pin/topic/7628070531568894003&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/topic/7628070531568894003&quot;&gt;五一计划抢先晒&lt;/a&gt;&lt;/td&gt;&lt;td&gt;2026年04月13日-2026年04月17日&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://juejin.cn/pin/topic/7628064716983631882&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/pin/topic/7628064716983631882&quot;&gt;万物皆可Skill&lt;/a&gt;&lt;/td&gt;&lt;td&gt;2026年04月13日-2026年04月17日&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 data-id=&quot;heading-10&quot;&gt;📖 投稿专区&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;大家可以在评论区推荐认为不错的技术类沸点，并附上链接和推荐理由，有机会呈现在下一期。沸点创建日期必须在下期沸点周刊发布前一周以内，可以推荐自己/他人的沸点。&lt;/p&gt;
&lt;/blockquote&gt;</description><link>https://juejin.cn/post/7629036295419953186</link><guid isPermaLink="false">https://juejin.cn/post/7629036295419953186</guid><pubDate>Thu, 16 Apr 2026 07:09:50 GMT</pubDate><author>掘金一周</author><category>人工智能</category><category>OpenAI</category><category>AI编程</category><category>沸点</category></item><item><title>彻底疯狂，Claude居然要上传身份证！</title><description>&lt;p&gt;事情是这样的。&lt;/p&gt;
&lt;p&gt;最近不少 Claude 用户在使用某些功能时，被弹出了一个新的页面。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/50ea8feaa1d14e0486539d557594b384~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg54ix5ZCD55qE5bCP6IKl576K:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776848685&amp;amp;x-signature=HrNmEO087snYfwiyBgg%2BvgXngtM%3D&quot; alt=&quot;图片&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;要求上传政府签发的身份证件（身份证、护照、驾照），还要来一张自拍做活体识别。&lt;/p&gt;
&lt;p&gt;不是注册的时候要上传，而是是用着用着突然要求你上传身份信息进行验证。&lt;/p&gt;
&lt;p&gt;随后大家发现Anthropic 在帮助中心悄悄上了一篇新文档，标题叫《Identity verification on Claude》（Claude 上的身份验证）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/52b43bf276824e3b9880f8f88f5ea72f~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg54ix5ZCD55qE5bCP6IKl576K:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776848685&amp;amp;x-signature=GXYCDguz1mBbBqvChq0ri%2B5YreI%3D&quot; alt=&quot;图片&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;原文翻译过来，核心就三句话：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;身份认证已经正式上线&lt;/li&gt;
&lt;li&gt;不是所有人都触发，是&quot;某些场景&quot;会触发&lt;/li&gt;
&lt;li&gt;触发了你就得传，不传就用不了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;至于&quot;某些场景&quot;具体是哪些场景？官方一个字没说。&lt;/p&gt;
&lt;p&gt;技术细节上，Anthropic 称不回存大家的身份证照片，而是接了一家叫&amp;nbsp;&lt;strong&gt;Persona&lt;/strong&gt;&amp;nbsp;的第三方身份验证公司。&lt;/p&gt;
&lt;p&gt;你的身份证和自拍直接给 Persona，Anthropic 想看的时候去 Persona 平台调。&lt;/p&gt;
&lt;p&gt;官方还附赠了三连承诺：数据加密、不用于训练、不卖给第三方。&lt;/p&gt;
&lt;p&gt;而且这事还不是孤立事件。&lt;/p&gt;
&lt;p&gt;就在几天前，其实就已经有人发现 Claude 不太对劲了。&lt;/p&gt;
&lt;p&gt;而在几天前，其实已经有人说Claude要进行验证还有另外一件事。&lt;/p&gt;
&lt;p&gt;国外社交平台上，陆陆续续有用户晒出 Anthropic 发来的邮件，大意是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;我们的团队发现信号显示，你的账户疑似由未成年人使用。&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/7a9969094af64087888c60286f772705~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg54ix5ZCD55qE5bCP6IKl576K:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776848685&amp;amp;x-signature=92IOwGXXSoHChAH4Psrmo7AmZp4%3D&quot; alt=&quot;图片&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;然后给一个 30 天有效的申诉链接。&lt;/p&gt;
&lt;p&gt;要解锁账号，得通过另一家叫&amp;nbsp;&lt;strong&gt;Yoti&lt;/strong&gt;&amp;nbsp;的服务做年龄验证，选项包括上传身份证、面部扫描、生物识别。&lt;/p&gt;
&lt;p&gt;30 天内不验证，账号直接禁用。&lt;/p&gt;
&lt;p&gt;Anthropic 官方支持页面给的解释又是另一套说法，说年龄检测主要靠 Apple App Store 和 Google Play 的数据，部分美国州有新法规要求平台依赖应用商店年龄数据。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/e1ce4cd7d5884bf58987353bb88da8d0~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg54ix5ZCD55qE5bCP6IKl576K:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776848685&amp;amp;x-signature=dqqzaE7BMbs1iv9WbklPvbSNW5g%3D&quot; alt=&quot;图片&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;但用户实际收到的邮件是&quot;我们团队发现信号&quot;，&lt;strong&gt;听起来像是有人工审查 + 算法标记&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这两套说法对不上，引发了不少用户疯狂吐槽。&lt;/p&gt;
&lt;p&gt;那现在还能怎么办？那必须只能使用Codex了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/7b989acfb0034313b7ebc2860fa1a860~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg54ix5ZCD55qE5bCP6IKl576K:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776848685&amp;amp;x-signature=DreRjj2HK4xkgbnHs8%2Bwd9sIQA8%3D&quot; alt=&quot;图片&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&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;p&gt;那剩下的选择就很清晰了，转到Codex上。&lt;/p&gt;
&lt;p&gt;目前来看，&lt;strong&gt;Codex 是市面上唯一一个能在编程能力上和 Claude Code 掰手腕的工具&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;其他的 Cursor、Cline、Aider 各有各的好，但说到&quot;开箱即用、一个命令搞定一个项目&quot;的体验，能打的就只剩 Codex 一个了。&lt;/p&gt;
&lt;p&gt;而且 OpenAI 最近还很懂事地推出了 100 美金/月的 ChatGPT Pro 档位，Codex 用量直接给到 Plus 的 5 倍，发布期还有 10 倍优惠。&lt;/p&gt;
&lt;p&gt;明摆着是来接盘的。&lt;/p&gt;
&lt;p&gt;如果你还不会订阅ChatGPT，目前WildAI已经支持订阅了，三个档次的都有。&lt;/p&gt;
&lt;p&gt;传送门：&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fbewild.ai%3Fcode%3DAIG&quot; target=&quot;_blank&quot; title=&quot;https://bewild.ai?code=AIG&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;wildAI一键订阅GPT&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/12779b82df1b4fa58e9e19f1e079d4a0~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg54ix5ZCD55qE5bCP6IKl576K:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776848685&amp;amp;x-signature=7kbWc0ItdZUhzmOkxFyYGvKEiiM%3D&quot; alt=&quot;图片&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以预见的是，&lt;strong&gt;国内想要正常订阅和使用 Claude 这条路，会越走越窄。&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;p&gt;根据 The Information 的消息，Anthropic 正在推进 IPO 计划，&lt;strong&gt;最快今年第四季度上市&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;要上市了，故事得讲漂亮，估值得撑得住，用户数据要好看。&lt;/p&gt;
&lt;p&gt;就在这个节骨眼上，亲手把一大批活跃用户挡在门外——&lt;/p&gt;
&lt;p&gt;Claude Code 一年干出 25 亿美金的盘子，&lt;strong&gt;里面有多少是国内开发者贡献的，Anthropic 应该比谁都清楚。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;身份认证上线那一刻，&lt;strong&gt;这部分收入基本就要拱手让给OpenAI&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在 IPO 前夕，主动把一个稳定收入盘砍掉，这操作怎么看都不像是一家急着冲业绩的公司该干的事。&lt;/p&gt;
&lt;p&gt;最后只想说一句：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/fef1c776caab41359fd22b3217dd78d8~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg54ix5ZCD55qE5bCP6IKl576K:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776848685&amp;amp;x-signature=b%2BkXWl8vq1JLyX8EpJ9GR0fug34%3D&quot; alt=&quot;图片&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;</description><link>https://juejin.cn/post/7628825069887799339</link><guid isPermaLink="false">https://juejin.cn/post/7628825069887799339</guid><pubDate>Wed, 15 Apr 2026 09:04:45 GMT</pubDate><author>爱吃的小肥羊</author><category>人工智能</category><category>AIGC</category><category>AI编程</category></item><item><title>compose_skill 和 android skills，对 Android 项目提升巨大的专家 AI Skills</title><description>&lt;p&gt;最近发现了一个非常不错的 Compose AI Skills 项目 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fhamen%2Fcompose_skill&quot; target=&quot;_blank&quot; title=&quot;https://github.com/hamen/compose_skill&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;compose_skill&lt;/a&gt; ，&lt;strong&gt;这个 Skills 可以针对你的 compose 项目生成一份评分的报告&lt;/strong&gt;，例如下面就是我的 Compose 项目在这个 Skills 下得到的评分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;性能：4/10&lt;/li&gt;
&lt;li&gt;状态管理：5/10&lt;/li&gt;
&lt;li&gt;副作用：6/10&lt;/li&gt;
&lt;li&gt;Composable API 质量：5/10&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/4ad0e9eacca4409c80c3199469356004~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776834165&amp;amp;x-signature=L9sEpepGZo6qxO8X4%2FpY0KsJwv8%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;技能检查后的整体评价是：项目已经是比较完整的 Compose 多模块应用，结构合理，但是列表性能、状态收集方式、可复用组件的 API 设计，还有 ViewModel/模型穿透 UI 的方式，都存在系统性的问题，最关键的是这些问题不是零星个别点，而是在多个 feature 里重复出现了，&lt;strong&gt;所以只有 46/100&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;compose_skill&lt;/code&gt; 整体可信度还是可以的，&lt;strong&gt;它不是只是通过提示词和 rules 做评审，而是会通过内置的 Gradle 初始化脚本&lt;/strong&gt;，直接生成并解析 Compose 编译器的报告（Reports）和指标（Metrics），主要评判维度有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;性能 (35%)：检查 Compose 里的耗时操作、Lazy 列表的 Key、稳定性（Stability）、强跳过模式（Strong Skipping）&lt;/li&gt;
&lt;li&gt;状态管理：审计状态提升（Hoisting）、单一事实来源、生命周期感知的状态收集&lt;/li&gt;
&lt;li&gt;Side Effects：检查 Effect API 的正确使用、Key 的选择、回调捕获&lt;/li&gt;
&lt;li&gt;API 质量：审查 Modifier 规范、参数顺序、Slot API 设计&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后技能会生成一个名为 &lt;code&gt;COMPOSE-AUDIT-REPORT.md&lt;/code&gt; 的报告，报告不只有分数（0-100），而是每一项扣分都会附带&lt;strong&gt;代码位置&lt;/strong&gt;和&lt;strong&gt;官方文档引用 (References)&lt;/strong&gt; ，例如我的检查里：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-less&quot; lang=&quot;less&quot;&gt;**&lt;span class=&quot;hljs-selector-tag&quot;&gt;State&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;management&lt;/span&gt;: &lt;span class=&quot;hljs-selector-tag&quot;&gt;Android&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;screens&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;collect&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;flows&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;without&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;lifecycle&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;awareness&lt;/span&gt;**

&lt;span class=&quot;hljs-selector-tag&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;Why&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;matters&lt;/span&gt;: `&lt;span class=&quot;hljs-selector-tag&quot;&gt;collectAsState&lt;/span&gt;()` &lt;span class=&quot;hljs-selector-tag&quot;&gt;keeps&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;collecting&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;even&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;when&lt;/span&gt; the screen is stopped, which wastes work &lt;span class=&quot;hljs-keyword&quot;&gt;and&lt;/span&gt; can keep screen state hot longer than intended.
- &lt;span class=&quot;hljs-attribute&quot;&gt;Evidence&lt;/span&gt;: &lt;span class=&quot;hljs-built_in&quot;&gt;`app/src/main/java/com/shuyu/gsygithubappcompose/MainActivity.kt:49`&lt;/span&gt;, &lt;span class=&quot;hljs-built_in&quot;&gt;`feature/search/src/main/java/com/shuyu/gsygithubappcompose/feature/search/SearchScreen.kt:34`&lt;/span&gt;, &lt;span class=&quot;hljs-built_in&quot;&gt;`feature/detail/src/main/java/com/shuyu/gsygithubappcompose/feature/detail/RepoDetailScreen.kt:106`&lt;/span&gt;, &lt;span class=&quot;hljs-built_in&quot;&gt;`feature/profile/src/main/java/com/shuyu/gsygithubappcompose/feature/profile/ProfileScreen.kt:28`&lt;/span&gt;
- Fix &lt;span class=&quot;hljs-attribute&quot;&gt;direction&lt;/span&gt;: add &lt;span class=&quot;hljs-built_in&quot;&gt;`lifecycle-runtime-compose`&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;and&lt;/span&gt; switch Android UI collectors to &lt;span class=&quot;hljs-built_in&quot;&gt;`collectAsStateWithLifecycle()`&lt;/span&gt;.
- &lt;span class=&quot;hljs-attribute&quot;&gt;References&lt;/span&gt;: &amp;lt;&lt;span class=&quot;hljs-attribute&quot;&gt;https&lt;/span&gt;:&lt;span class=&quot;hljs-comment&quot;&gt;//developer.android.com/develop/ui/compose/state&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最重要是，&lt;strong&gt;它还能从审计结果利筛选出影响力最大的修复点，同时预测修复后对 &lt;code&gt;skippable%&lt;/code&gt;（可跳过率）指标的提升效果&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/8ccdac19f45d4ed19cd0821e607eb821~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776834165&amp;amp;x-signature=Ab%2BdC%2FmZbZljdYq6W0tVJ%2FLmocY%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;而相比起那些只通过提示词和规格的技能， 这个技能之所以能够做到深度的专家级审计，主要是因为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gradle ： 项目自带了 &lt;code&gt;scripts/compose-reports.init.gradle&lt;/code&gt;，在技能使用时 AI 会通过 &lt;code&gt;--init-script&lt;/code&gt; 运行项目的 Gradle 任务，将编译器报告的配置动态注入到构建流程中，在不改动代码的情况下拿到部分数据&lt;/li&gt;
&lt;li&gt;静态源码分析： 它定义了一套详尽的 Ripgrep (rg) 搜索策略，涵盖了从 &lt;code&gt;derivedStateOf&lt;/code&gt; 滥用、&lt;code&gt;remember&lt;/code&gt; 缺失 Key 到 &lt;code&gt;Scaffold&lt;/code&gt; 忽略内边距等数十种常见问题&lt;/li&gt;
&lt;li&gt;评分机制： 不是主观评价，在性能评分里如果 &lt;code&gt;skippable%&lt;/code&gt; 低于 50%，或者不稳定类的数量超过阈值，性能得分会强制“封顶”（例如最高只能给 4 分），无论其他地方写得再好也不行&lt;/li&gt;
&lt;li&gt;知识图谱与官方文档锚定： 该项目维护了一份 &lt;code&gt;canonical-sources.md&lt;/code&gt;，将所有的审计规则直接关联到 Android 官方文档和 AndroidX 源码中的 API 指南&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;所以相比起单纯的 AI 评价，它会更专业和全面，而相比起一般的 Linter，它结合了编译器的真实数据 + AI，不仅能告诉你“这个类可能不稳定”，还能通过编译器报告指出是哪个属性导致了不稳定&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当然，它对视觉能力审计会比较弱，作者也维护了另一个叫 &lt;code&gt;material-3&lt;/code&gt; 的技能，这个技能会针对Material 3 的合规性、配色或排版进行评分。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个技能可以看成是一个高级工程师的经验（如何看编译器报告、如何找状态提升问题、如何优化侧效应）固化成了 AI 可执行的脚本和规则，然后再通过 AI 进行量化判断，可以说是无私的奉献了：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;你可以用它作为每次提交代码后的自动审查工具，或者在接手一个混乱的旧项目时，用它来快速定位性能瓶颈和架构风险&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;总之，如果你正在用 Compose ，这个技能技必须不能错过。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我糟糕项目的报告可见：&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FCarGuo%2FGSYGithubAppCompose%2Fblob%2Fmaster%2FCOMPOSE-AUDIT-REPORT.md&quot; target=&quot;_blank&quot; title=&quot;https://github.com/CarGuo/GSYGithubAppCompose/blob/master/COMPOSE-AUDIT-REPORT.md&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github.com/CarGuo/GSYG…&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最后，&lt;strong&gt;android 官方也发布了对应的 skills&lt;/strong&gt; ，目前主要是用于帮助 AI 升级和适配的技能，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将 Android 项目升级到 &lt;strong&gt;Android Gradle Plugin (AGP) 9&lt;/strong&gt;，涉及 DSL 迁移、内置 Kotlin 迁移、KSP 适配等复杂步骤&lt;/li&gt;
&lt;li&gt;提供了将旧版 &lt;strong&gt;XML 视图迁移到 Jetpack Compose&lt;/strong&gt; 的结构化工作流，定义了一套包含 10 个步骤的方法论，确保 UI 的视觉一致性和功能完整性&lt;/li&gt;
&lt;li&gt;其他还涵盖了 &lt;strong&gt;Navigation 3&lt;/strong&gt; 导航系统、&lt;strong&gt;R8 分析器&lt;/strong&gt;性能优化以及 &lt;strong&gt;全屏显示 (Edge-to-Edge)&lt;/strong&gt; 适配等场景&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/8fff952847bb442a8408a70359f67d4e~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776834165&amp;amp;x-signature=Dgm9WdgxIyQj2YmEAwL%2FKdOHdqE%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;这份技能主要通过各种结构化的技术文档来告诉 AI 应该怎么处理升级和适配，需要遵循什么规则，每个技能都包含详细的前置要求 、具体步骤、操作准则 和验证方法，所以它不仅告诉 AI “做什么”，还明确规定了“不能做什么”，比如：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 AGP 升级中禁止编写 Python 脚本。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;技能文档中大量引用了 &lt;code&gt;references/&lt;/code&gt; 目录下的技术参考资料，例如在 XML 迁移技能中，它会引用专门的“分析项目布局”和“设置 Compose 依赖”的子文档。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;所以这里最重要的就是，&lt;strong&gt;官方已经在强烈暗示你:「migrate-xml-views-to-jetpack-compose」，毕竟 AI 场景下，XML 确实也是更不上了&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;总的来说，&lt;code&gt;android skills&lt;/code&gt; 是偏通用性质的升级和适配场景，内容也都是文档和资料，而 &lt;code&gt;compose_skill &lt;/code&gt;则是专家级别的经验检测，能快速帮你找到需要优化的问题和建议。&lt;/p&gt;
&lt;p&gt;我已经开始好奇了，你的项目能得几分 ~~~~~&lt;/p&gt;
&lt;h2 data-id=&quot;heading-0&quot;&gt;链接&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fhamen%2Fcompose_skill&quot; target=&quot;_blank&quot; title=&quot;https://github.com/hamen/compose_skill&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github.com/hamen/compo…&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fandroid%2Fskills&quot; target=&quot;_blank&quot; title=&quot;https://github.com/android/skills&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github.com/android/ski…&lt;/a&gt;&lt;/p&gt;</description><link>https://juejin.cn/post/7628587639852630052</link><guid isPermaLink="false">https://juejin.cn/post/7628587639852630052</guid><pubDate>Wed, 15 Apr 2026 05:02:18 GMT</pubDate><author>恋猫de小郭</author><category></category><category>前端</category><category>Flutter</category><category>Android</category></item><item><title>节省Token的8种方案</title><description>&lt;h2 data-id=&quot;heading-0&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;最近有球友问：“三哥，我们团队在做AI客服，对话一长token消耗扛不住。有没有一种方案，既能保留完整上下文记忆，又能省token？”&lt;/p&gt;
&lt;p&gt;这位朋友的问题，恰恰戳中了当下AI应用开发最头疼的痛点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;既要马儿跑得快，又要马儿不吃草。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这听起来像是矛盾，但经过这两年的摸索，我发现在某些条件下，确实存在“相对两全”的解法。&lt;/p&gt;
&lt;p&gt;今天这篇文章就专门跟大家一起聊聊这个话题，希望对你会有所帮助。&lt;/p&gt;
&lt;p&gt;更多项目实战在Java突击队网：susan.net.cn&lt;/p&gt;
&lt;h2 data-id=&quot;heading-1&quot;&gt;一、为什么记忆必然消耗token？&lt;/h2&gt;
&lt;p&gt;很多小伙伴可能觉得，大模型就像一个人，你说过的话它应该天然记得住。&lt;/p&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;p&gt;每次调用都是独立的，它没有任何“记忆细胞”。&lt;/p&gt;
&lt;p&gt;为了让AI记住之前聊过什么，唯一的办法就是：&lt;strong&gt;把历史对话拼接到下一次请求里&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这就是所谓的“上下文注入”。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/b2b6df4306a942bc919c8733ae4d419a~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6IuP5LiJ6K-05oqA5pyv:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776825001&amp;amp;x-signature=S31EHEg3%2BsheBT0V6ZO4852Q%2BnU%3D&quot; alt=&quot;image&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;看到没？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第N次请求携带的历史，是前N-1轮的总和&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;token消耗随着对话轮数线性增长——更准确地说，是O(n)级别的增长。&lt;/p&gt;
&lt;p&gt;但事情没这么简单。&lt;/p&gt;
&lt;p&gt;Transformer模型的核心是&lt;strong&gt;自注意力机制&lt;/strong&gt;，它的计算复杂度是&lt;strong&gt;O(n²)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是说，输入长度翻一倍，计算量翻四倍。&lt;/p&gt;
&lt;p&gt;更可怕的是，当输入过长时，模型会患上“中间迷失症”——位于长文本中间的信息被严重忽略。&lt;/p&gt;
&lt;p&gt;所以，我们的真实困境是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;保留全部历史 → token爆炸 + 注意力稀释 → 又贵又笨&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;丢弃历史 → 信息丢失 → AI变“金鱼脑”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;有没有一条中间道路？&lt;/p&gt;
&lt;p&gt;有。&lt;/p&gt;
&lt;p&gt;下面我会介绍8种方案，从简单到复杂，从廉价到智能，你可以根据自己的场景按需选择。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-2&quot;&gt;二、方案一：全量记忆&lt;/h2&gt;
&lt;p&gt;简单粗暴，但不推荐。&lt;/p&gt;
&lt;p&gt;这是最直觉的实现：把所有对话都存下来，每次请求全部带上。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;public class FullMemorySession {
    // 使用LinkedList存储全部对话轮次
    private List&amp;lt;Message&amp;gt; fullHistory = new ArrayList&amp;lt;&amp;gt;();
    
    public void addTurn(String userMsg, String assistantMsg) {
        fullHistory.add(new Message(&lt;span class=&quot;hljs-string&quot;&gt;&quot;user&quot;&lt;/span&gt;, userMsg));
        fullHistory.add(new Message(&lt;span class=&quot;hljs-string&quot;&gt;&quot;assistant&quot;&lt;/span&gt;, assistantMsg));
    }
    
    public String &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;buildContext&lt;/span&gt;&lt;/span&gt;() {
        StringBuilder sb = new StringBuilder();
        &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (Message msg : fullHistory) {
            sb.append(msg.getRole()).append(&lt;span class=&quot;hljs-string&quot;&gt;&quot;: &quot;&lt;/span&gt;).append(msg.getContent()).append(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\n&quot;&lt;/span&gt;);
        }
        &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; sb.toString();
    }
    
    // 消息实体
    static class Message {
        String role;
        String content;
        // 构造器、getter省略
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;代码解析&lt;/strong&gt;：逻辑非常直接——&lt;code&gt;fullHistory&lt;/code&gt;列表保存所有消息，&lt;code&gt;buildContext()&lt;/code&gt;把它们全部拼接成字符串。没有任何优化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;信息零损失，完美保留每句话&lt;/li&gt;
&lt;li&gt;实现极简单，5分钟写完&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;token消耗随轮数线性增长，100轮可能几万token&lt;/li&gt;
&lt;li&gt;达到模型上下文上限后（比如8K/128K），旧消息会被截断&lt;/li&gt;
&lt;li&gt;响应时间越来越慢，账单越来越高&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：只适合Demo演示、调试测试，或者保证对话不超过10轮的极短场景。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;生产环境慎用&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-3&quot;&gt;三、方案二：滑动窗口&lt;/h2&gt;
&lt;p&gt;省token，但记性差。&lt;/p&gt;
&lt;p&gt;滑动窗口只保留最近N轮对话，超出窗口的直接丢弃。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;public class SlidingWindowMemory {
    private final int windowSize;          // 窗口大小，比如5轮
    private final Queue&amp;lt;Message&amp;gt; window;   // 用队列自动淘汰旧数据
    
    public SlidingWindowMemory(int windowSize) {
        this.windowSize = windowSize;
        this.window = new ArrayDeque&amp;lt;&amp;gt;();
    }
    
    public void addTurn(String userMsg, String assistantMsg) {
        // 加入新消息
        window.offer(new Message(&lt;span class=&quot;hljs-string&quot;&gt;&quot;user&quot;&lt;/span&gt;, userMsg));
        window.offer(new Message(&lt;span class=&quot;hljs-string&quot;&gt;&quot;assistant&quot;&lt;/span&gt;, assistantMsg));
        // 如果超过窗口大小（注意：一轮=2条消息），就移除头部
        &lt;span class=&quot;hljs-keyword&quot;&gt;while&lt;/span&gt; (window.size() &amp;gt; windowSize * 2) {
            window.poll();
        }
    }
    
    public String &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;buildContext&lt;/span&gt;&lt;/span&gt;() {
        &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; window.stream()
            .map(m -&amp;gt; m.getRole() + &lt;span class=&quot;hljs-string&quot;&gt;&quot;: &quot;&lt;/span&gt; + m.getContent())
            .collect(Collectors.joining(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\n&quot;&lt;/span&gt;));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;代码解析&lt;/strong&gt;：&lt;code&gt;ArrayDeque&lt;/code&gt;作为队列，&lt;code&gt;offer()&lt;/code&gt;在尾部添加，当大小超出&lt;code&gt;windowSize * 2&lt;/code&gt;（因为一轮包含用户和助手两条消息）时，&lt;code&gt;poll()&lt;/code&gt;移除最旧的。&lt;/p&gt;
&lt;p&gt;这样窗口始终保持固定大小。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;token消耗&lt;strong&gt;严格可控&lt;/strong&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;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;早期信息永久丢失&lt;/strong&gt;。用户第一句说“我是VIP会员”，第20轮问“我的会员权益”，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;
&lt;h2 data-id=&quot;heading-4&quot;&gt;四、方案三：摘要压缩&lt;/h2&gt;
&lt;p&gt;让AI自己总结记忆。&lt;/p&gt;
&lt;p&gt;这个方案的想法很巧妙：不保留原始对话，而是定期让大模型把旧对话“压缩”成一段摘要，只保留关键信息。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;public class SummaryMemory {
    private String summary = &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;;                    // 历史摘要
    private List&amp;lt;Message&amp;gt; recentMessages = new ArrayList&amp;lt;&amp;gt;();  // 未压缩的新消息
    private final int compressThreshold;             // 触发压缩的token阈值
    private final LLMClient llm;                     // 大模型客户端
    
    public SummaryMemory(LLMClient llm, int compressThreshold) {
        this.llm = llm;
        this.compressThreshold = compressThreshold;
    }
    
    public void addTurn(String userMsg, String assistantMsg) {
        recentMessages.add(new Message(&lt;span class=&quot;hljs-string&quot;&gt;&quot;user&quot;&lt;/span&gt;, userMsg));
        recentMessages.add(new Message(&lt;span class=&quot;hljs-string&quot;&gt;&quot;assistant&quot;&lt;/span&gt;, assistantMsg));
        
        // 估算当前token数，超过阈值则触发压缩
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (estimateTokenCount() &amp;gt; compressThreshold) {
            compress();
        }
    }
    
    private void &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;compress&lt;/span&gt;&lt;/span&gt;() {
        // 构建待压缩的内容：旧摘要 + 新消息
        String toCompress = summary + &lt;span class=&quot;hljs-string&quot;&gt;&quot;\n&quot;&lt;/span&gt; + formatMessages(recentMessages);
        String prompt = &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;
            请将以下对话历史压缩成一段简洁的摘要，保留：
            1. 用户的关键信息（姓名、偏好、身份、已做出的决定）
            2. 重要的任务状态和上下文
            3. 任何后续对话可能需要的事实
            原始对话：
            %s
            摘要：
            &quot;&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;.formatted(toCompress);
        
        // 调用大模型生成摘要
        String newSummary = llm.chat(prompt);
        this.summary = newSummary;
        // 压缩后清空近期消息，但可以保留最后一轮作为锚点（防止断层）
        this.recentMessages.clear();
    }
    
    public String &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;buildContext&lt;/span&gt;&lt;/span&gt;() {
        // 上下文 = 摘要 + 最近未压缩的消息
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (summary.isEmpty()) {
            &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; formatMessages(recentMessages);
        }
        &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;【历史摘要】\n&quot;&lt;/span&gt; + summary + &lt;span class=&quot;hljs-string&quot;&gt;&quot;\n\n【最近对话】\n&quot;&lt;/span&gt; + formatMessages(recentMessages);
    }
    
    private int &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;estimateTokenCount&lt;/span&gt;&lt;/span&gt;() {
        // 简单估算：中文1字≈2token，英文1词≈1.3token，这里粗略用字符数/2
        int totalChars = summary.length() + formatMessages(recentMessages).length();
        &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; totalChars / 2;
    }
    
    private String formatMessages(List&amp;lt;Message&amp;gt; msgs) {
        &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; msgs.stream()
            .map(m -&amp;gt; m.getRole() + &lt;span class=&quot;hljs-string&quot;&gt;&quot;: &quot;&lt;/span&gt; + m.getContent())
            .collect(Collectors.joining(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\n&quot;&lt;/span&gt;));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;代码详解&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;summary&lt;/code&gt;字段存储压缩后的历史摘要，初始为空。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;recentMessages&lt;/code&gt;存储尚未被压缩的新消息。&lt;/li&gt;
&lt;li&gt;每次添加消息后，估算总token数（摘要+新消息），如果超过阈值，调用&lt;code&gt;compress()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;压缩时，将旧摘要和新消息一起发给大模型，让它生成一个新的、更精炼的摘要。&lt;/li&gt;
&lt;li&gt;压缩完成后清空&lt;code&gt;recentMessages&lt;/code&gt;，但也可以选择保留最后1-2轮，防止摘要丢失近期细节。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;buildContext()&lt;/code&gt;返回摘要+最近消息的拼接，作为下次请求的上下文。&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;strong&gt;压缩比极高&lt;/strong&gt;：100轮对话可能压缩成200字摘要，token节省90%以上&lt;/li&gt;
&lt;li&gt;关键信息被提炼出来，比滑动窗口聪明得多&lt;/li&gt;
&lt;li&gt;成本可控，摘要生成的调用次数不多（每隔N轮一次）&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;strong&gt;摘要可能失真&lt;/strong&gt;：模型可能漏掉重要细节或产生幻觉&lt;/li&gt;
&lt;li&gt;每次压缩需要额外调用LLM，增加几十毫秒延迟&lt;/li&gt;
&lt;li&gt;摘要的“信息密度”随着压缩次数增加而下降（反复压缩会丢失细节）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：长周期对话（几十到几百轮），对信息完整性要求不是100%严谨的场景，比如教育辅导、角色扮演。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-5&quot;&gt;五、方案四：向量记忆（RAG）&lt;/h2&gt;
&lt;p&gt;检索相关而不是保留全部。&lt;/p&gt;
&lt;p&gt;这是目前工业界最主流的方案。&lt;/p&gt;
&lt;p&gt;思路是：&lt;strong&gt;不保存全部历史，而是把历史消息向量化后存入数据库，每次只检索最相关的几条历史&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;public class VectorMemory {
    private final EmbeddingClient embeddingClient;  // 向量化模型，如OpenAI embedding
    private final VectorDatabase vectorDb;           // 向量数据库，如Milvus、Chroma
    private final int topK = 5;                      // 每次检索几条最相关的历史
    
    public VectorMemory(EmbeddingClient embeddingClient, VectorDatabase vectorDb) {
        this.embeddingClient = embeddingClient;
        this.vectorDb = vectorDb;
    }
    
    // 存储一条历史消息（每个消息单独存储，带上元数据）
    public void saveMessage(String role, String content, Map&amp;lt;String, Object&amp;gt; metadata) {
        // 1. 调用embedding接口，将文本转为向量
        &lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;[] vector = embeddingClient.embed(content);
        // 2. 存入向量数据库
        VectorRecord record = new VectorRecord(vector, content, metadata);
        vectorDb.insert(record);
    }
    
    // 检索与当前问题最相关的历史记忆
    public List&amp;lt;Message&amp;gt; retrieveRelevantHistory(String currentQuestion) {
        // 将当前问题向量化
        &lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;[] queryVector = embeddingClient.embed(currentQuestion);
        // 在数据库中做相似度搜索，返回topK条最相似的历史消息
        List&amp;lt;VectorRecord&amp;gt; results = vectorDb.search(queryVector, topK);
        // 转换成Message对象
        &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; results.stream()
            .map(r -&amp;gt; new Message((String)r.getMetadata().get(&quot;role&quot;), r.getContent()))
            .collect(Collectors.toList());
    }
    
    // 构建上下文：检索结果 + 最近几轮（可选）
    public String buildContext(String userQuestion) {
        List&amp;lt;Message&amp;gt; relevantHistory = retrieveRelevantHistory(userQuestion);
        StringBuilder context = new StringBuilder(&lt;span class=&quot;hljs-string&quot;&gt;&quot;相关历史记忆：\n&quot;&lt;/span&gt;);
        &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (Message msg : relevantHistory) {
            context.append(msg.getRole()).append(&lt;span class=&quot;hljs-string&quot;&gt;&quot;: &quot;&lt;/span&gt;).append(msg.getContent()).append(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\n&quot;&lt;/span&gt;);
        }
        &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; context.toString();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;代码详解&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每条消息单独存储，调用&lt;code&gt;embeddingClient.embed()&lt;/code&gt;将其转为高维向量（比如1536维）。&lt;/li&gt;
&lt;li&gt;向量数据库存储向量+原始文本+元数据（角色、时间戳等）。&lt;/li&gt;
&lt;li&gt;当用户发来新问题时，同样将问题向量化，然后到数据库中做&lt;strong&gt;余弦相似度搜索&lt;/strong&gt;，找到最相似的&lt;code&gt;topK&lt;/code&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;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;token消耗极低&lt;/strong&gt;：每次只带5-10条最相关的历史，而不是几百条&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可以访问非常久远的记忆&lt;/strong&gt;：只要存储了，就能检索到，不受窗口限制&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;灵活性高&lt;/strong&gt;：可以混入知识库、FAQ等外部知识&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;strong&gt;检索可能不准确&lt;/strong&gt;：如果embedding模型质量差，或者问题与历史的相关性未被捕捉到，就会漏掉关键信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要额外组件&lt;/strong&gt;：向量数据库、embedding服务，增加系统复杂度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有延迟&lt;/strong&gt;：embedding调用+向量检索，大约增加50-200ms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：绝大多数生产环境——智能客服、AI助手、个性化推荐等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这是目前最推荐的方案&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-6&quot;&gt;六、方案五：分层混合记忆&lt;/h2&gt;
&lt;p&gt;它是工业级最强方案。&lt;/p&gt;
&lt;p&gt;没有单一方案是完美的。真正的工业级系统，往往会&lt;strong&gt;组合多种策略&lt;/strong&gt;，形成分层记忆。&lt;/p&gt;
&lt;p&gt;下面这张图展示了一个典型的混合记忆架构：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/4a6ad6ef27cf4114bbaa1482f33a1417~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6IuP5LiJ6K-05oqA5pyv:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776825001&amp;amp;x-signature=ZVgIFFo%2FElAK0sBBIpWN%2Fo6IpyA%3D&quot; alt=&quot;image&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;Java实现的核心骨架：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;public class HierarchicalMemory {
    private SlidingWindowMemory shortTerm;     // L1 短期
    private SummaryMemory midTerm;              // L2 中期摘要
    private VectorMemory longTerm;              // L3 长期向量
    
    private final int SUMMARY_INTERVAL = 10;    // 每10轮触发一次摘要压缩
    private int turnCount = 0;
    
    public HierarchicalMemory(LLMClient llm, EmbeddingClient embed, VectorDatabase db) {
        this.shortTerm = new SlidingWindowMemory(5);   // 保留最近5轮
        this.midTerm = new SummaryMemory(llm, 2000);    // token超2000压缩
        this.longTerm = new VectorMemory(embed, db);
    }
    
    public void addTurn(String userMsg, String assistantMsg) {
        turnCount++;
        
        // 存入三层记忆
        shortTerm.addTurn(userMsg, assistantMsg);
        midTerm.addTurn(userMsg, assistantMsg);
        longTerm.saveMessage(&lt;span class=&quot;hljs-string&quot;&gt;&quot;user&quot;&lt;/span&gt;, userMsg, Map.of(&lt;span class=&quot;hljs-string&quot;&gt;&quot;turn&quot;&lt;/span&gt;, turnCount));
        longTerm.saveMessage(&lt;span class=&quot;hljs-string&quot;&gt;&quot;assistant&quot;&lt;/span&gt;, assistantMsg, Map.of(&lt;span class=&quot;hljs-string&quot;&gt;&quot;turn&quot;&lt;/span&gt;, turnCount));
        
        // 每10轮额外触发一次摘要同步（可选）
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (turnCount % SUMMARY_INTERVAL == 0) {
            midTerm.compress();  // 强制压缩
        }
    }
    
    public String buildContext(String currentQuestion) {
        // 1. 短期记忆（最近对话）—— 最重要，直接拼接
        String shortContext = shortTerm.buildContext();
        
        // 2. 检索长期记忆（基于当前问题）
        List&amp;lt;Message&amp;gt; longMemory = longTerm.retrieveRelevantHistory(currentQuestion);
        String longContext = formatMessages(longMemory);
        
        // 3. 中期摘要（如果摘要非空）
        String midContext = midTerm.getCurrentSummary();
        
        // 4. 按重要性组装：短期 &amp;gt; 检索结果 &amp;gt; 摘要
        StringBuilder finalContext = new StringBuilder();
        finalContext.append(&lt;span class=&quot;hljs-string&quot;&gt;&quot;【最近对话】\n&quot;&lt;/span&gt;).append(shortContext).append(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\n&quot;&lt;/span&gt;);
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!longContext.isEmpty()) {
            finalContext.append(&lt;span class=&quot;hljs-string&quot;&gt;&quot;【相关历史】\n&quot;&lt;/span&gt;).append(longContext).append(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\n&quot;&lt;/span&gt;);
        }
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (midContext != null &amp;amp;&amp;amp; !midContext.isEmpty()) {
            finalContext.append(&lt;span class=&quot;hljs-string&quot;&gt;&quot;【历史摘要】\n&quot;&lt;/span&gt;).append(midContext).append(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\n&quot;&lt;/span&gt;);
        }
        &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; finalContext.toString();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;代码解析&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;L1 短期&lt;/strong&gt;：滑动窗口保留最近5轮，保证对话连贯性，延迟最低。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L2 中期&lt;/strong&gt;：摘要压缩，当消息积累到一定程度（比如token超2000或每10轮）就压缩一次，保留全局脉络。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L3 长期&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;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;兼具短时连贯、长时检索、全局摘要&lt;/strong&gt;，覆盖几乎所有场景&lt;/li&gt;
&lt;li&gt;token消耗可控（短期固定+检索topK+摘要）&lt;/li&gt;
&lt;li&gt;即使检索失败，摘要和短期窗口也能兜底&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;/li&gt;
&lt;li&gt;需要精细调参（窗口大小、摘要频率、检索数量）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：大型生产系统、企业级AI应用，对体验和成本都有高要求的场景。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-7&quot;&gt;七、方案六：状态变量提取&lt;/h2&gt;
&lt;p&gt;该方案需要极致的结构化压缩。&lt;/p&gt;
&lt;p&gt;有些场景下，真正需要记忆的不是整个对话，而是&lt;strong&gt;几个关键状态变量&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如订票机器人只需要知道：&lt;code&gt;{目的地: &quot;北京&quot;, 日期: &quot;2026-05-01&quot;, 人数: 2}&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;public class StateVariableMemory {
    private Map&amp;lt;String, Object&amp;gt; state = new HashMap&amp;lt;&amp;gt;();  // 核心状态
    
    // 通过LLM从对话中提取结构化状态
    public void updateState(String userMsg, String assistantMsg, LLMClient llm) {
        String extractPrompt = &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;
            从以下对话中提取关键状态变量，以JSON格式输出。
            当前已有状态：%s
            用户最新消息：%s
            AI回复：%s
            请更新状态，只输出JSON，不要其他内容。
            &quot;&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;.formatted(toJson(state), userMsg, assistantMsg);
        
        String jsonResponse = llm.chat(extractPrompt);
        Map&amp;lt;String, Object&amp;gt; newState = parseJson(jsonResponse);
        state.putAll(newState);  // 合并更新
    }
    
    public String &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;buildContext&lt;/span&gt;&lt;/span&gt;() {
        // 上下文只需要展示当前状态，而不是历史对话
        &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;当前会话状态：&quot;&lt;/span&gt; + toJson(state);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;极致省token&lt;/strong&gt;：几KB的状态就能代替几十KB的对话&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结构化&lt;/strong&gt;，模型更容易理解&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;strong&gt;高度结构化&lt;/strong&gt;的任务（订票、填表、参数收集）&lt;/li&gt;
&lt;li&gt;提取状态本身需要调用LLM，有额外成本&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：任务型对话、表单填写、配置向导。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-8&quot;&gt;八、方案七：工具/函数调用&lt;/h2&gt;
&lt;p&gt;把记忆“外包”给外部系统。&lt;/p&gt;
&lt;p&gt;大模型不是万能的，记忆完全可以交给外部数据库。&lt;/p&gt;
&lt;p&gt;模型只需要学会调用“保存记忆”和“查询记忆”的工具。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;public class ToolBasedMemory {
    // 定义两个工具函数
    @Tool(name = &lt;span class=&quot;hljs-string&quot;&gt;&quot;save_memory&quot;&lt;/span&gt;, description = &lt;span class=&quot;hljs-string&quot;&gt;&quot;保存一条重要信息到长期记忆&quot;&lt;/span&gt;)
    public void saveMemory(String key, String value) {
        externalDB.put(key, value);
    }
    
    @Tool(name = &lt;span class=&quot;hljs-string&quot;&gt;&quot;recall_memory&quot;&lt;/span&gt;, description = &lt;span class=&quot;hljs-string&quot;&gt;&quot;根据关键词回忆之前保存的信息&quot;&lt;/span&gt;)
    public String recallMemory(String key) {
        &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; externalDB.get(key);
    }
    
    // 在对话循环中，让模型自主决定何时调用这些工具
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种方案让模型&lt;strong&gt;自主管理记忆&lt;/strong&gt;——它觉得重要就存，需要就用。这是目前AI Agent的主流做法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：极其灵活，模型可以按需存取；token消耗几乎为零（只传工具调用结果）。&lt;br&gt;
&lt;strong&gt;缺点&lt;/strong&gt;：依赖模型自身的函数调用能力，容易出错或漏存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：Agent系统、自主决策类应用。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-9&quot;&gt;九、终极对比&lt;/h2&gt;





























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;方案&lt;/th&gt;&lt;th&gt;token节省效果&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;/td&gt;&lt;td&gt;0%（无节省）&lt;/td&gt;&lt;td&gt;100%&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;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;高（70-90%）&lt;/td&gt;&lt;td&gt;中（可能失真）&lt;/td&gt;&lt;td&gt;⭐⭐⭐&lt;/td&gt;&lt;td&gt;⭐⭐⭐⭐ 长对话&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;向量检索(RAG)&lt;/td&gt;&lt;td&gt;高（每次topK）&lt;/td&gt;&lt;td&gt;高（语义检索）&lt;/td&gt;&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;&lt;td&gt;⭐⭐⭐⭐⭐ 首选&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;分层混合&lt;/td&gt;&lt;td&gt;高&lt;/td&gt;&lt;td&gt;极高&lt;/td&gt;&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;&lt;td&gt;⭐⭐⭐⭐⭐ 工业级&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;状态变量&lt;/td&gt;&lt;td&gt;极高（近乎0）&lt;/td&gt;&lt;td&gt;中（仅结构化）&lt;/td&gt;&lt;td&gt;⭐⭐&lt;/td&gt;&lt;td&gt;⭐⭐⭐ 任务型&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;工具调用&lt;/td&gt;&lt;td&gt;极高&lt;/td&gt;&lt;td&gt;中（靠模型）&lt;/td&gt;&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;&lt;td&gt;⭐⭐⭐⭐ Agent场景&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;更多项目实战在Java突击队网：susan.net.cn&lt;/p&gt;
&lt;h2 data-id=&quot;heading-10&quot;&gt;总结&lt;/h2&gt;
&lt;p&gt;回到最初的问题：&lt;strong&gt;有没有方案既能保留上下文记忆，又能省token？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我的答案是：&lt;strong&gt;有，但不存在“免费午餐”。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每一分token的节省，都换来了系统复杂度的增加或记忆精度的下降。&lt;/p&gt;
&lt;p&gt;根据我的实战经验，给你几条直接的建议：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;如果你刚开始做MVP&lt;/strong&gt;：直接用&lt;strong&gt;滑动窗口（最近10轮）&lt;/strong&gt;，上线跑起来再说。先验证产品价值，再优化成本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如果你做的是通用客服/AI助手&lt;/strong&gt;：首选&lt;strong&gt;向量检索（RAG）&lt;/strong&gt;。这是当前最成熟、性价比最高的方案。配合一个小的滑动窗口（3-5轮）保证对话连贯性，效果已经很好了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如果你的对话轮次非常长（100+）且信息密度高&lt;/strong&gt;：上&lt;strong&gt;分层混合记忆&lt;/strong&gt;。短期窗口+中期摘要+长期向量，三者配合才能既省token又不丢信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如果你做的是表单/订票/参数收集&lt;/strong&gt;：&lt;strong&gt;状态变量提取&lt;/strong&gt;是王道。几十个字段就能代表整个会话，token几乎不增长。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;永远不要在生产环境用全量记忆&lt;/strong&gt;——除非你预算无限且用户只聊5句话。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;posted @ &amp;nbsp; &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fwww.cnblogs.com%2F12lisu&quot; target=&quot;_blank&quot; title=&quot;https://www.cnblogs.com/12lisu&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;苏三说技术&lt;/a&gt;&amp;nbsp; 阅读(0)&amp;nbsp; 评论(0)&amp;nbsp; &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fwww.cnblogs.com%2F12lisu%2Fp%2F19869593.md&quot; target=&quot;_blank&quot; title=&quot;https://www.cnblogs.com/12lisu/p/19869593.md&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;MD&lt;/a&gt;&amp;nbsp; &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fi.cnblogs.com%2FEditPosts.aspx%3Fpostid%3D19869593&quot; target=&quot;_blank&quot; title=&quot;https://i.cnblogs.com/EditPosts.aspx?postid=19869593&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;编辑&lt;/a&gt;&amp;nbsp; &lt;a title=&quot;&quot; ref=&quot;nofollow noopener noreferrer&quot; href=&quot;https://link.juejin.cn/?target=&quot;&gt;收藏&lt;/a&gt;&amp;nbsp; &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Freport.cnblogs.com%2F%3FtargetLink%3Dhttps%253A%252F%252Fwww.cnblogs.com%252F12lisu%252Fp%252F19869593%26targetId%3D19869593%26targetType%3D0&quot; target=&quot;_blank&quot; title=&quot;https://report.cnblogs.com/?targetLink=https%3A%2F%2Fwww.cnblogs.com%2F12lisu%2Fp%2F19869593&amp;amp;targetId=19869593&amp;amp;targetType=0&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;举报&lt;/a&gt;&lt;/p&gt;</description><link>https://juejin.cn/post/7628442107121598479</link><guid isPermaLink="false">https://juejin.cn/post/7628442107121598479</guid><pubDate>Wed, 15 Apr 2026 02:30:02 GMT</pubDate><author>苏三说技术</author><category></category><category>后端</category></item><item><title>别再用 JSON.parse 深拷贝了，聊聊 StructuredClone</title><description>&lt;p&gt;临近下班，我们业务线出了一个极度无语的线上 Bug。&lt;/p&gt;
&lt;p&gt;产品侧反馈，在一个非常核心的财务表单里，用户明明选择了 &lt;code&gt;2026-04-14&lt;/code&gt; 作为结算日期，但点击提交后，整个页面直接白屏崩溃。&lt;/p&gt;
&lt;p&gt;我打开错误监控看了一眼日志，立刻就把组里那个刚入职不久的小伙子叫了过来。
原因极其经典：他在把表单的原始状态同步给历史快照时，为了图省事，顺手写了一段几乎所有前端都写过的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-js&quot; lang=&quot;js&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 模拟用户表单数据&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; formData = {
  &lt;span class=&quot;hljs-attr&quot;&gt;amount&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1000&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;date&lt;/span&gt;: &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;2026-04-14&quot;&lt;/span&gt;), &lt;span class=&quot;hljs-comment&quot;&gt;// 用户选的结算日期（Date对象）&lt;/span&gt;
};

&lt;span class=&quot;hljs-comment&quot;&gt;// 深拷贝&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; snapshot = &lt;span class=&quot;hljs-title class_&quot;&gt;JSON&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;parse&lt;/span&gt;(&lt;span class=&quot;hljs-title class_&quot;&gt;JSON&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;stringify&lt;/span&gt;(formData));

&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;原始:&quot;&lt;/span&gt;, formData.&lt;span class=&quot;hljs-property&quot;&gt;date&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;typeof&lt;/span&gt; formData.&lt;span class=&quot;hljs-property&quot;&gt;date&lt;/span&gt;); 
&lt;span class=&quot;hljs-comment&quot;&gt;// Date object&lt;/span&gt;

&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;快照:&quot;&lt;/span&gt;, snapshot.&lt;span class=&quot;hljs-property&quot;&gt;date&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;typeof&lt;/span&gt; snapshot.&lt;span class=&quot;hljs-property&quot;&gt;date&lt;/span&gt;); 
&lt;span class=&quot;hljs-comment&quot;&gt;// &quot;2026-04-14T00:00:00.000Z&quot; string&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;// 后续业务代码&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;calcSettlementTime&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;data&lt;/span&gt;) {
  &lt;span class=&quot;hljs-comment&quot;&gt;// 这里默认 date 是 Date 对象&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; data.&lt;span class=&quot;hljs-property&quot;&gt;date&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;getTime&lt;/span&gt;();
}

&lt;span class=&quot;hljs-comment&quot;&gt;// 页面直接崩溃😢&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; time = &lt;span class=&quot;hljs-title function_&quot;&gt;calcSettlementTime&lt;/span&gt;(snapshot);
  &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;时间戳:&quot;&lt;/span&gt;, time);
} &lt;span class=&quot;hljs-keyword&quot;&gt;catch&lt;/span&gt; (err) {
  &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;error&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;页面崩溃:&quot;&lt;/span&gt;, err);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;他满脸委屈：老大，大家平时深拷贝不都是这么写的吗？🤷‍♂️&lt;/p&gt;
&lt;p&gt;我让他自己把这段代码在控制台跑一遍。
当他看到表单里原本好好的 &lt;code&gt;Date&lt;/code&gt; 对象，经过这一进一出，硬生生变成了一串 ISO 格式的&lt;strong&gt;字符串&lt;/strong&gt;，导致后面调用 &lt;code&gt;snapshot.date.getTime()&lt;/code&gt; 直接抛出 &lt;code&gt;TypeError&lt;/code&gt; 时，他自己也沉默了。&lt;/p&gt;
&lt;p&gt;作为前端老油条，这种因为 &lt;code&gt;JSON.parse(JSON.stringify())&lt;/code&gt; 引发的血案，我见过太多了。
它不仅会把 &lt;code&gt;Date&lt;/code&gt; 变成字符串，还会把 &lt;code&gt;Map&lt;/code&gt; 和 &lt;code&gt;Set&lt;/code&gt; 变成空对象 &lt;code&gt;{}&lt;/code&gt;，会把 &lt;code&gt;undefined&lt;/code&gt;、&lt;code&gt;Symbol&lt;/code&gt; 以及函数直接活生生抹除，更别提遇到循环引用时，它会当场抛出异常让你的主线程直接崩溃。&lt;/p&gt;
&lt;p&gt;以前，我们为了解决这个破事，不得不在每个项目里老老实实 &lt;code&gt;npm install lodash&lt;/code&gt;，然后引入那个笨重的 &lt;code&gt;cloneDeep&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;但现在是 2026 年了。浏览器早就原生内置了完美的终极解药——&lt;strong&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FWindow%2FstructuredClone&quot; target=&quot;_blank&quot; title=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;&lt;code&gt;structuredClone&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;今天咱们不聊虚的架构，就花三分钟，把这个原生 API 的底层逻辑讲清楚。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-0&quot;&gt;它是怎么解决历史遗留问题的？&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;structuredClone&lt;/code&gt; 不是什么语法糖，它是浏览器底层暴露出来的 &lt;strong&gt;结构化克隆算法（Structured Clone Algorithm）&lt;/strong&gt;。这就意味着，它在 C++ 引擎层面的处理逻辑，远比 JS 业务层面的递归拷贝要深得多。&lt;/p&gt;
&lt;p&gt;看一下原生 API 的用法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; original = {
  &lt;span class=&quot;hljs-attr&quot;&gt;date&lt;/span&gt;: &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;(),
  &lt;span class=&quot;hljs-attr&quot;&gt;set&lt;/span&gt;: &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;([&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;]),
  &lt;span class=&quot;hljs-attr&quot;&gt;map&lt;/span&gt;: &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Map&lt;/span&gt;([[&lt;span class=&quot;hljs-string&quot;&gt;&#39;key&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;value&#39;&lt;/span&gt;]]),
  &lt;span class=&quot;hljs-attr&quot;&gt;regex&lt;/span&gt;: &lt;span class=&quot;hljs-regexp&quot;&gt;/hello/i&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;buffer&lt;/span&gt;: &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Uint8Array&lt;/span&gt;([&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;]).&lt;span class=&quot;hljs-property&quot;&gt;buffer&lt;/span&gt;,
};

&lt;span class=&quot;hljs-comment&quot;&gt;// 制造一个循环引用&lt;/span&gt;
original.&lt;span class=&quot;hljs-property&quot;&gt;self&lt;/span&gt; = original;

&lt;span class=&quot;hljs-comment&quot;&gt;// 一行代码，原生搞定&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; cloned = &lt;span class=&quot;hljs-title function_&quot;&gt;structuredClone&lt;/span&gt;(original);

&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(cloned.&lt;span class=&quot;hljs-property&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// true&lt;/span&gt;
&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(cloned.&lt;span class=&quot;hljs-property&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;);   &lt;span class=&quot;hljs-comment&quot;&gt;// true&lt;/span&gt;
&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(cloned.&lt;span class=&quot;hljs-property&quot;&gt;self&lt;/span&gt; === cloned);      &lt;span class=&quot;hljs-comment&quot;&gt;// true 完美处理循环引用！&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现没有？它不仅完美保留了所有的内置对象类型，连 &lt;code&gt;JSON.parse&lt;/code&gt; 绝对搞不定的&lt;strong&gt;循环引用&lt;/strong&gt;，它都处理得游刃有余。由于是在引擎底层运行，不需要像 Lodash 那样在 JS 运行时里疯狂压栈递归，它的执行效率在大部分复杂场景下都具有压倒性优势👍👍👍。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-1&quot;&gt;零拷贝转移 （Transferable Objects）&lt;/h3&gt;
&lt;p&gt;如果你以为 &lt;code&gt;structuredClone&lt;/code&gt; 只是为了少引入一个 Lodash，那你就太小看浏览器的底层野心了。&lt;/p&gt;
&lt;p&gt;它藏着一个 90% 的前端都不知道的极其硬核的功能：&lt;strong&gt;内存转移（Transfer）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在前端处理音视频、WebGL、或者读取几十 MB 的大文件时，我们经常会生成巨大的 &lt;code&gt;ArrayBuffer&lt;/code&gt;。如果你用传统的深拷贝，内存瞬间翻倍，几十兆的内存分配极容易引起页面的掉帧卡顿。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;structuredClone&lt;/code&gt; 提供了一个极其变态的第二个参数配置：&lt;code&gt;{ transfer }&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 假设这是一个极大的 50MB 数据内存块&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; u8Array = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Uint8Array&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;1024&lt;/span&gt; * &lt;span class=&quot;hljs-number&quot;&gt;1024&lt;/span&gt; * &lt;span class=&quot;hljs-number&quot;&gt;50&lt;/span&gt;);
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; hugeBuffer = u8Array.&lt;span class=&quot;hljs-property&quot;&gt;buffer&lt;/span&gt;;

&lt;span class=&quot;hljs-comment&quot;&gt;// 传统的深拷贝：内存翻倍，耗时极长&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;// const badCopy = lodash.cloneDeep(hugeBuffer); &lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;// 直接内存转移&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; fastClone = &lt;span class=&quot;hljs-title function_&quot;&gt;structuredClone&lt;/span&gt;(hugeBuffer, { &lt;span class=&quot;hljs-attr&quot;&gt;transfer&lt;/span&gt;: [hugeBuffer] });

&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(fastClone.&lt;span class=&quot;hljs-property&quot;&gt;byteLength&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// 52428800 (50MB 完美转移)&lt;/span&gt;
&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(hugeBuffer.&lt;span class=&quot;hljs-property&quot;&gt;byteLength&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// 0 (原对象的内存地址被转移)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码的核心在于：&lt;strong&gt;它压根没有复制数据。&lt;/strong&gt;
它直接在内存层面，把这块 50MB 数据的所有权，从 &lt;code&gt;hugeBuffer&lt;/code&gt; 强行转移给了 &lt;code&gt;fastClone&lt;/code&gt;。原对象被彻底掏空（变成了 detached 状态）。&lt;/p&gt;
&lt;p&gt;这种零拷贝机制，在结合 Web Worker 处理复杂后台计算时，是打破性能瓶颈的绝对神器。这是任何第三方 JS 库都做不到的底层API。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-2&quot;&gt;一些坑要讲清楚🤔&lt;/h3&gt;
&lt;p&gt;既然这么牛，是不是以后项目里所有的拷贝闭着眼睛用它就行了？
作为一个踩过无数坑的老兵，我必须点出它的几个致命死角。如果你在真实的业务架构里滥用，下场比用 &lt;code&gt;JSON.parse&lt;/code&gt; 还要惨。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对于函数和 DOM 节点的处理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;JSON.parse&lt;/code&gt; 遇到函数，它会默默地忽略掉，至少不报错。
但 &lt;code&gt;structuredClone&lt;/code&gt; 很直接。只要你的对象树里藏着一个方法，或者藏着一个 DOM 节点的引用，它会直接给你抛出 &lt;code&gt;DataCloneError&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; objWithFunc = {
  &lt;span class=&quot;hljs-attr&quot;&gt;data&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;123&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;onClick&lt;/span&gt;: &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;click&#39;&lt;/span&gt;)
};

&lt;span class=&quot;hljs-comment&quot;&gt;// 只要带有函数，直接抛同步错误&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;// DOMException: () =&amp;gt; console.log(&#39;click&#39;) could not be cloned.&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; copy = &lt;span class=&quot;hljs-title function_&quot;&gt;structuredClone&lt;/span&gt;(objWithFunc); 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这就意味着，如果你要拷贝的是一个 Vue/React 的响应式组件实例，或者是带有业务方法的数据模型，绝对不能用它👋。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原型链的断裂&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不管你原本是一个通过 &lt;code&gt;class&lt;/code&gt; 实例化的多么高级的业务对象，经过 &lt;code&gt;structuredClone&lt;/code&gt; 的洗礼后，它都会变成一个普通的纯对象（Plain Object）。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-javascript&quot; lang=&quot;javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt; {
  &lt;span class=&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;name&lt;/span&gt;) { &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;name&lt;/span&gt; = name; }
  &lt;span class=&quot;hljs-title function_&quot;&gt;sayHi&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) { &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;hi&#39;&lt;/span&gt;); }
}

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; user = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&#39;前端&#39;&lt;/span&gt;);
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; cloneUser = &lt;span class=&quot;hljs-title function_&quot;&gt;structuredClone&lt;/span&gt;(user);

&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(cloneUser &lt;span class=&quot;hljs-keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// false &lt;/span&gt;
cloneUser.&lt;span class=&quot;hljs-title function_&quot;&gt;sayHi&lt;/span&gt;(); &lt;span class=&quot;hljs-comment&quot;&gt;// TypeError: cloneUser.sayHi is not a function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原型链上的所有方法全部丢失。它只关心纯粹的数据，不关心你的面向对象架构。‘&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-3&quot;&gt;需要时收藏起来⭐⭐⭐&lt;/h3&gt;
&lt;p&gt;这几年，前端的工具链卷得飞起，大家的 &lt;code&gt;package.json&lt;/code&gt; 越来越臃肿。遇到数组去重找库，遇到时间格式化找库，遇到深拷贝也要找库。&lt;/p&gt;
&lt;p&gt;如果你只是单纯地处理一些后端传过来的嵌套数据，或者表单的复杂配置结构，完全可以直接把 &lt;code&gt;structuredClone&lt;/code&gt; 敲在你的代码里。不用担心兼容性，目前主流浏览器（包括 Node.js）的支持率早就达到了工业级使用的标准了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/3b789e6ab1254003b8c24f3e4896f6d5~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgRXJwYW5PbWVy:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776823092&amp;amp;x-signature=4VK3Fxd1DXSMdFXAWeciAxaepfc%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;下次 Code Review 时，别再让我看到满屏的 &lt;code&gt;JSON.parse&lt;/code&gt; 了 （玩笑😁😁😁）。&lt;/p&gt;
&lt;p&gt;分享完毕，谢谢大家🙌&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/5fced56fc5ab4fa98df1ab9e1571e3b9~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgRXJwYW5PbWVy:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776823092&amp;amp;x-signature=MawtKAv8ftrh8CnSFrDED%2BMMdhY%3D&quot; alt=&quot;Suggestion.gif&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;</description><link>https://juejin.cn/post/7628489262722711590</link><guid isPermaLink="false">https://juejin.cn/post/7628489262722711590</guid><pubDate>Wed, 15 Apr 2026 01:58:12 GMT</pubDate><author>ErpanOmer</author><category></category><category>前端</category><category>JavaScript</category><category>Vue.js</category></item><item><title>GetX 之死 | 8 年从未用过，以后将不会再用</title><description>&lt;h4 data-id=&quot;heading-0&quot;&gt;1.一个时代的句号&lt;/h4&gt;
&lt;p&gt;2026 年的某一天，当你打开 &lt;code&gt;https://github.com/jonataslaw/getx&lt;/code&gt;，迎接你的不是那个熟悉的 10000+ star 的仓库，而是一个冰冷的 404 页面。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b8ed026a97074c46a961ffe6efb8033b~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.awebp#?w=1748&amp;amp;h=849&amp;amp;s=720039&amp;amp;e=png&amp;amp;b=f0d8ca&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;不只是仓库。作者 jonataslaw（Jonny Borges）的整个 GitHub 主页也 404 了。&lt;code&gt;get_cli&lt;/code&gt;、&lt;code&gt;get_server&lt;/code&gt;、&lt;code&gt;get_storage&lt;/code&gt;——整个 GetX 生态的所有仓库，一夜之间全部消失。&lt;/p&gt;
&lt;p&gt;没有告别信，没有迁移公告，没有&quot;项目已归档&quot;的标注。一个曾经是 Flutter 社区最具争议、也最广泛使用的第三方包，就这样无声无息地消失了。&lt;/p&gt;
&lt;p&gt;pub.dev 上的 &lt;code&gt;get&lt;/code&gt; 包还在——因为 pub.dev 是独立托管的，不会因为 GitHub 删库而下架。你的项目今天还能 &lt;code&gt;flutter pub get&lt;/code&gt;，还能编译，还能运行。但源码没了，issue 没了，786 个未关闭的 issue 没了，78 个待合并的 PR 没了，文档没了。&lt;/p&gt;
&lt;p&gt;一个没有源码仓库的包，和一具没有灵魂的躯壳有什么区别？&lt;/p&gt;
&lt;hr&gt;
&lt;h4 data-id=&quot;heading-1&quot;&gt;2.回顾：GetX 曾经有多火&lt;/h4&gt;
&lt;p&gt;在说&quot;为什么不用&quot;之前，先承认一个事实：GetX 确实火过。并且号称宇宙第一：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/af59ac8eaa1a4b5fb3c505875db07d67~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.awebp#?w=1320&amp;amp;h=586&amp;amp;s=387597&amp;amp;e=png&amp;amp;b=fefdfd&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub 10500+ stars，1600+ forks&lt;/li&gt;
&lt;li&gt;pub.dev 上曾经是 likes 数最高的 Flutter 包之一&lt;/li&gt;
&lt;li&gt;README 被翻译成十几种语言（日语、韩语、阿拉伯语、葡萄牙语……）&lt;/li&gt;
&lt;li&gt;大量教程、视频、课程围绕 GetX 构建&lt;/li&gt;
&lt;li&gt;在中文 Flutter 社区尤其流行，几乎每个技术群都有人推荐&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它火是有原因的。看这段代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-dart&quot; lang=&quot;dart&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 传统方式：定义 StatefulWidget，写 setState，管理生命周期&lt;/span&gt;
&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;CounterPage&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;StatefulWidget&lt;/span&gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-meta&quot;&gt;@override&lt;/span&gt;
  _CounterPageState createState() =&amp;gt; _CounterPageState();
}

&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;_CounterPageState&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;State&lt;/span&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot;&gt;CounterPage&lt;/span&gt;&amp;gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; count = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;;

  &lt;span class=&quot;hljs-meta&quot;&gt;@override&lt;/span&gt;
  Widget build(BuildContext context) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; Scaffold(
      body: Center(child: Text(&lt;span class=&quot;hljs-string&quot;&gt;&#39;&lt;span class=&quot;hljs-subst&quot;&gt;$count&lt;/span&gt;&#39;&lt;/span&gt;)),
      floatingActionButton: FloatingActionButton(
        onPressed: () =&amp;gt; setState(() =&amp;gt; count++),
        child: Icon(Icons.add),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-dart&quot; lang=&quot;dart&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// GetX 方式：三行搞定&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;final&lt;/span&gt; count = &lt;span class=&quot;hljs-number&quot;&gt;0.&lt;/span&gt;obs;

Obx(() =&amp;gt; Text(&lt;span class=&quot;hljs-string&quot;&gt;&#39;&lt;span class=&quot;hljs-subst&quot;&gt;$count&lt;/span&gt;&#39;&lt;/span&gt;));

onPressed: () =&amp;gt; count++;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于刚入门 Flutter 的开发者来说，这种简洁性是致命的吸引力。&quot;为什么要写那么多样板代码？GetX 三行就搞定了。&quot;&lt;/p&gt;
&lt;p&gt;但简洁和简单不是一回事。简洁是表面上代码少，简单是底层复杂度低。GetX 的代码少，但底层复杂度一点都不低——它只是把复杂度藏起来了。&lt;/p&gt;
&lt;hr&gt;
&lt;h4 data-id=&quot;heading-2&quot;&gt;3.我为什么 8 年从未用过 GetX&lt;/h4&gt;
&lt;p&gt;从 2018 年开始写 Flutter，到现在 8 年了。期间无数次被推荐 GetX，无数次在技术群里看到&quot;用 GetX 三行代码搞定&quot;的安利。但我一次都没用过。甚至我的群聊公告里都写着:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;本群禁止讨论 GetX,有任何问题可以向 GetX 官方提issue。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不是因为我有先见之明，而是因为它违反了我认为正确的几个原则。&lt;/p&gt;
&lt;h5 data-id=&quot;heading-3&quot;&gt;原则一：不要把所有鸡蛋放在一个篮子里&lt;/h5&gt;
&lt;p&gt;GetX 管了你的状态、路由、依赖注入、国际化、主题、HTTP 请求、本地存储、表单验证……一个包解决所有问题，听起来很美。&lt;/p&gt;
&lt;p&gt;但软件工程的基本常识是：&lt;strong&gt;耦合越紧，风险越大。&lt;/strong&gt; 来看一个真实的对比：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# 方案 A：每层独立&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;dependencies:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;provider:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;^6.0.0&lt;/span&gt;        &lt;span class=&quot;hljs-comment&quot;&gt;# 状态管理&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;go_router:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;^14.0.0&lt;/span&gt;      &lt;span class=&quot;hljs-comment&quot;&gt;# 路由&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;dio:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;^5.0.0&lt;/span&gt;             &lt;span class=&quot;hljs-comment&quot;&gt;# 网络请求&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;shared_preferences:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;^2.0.0&lt;/span&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;# 本地存储&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;intl:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;^0.19.0&lt;/span&gt;           &lt;span class=&quot;hljs-comment&quot;&gt;# 国际化&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# 方案 B：全家桶&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;dependencies:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;get:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;^4.7.3&lt;/span&gt;             &lt;span class=&quot;hljs-comment&quot;&gt;# 状态 + 路由 + DI + 网络 + 存储 + 国际化 + ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;方案 A 看起来依赖多，但每个依赖都是独立的。&lt;code&gt;dio&lt;/code&gt; 不维护了？换成 &lt;code&gt;http&lt;/code&gt; 包，其他四个完全不受影响。路由库想升级？只改路由层，状态管理一行不用动。&lt;/p&gt;
&lt;p&gt;方案 B 看起来清爽，只有一个依赖。但这个依赖出问题 = 所有东西出问题。&lt;/p&gt;
&lt;p&gt;今天，这个&quot;所有东西出问题&quot;的场景真的发生了。&lt;/p&gt;
&lt;h5 data-id=&quot;heading-4&quot;&gt;原则二：隐式依赖是技术债的温床&lt;/h5&gt;
&lt;p&gt;GetX 最大的卖点之一是&quot;不需要 context&quot;。&lt;code&gt;Get.find()&lt;/code&gt; 可以在代码的任何角落调用，不需要 Widget 树，不需要构造函数参数。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-dart&quot; lang=&quot;dart&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// GetX 的方式：在任何地方都能拿到任何东西&lt;/span&gt;
&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;OrderController&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;GetxController&lt;/span&gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; submitOrder() {
    &lt;span class=&quot;hljs-comment&quot;&gt;// 这三个依赖从哪来的？谁注册的？什么时候注册的？生命周期是什么？&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;final&lt;/span&gt; cart = Get.find&amp;lt;CartController&amp;gt;();
    &lt;span class=&quot;hljs-keyword&quot;&gt;final&lt;/span&gt; auth = Get.find&amp;lt;AuthController&amp;gt;();
    &lt;span class=&quot;hljs-keyword&quot;&gt;final&lt;/span&gt; api = Get.find&amp;lt;ApiService&amp;gt;();
    &lt;span class=&quot;hljs-comment&quot;&gt;// ...&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-dart&quot; lang=&quot;dart&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 官方推荐的方式：依赖通过构造函数显式传入&lt;/span&gt;
&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;OrderViewModel&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ChangeNotifier&lt;/span&gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-keyword&quot;&gt;final&lt;/span&gt; CartRepository _cartRepository;
  &lt;span class=&quot;hljs-keyword&quot;&gt;final&lt;/span&gt; AuthRepository _authRepository;

  &lt;span class=&quot;hljs-comment&quot;&gt;// 一眼就知道依赖了什么，测试时直接传 Fake&lt;/span&gt;
  OrderViewModel({
    &lt;span class=&quot;hljs-keyword&quot;&gt;required&lt;/span&gt; CartRepository cartRepository,
    &lt;span class=&quot;hljs-keyword&quot;&gt;required&lt;/span&gt; AuthRepository authRepository,
  }) : _cartRepository = cartRepository,
       _authRepository = authRepository;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写起来确实多了几行。但代价是什么？&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;&lt;code&gt;Get.find()&lt;/code&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;测试时 Mock 依赖&lt;/td&gt;&lt;td&gt;配置 GetX 全局容器&lt;/td&gt;&lt;td&gt;直接传 Fake 对象&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;code&gt;Get.find&amp;lt;T&amp;gt;()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;IDE 的&quot;Find Usages&quot;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;循环依赖检测&lt;/td&gt;&lt;td&gt;运行时才发现&lt;/td&gt;&lt;td&gt;编译时就报错&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;这不是&quot;风格偏好&quot;，这是&lt;strong&gt;编译时安全 vs 运行时崩溃&lt;/strong&gt;的根本差异。一个项目有 50 个 Controller，每个都用 &lt;code&gt;Get.find()&lt;/code&gt; 拿依赖，你敢重构吗？&lt;/p&gt;
&lt;h5 data-id=&quot;heading-5&quot;&gt;原则三：不要依赖个人英雄主义&lt;/h5&gt;
&lt;p&gt;GetX 本质上是一个人的项目。jonataslaw 一个人写了状态管理、路由、依赖注入、HTTP 客户端、国际化、主题……一个人维护这么大的范围，质量和持续性都无法保证。&lt;/p&gt;
&lt;p&gt;早在删库之前，信号就已经很明显了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2023 年 6 月&lt;/strong&gt;：社区开始问&quot;5.0 什么时候发布？&quot;（Issue #2797），至今没有正式版&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2023-2024 年&lt;/strong&gt;：超过 13 个月没有版本更新，停留在 4.6.5&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2025 年 1 月&lt;/strong&gt;：有人开了 Issue #3295 直接问&quot;GetX 还活着吗？&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;786 个未关闭的 issue&lt;/strong&gt;，78 个待合并的 PR——一个人根本处理不过来&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对比一下官方推荐的方案：&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;包&lt;/th&gt;&lt;th&gt;维护者&lt;/th&gt;&lt;th&gt;会因为一个人的决定消失吗？&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;provider&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Flutter 官方团队&lt;/td&gt;&lt;td&gt;不会&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;go_router&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Flutter 官方团队&lt;/td&gt;&lt;td&gt;不会&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ChangeNotifier&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Flutter 框架内置&lt;/td&gt;&lt;td&gt;不会&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Navigator&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Flutter 框架内置&lt;/td&gt;&lt;td&gt;不会&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;flutter_bloc&lt;/code&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;code&gt;riverpod&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Remi Rousselet（provider 作者）&lt;/td&gt;&lt;td&gt;低概率，且有社区 fork 能力&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;GetX&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;jonataslaw（一个人）&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;已经发生了&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h5 data-id=&quot;heading-6&quot;&gt;原则四：&quot;不需要 context&quot;不是优点，是危险信号&lt;/h5&gt;
&lt;p&gt;GetX 的宣传语之一是&quot;不需要 context 就能导航、弹对话框、显示 SnackBar&quot;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-dart&quot; lang=&quot;dart&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// GetX：不需要 context&lt;/span&gt;
Get.to(HomePage());
Get.snackbar(&lt;span class=&quot;hljs-string&quot;&gt;&#39;标题&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;内容&#39;&lt;/span&gt;);
Get.dialog(AlertDialog(...));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;听起来很方便。但 &lt;code&gt;context&lt;/code&gt; 在 Flutter 中不是累赘——它是 Widget 树的定位器，告诉框架&quot;我在哪里&quot;。绕过 context 意味着绕过 Flutter 的 Widget 生命周期管理，这会导致：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SnackBar 在页面已经销毁后还在显示&lt;/li&gt;
&lt;li&gt;对话框在错误的路由上弹出&lt;/li&gt;
&lt;li&gt;内存泄漏——Controller 没有跟随 Widget 生命周期释放&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GetX 用全局状态和静态方法绕过了 Flutter 的设计，短期内写起来爽，长期维护时各种幽灵 Bug 让你怀疑人生。&lt;/p&gt;
&lt;hr&gt;
&lt;h4 data-id=&quot;heading-7&quot;&gt;4.给正在用 GetX 的项目的建议&lt;/h4&gt;
&lt;p&gt;如果你的项目正在用 GetX，不要恐慌，但也不要心存侥幸。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;短期内你是安全的：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pub.dev 上的包不会因为 GitHub 删库而消失&lt;/li&gt;
&lt;li&gt;你的 &lt;code&gt;pubspec.lock&lt;/code&gt; 锁定了版本，&lt;code&gt;flutter pub get&lt;/code&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;
&lt;ul&gt;
&lt;li&gt;Flutter 下一个大版本更新（比如 Dart 4.0）可能导致 GetX 不兼容，没有人会修&lt;/li&gt;
&lt;li&gt;发现 Bug 没有人修，发现安全漏洞没有人补&lt;/li&gt;
&lt;li&gt;新入职的同事看到一个 404 的仓库链接，会对项目的技术选型产生严重怀疑&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 data-id=&quot;heading-8&quot;&gt;迁移路径&lt;/h5&gt;
&lt;p&gt;不要一次性重写。逐层替换，每次只动一个模块，每次都跑测试：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-scss&quot; lang=&quot;scss&quot;&gt;第一步：路由（影响范围最小，最先动手）
  Get&lt;span class=&quot;hljs-selector-class&quot;&gt;.toNamed&lt;/span&gt;(&#39;/home&#39;)  →  GoRouter 的声明式路由
  Get&lt;span class=&quot;hljs-selector-class&quot;&gt;.back&lt;/span&gt;()            →  context&lt;span class=&quot;hljs-selector-class&quot;&gt;.pop&lt;/span&gt;()
  GetPage              →  GoRoute

第二步：依赖注入（把隐式改成显式）
  Get&lt;span class=&quot;hljs-selector-class&quot;&gt;.put&lt;/span&gt;(MyController())     →  &lt;span class=&quot;hljs-built_in&quot;&gt;ChangeNotifierProvider&lt;/span&gt;(create: (_) =&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;MyViewModel&lt;/span&gt;())
  Get&lt;span class=&quot;hljs-selector-class&quot;&gt;.find&lt;/span&gt;&amp;lt;MyController&amp;gt;()    →  context&lt;span class=&quot;hljs-selector-class&quot;&gt;.read&lt;/span&gt;&amp;lt;MyViewModel&amp;gt;()
  Get&lt;span class=&quot;hljs-selector-class&quot;&gt;.lazyPut&lt;/span&gt;(...)            →  Provider 的 lazy 默认行为

第三步：状态管理（工作量最大，收益也最大）
  GetxController + &lt;span class=&quot;hljs-selector-class&quot;&gt;.obs&lt;/span&gt; + Obx  →  ChangeNotifier + Consumer / context&lt;span class=&quot;hljs-selector-class&quot;&gt;.watch&lt;/span&gt;
  &lt;span class=&quot;hljs-built_in&quot;&gt;update&lt;/span&gt;()                     →  &lt;span class=&quot;hljs-built_in&quot;&gt;notifyListeners&lt;/span&gt;()
  GetBuilder                   →  Consumer

第四步：其他零散功能
  GetConnect        →  dio / http
  GetX 国际化       →  flutter_localizations + ARB 文件
  GetX 主题切换     →  ThemeData + ThemeMode
  Get&lt;span class=&quot;hljs-selector-class&quot;&gt;.snackbar&lt;/span&gt;()    →  ScaffoldMessenger&lt;span class=&quot;hljs-selector-class&quot;&gt;.of&lt;/span&gt;(context)&lt;span class=&quot;hljs-selector-class&quot;&gt;.showSnackBar&lt;/span&gt;()
  Get&lt;span class=&quot;hljs-selector-class&quot;&gt;.dialog&lt;/span&gt;()      →  &lt;span class=&quot;hljs-built_in&quot;&gt;showDialog&lt;/span&gt;(context: context, ...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每完成一步，跑一遍测试，确认没有回归。如果你的项目没有测试——这是另一个需要补的债，而且优先级比迁移 GetX 更高。&lt;/p&gt;
&lt;h5 data-id=&quot;heading-9&quot;&gt;迁移的工作量评估&lt;/h5&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;项目规模&lt;/th&gt;&lt;th&gt;GetX 使用深度&lt;/th&gt;&lt;th&gt;预估迁移时间&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;小项目（10-20 个页面）&lt;/td&gt;&lt;td&gt;只用了状态管理&lt;/td&gt;&lt;td&gt;1-2 周&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;中项目（30-50 个页面）&lt;/td&gt;&lt;td&gt;状态 + 路由 + DI&lt;/td&gt;&lt;td&gt;2-4 周&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;大项目（100+ 个页面）&lt;/td&gt;&lt;td&gt;全家桶深度使用&lt;/td&gt;&lt;td&gt;1-3 个月&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;如果是大项目，建议新功能用新方案写，老功能逐步迁移，不要停下业务开发来专门做迁移。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-10&quot;&gt;5.更深层的教训&lt;/h4&gt;
&lt;p&gt;GetX 之死不是个案。它揭示了开源生态中一个结构性的风险：&lt;strong&gt;社区的繁荣可以掩盖项目的脆弱性。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;10000+ star、几十种语言的 README 翻译、数百个教程视频——这些都是社区繁荣的表现。但项目的健康度不取决于 star 数，而取决于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心维护者有几个人？（GetX：1 个）&lt;/li&gt;
&lt;li&gt;有没有组织/公司背书？（GetX：没有）&lt;/li&gt;
&lt;li&gt;issue 响应速度如何？（GetX：786 个未关闭）&lt;/li&gt;
&lt;li&gt;版本发布频率如何？（GetX：5.0 难产三年）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下次选择一个第三方包时，不要只看 star 数和 likes。打开 GitHub 仓库，看看：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Contributors 页面&lt;/strong&gt;——核心贡献者超过 3 个人吗？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最近的 commit&lt;/strong&gt;——最后一次提交是什么时候？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Issue 列表&lt;/strong&gt;——维护者有在回复吗？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Release 历史&lt;/strong&gt;——版本发布有规律吗？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果一个包只有一个核心维护者、半年没有 commit、几百个未关闭的 issue——不管它有多少 star，都要三思。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-11&quot;&gt;5. 2026 年，Flutter 状态管理该怎么选&lt;/h4&gt;
&lt;p&gt;GetX 退场后，Flutter 状态管理的格局更清晰了：&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;th&gt;维护状况&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;provider&lt;/code&gt; + &lt;code&gt;ChangeNotifier&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Flutter 官方推荐的入门方案&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;code&gt;Riverpod&lt;/code&gt;&lt;/td&gt;&lt;td&gt;provider 的进化版，编译时安全&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;code&gt;flutter_bloc&lt;/code&gt; / &lt;code&gt;Cubit&lt;/code&gt;&lt;/td&gt;&lt;td&gt;企业级方案，严格的单向数据流&lt;/td&gt;&lt;td&gt;大型项目、需要审计追踪的场景&lt;/td&gt;&lt;td&gt;社区活跃，多人维护&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;setState&lt;/code&gt;&lt;/td&gt;&lt;td&gt;最简单的方式&lt;/td&gt;&lt;td&gt;临时状态、原型开发&lt;/td&gt;&lt;td&gt;框架内置，永远不会消失&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;没有 GetX。以后也不会有 GetX。&lt;/p&gt;
&lt;h4 data-id=&quot;heading-12&quot;&gt;6. 写在最后&lt;/h4&gt;
&lt;p&gt;8 年前我选择不用 GetX，不是因为预见了今天。是因为几个朴素的判断：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个人维护的全家桶，风险太高&lt;/li&gt;
&lt;li&gt;隐式依赖写起来爽，维护起来痛&lt;/li&gt;
&lt;li&gt;绕过框架设计的&quot;便利&quot;，迟早要还债&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今天这些判断被验证了。但我并不觉得高兴——毕竟有大量的项目和开发者受到了影响。那些用 GetX 写了几万行代码的团队，现在面对的是一个没有源码、没有维护者、没有未来的核心依赖。&lt;/p&gt;
&lt;p&gt;如果你正在选择 Flutter 的技术栈，记住一条原则：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;每一层都应该可以独立替换。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;状态管理、路由、依赖注入、网络请求——每一层用独立的方案，每一层都有替代品。这样无论哪一层出问题，你都只需要换那一层，而不是重写整个应用。&lt;/p&gt;
&lt;p&gt;这不是过度设计，这是基本的风险管理。&lt;/p&gt;
&lt;p&gt;GetX 教会了我们：在开源世界里，&lt;strong&gt;便利是借来的，风险是自己的。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;笔者目前正在公众号 &lt;strong&gt;编程之王&lt;/strong&gt; 编写并发布《Flutter Agent Skills 全解析》系列，覆盖 Flutter 官方推荐的 22 个开发技能。如果你对 Flutter 官方推荐的架构、状态管理、测试等最佳实践感兴趣，欢迎关注。&lt;/p&gt;</description><link>https://juejin.cn/post/7628535637260959770</link><guid isPermaLink="false">https://juejin.cn/post/7628535637260959770</guid><pubDate>Tue, 14 Apr 2026 23:55:31 GMT</pubDate><author>张风捷特烈</author><category></category><category>Android</category><category>Flutter</category><category>前端</category></item><item><title>我装了 30 多个 Claude Code Skill，每天真开的就 6 个</title><description>&lt;p&gt;用过Claude code 的都知道，在Claude code 里没打开marketplace的时候里面琳琅满目的Skill根本看不完。我前后装了 30 多个skill,跑了两个月，最后留下的只有这六个。&lt;/p&gt;
&lt;p&gt;剩下哪些skill，基本上都是新鲜感装完了就好了。因为最主要的问题不是它们不好——而是&lt;strong&gt;装太多本身就是坑&lt;/strong&gt;，根据情况触发正确的准确率会直接掉到 50% 以下。Claude 得扫所有 skill 的描述决定用哪个，描述一多它就开始乱猜。官方建议创作者合理持有量 20 到 30 个，而且得是&lt;strong&gt;贴你自己工作流&lt;/strong&gt;的那种。&lt;/p&gt;
&lt;p&gt;这篇讲我筛剩下的 6 个。3 个通用，3 个创作。不讲原理，只讲每个放工作流哪一段、我为什么选他们的原因，替你节省判断和挑选的时间。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/1987d17f9ae24267912f1f15405cc840~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5bCP5aKo5ZCM5a2mYm95:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776773980&amp;amp;x-signature=wxRk5BV8344jgdRywiubUBZNNnE%3D&quot; alt=&quot;Gemini_Generated_Image_46ndlg46ndlg46nd(1).webp&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-0&quot;&gt;Skill 到底是啥，顺便把&quot;必装清单&quot;这个坑点掉&lt;/h2&gt;
&lt;p&gt;Skill 就是一个 &lt;code&gt;SKILL.md&lt;/code&gt; 加几个参考资料，按需加载。关键词是&lt;strong&gt;按需&lt;/strong&gt;——不用的时候不占上下文，用的时候才拉全文。这个机制叫渐进式披露，你要是一上来把所以相关的内容全塞主文件里，那这样上下文全是你的skill，根本就没办法判断到底该使用什么skill。&lt;/p&gt;
&lt;p&gt;找 skill 的入口有几个:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Claude Code 里跑 &lt;code&gt;/plugin marketplace&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聚合站 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fskillsmpp.com%2F&quot; target=&quot;_blank&quot; title=&quot;https://skillsmpp.com/&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;skillsmpp.com&lt;/a&gt;,号称收录了 96,000+ 个&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fskillhub.club%2F&quot; target=&quot;_blank&quot; title=&quot;https://skillhub.club/&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;skillhub.club&lt;/a&gt;,精选路线&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GitHub 上的 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Ftravisvn%2Fawesome-claude-skills&quot; target=&quot;_blank&quot; title=&quot;https://github.com/travisvn/awesome-claude-skills&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;awesome-claude-skills&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但清单里的&quot;必装 Top 30&quot;基本都不能抄。别人的工作流不等于你的。我筛的时候只问一个问题:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;它能不能替我每天省掉一步手动动作?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;答不上来就不装。比任何评分都管用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/41c60af224b7403da74064c2c227edd3~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5bCP5aKo5ZCM5a2mYm95:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776773980&amp;amp;x-signature=cjxRhYnjUVq5gPR5gQEtaimmXVE%3D&quot; alt=&quot;iShot_2026-04-14_19.09.35.webp&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-1&quot;&gt;通用 3 个:不管你写博客还是跑项目，都用得上&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-2&quot;&gt;1. &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fanthropics%2Fskills&quot; target=&quot;_blank&quot; title=&quot;https://github.com/anthropics/skills&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;Skill Creator&lt;/a&gt;(官方)&lt;/h3&gt;
&lt;p&gt;元技能，主动问你流程怎么跑的，然后帮你写出 &lt;code&gt;SKILL.md&lt;/code&gt;,连测试用例都生成好。&lt;/p&gt;
&lt;p&gt;我排它第一的理由很直接:你装的其他 skill,最后都要能自己造。在素材库里 &lt;strong&gt;10 多个独立来源&lt;/strong&gt;都把它放在起点，几乎所有教程开篇都是它。&lt;/p&gt;
&lt;p&gt;典型指令就一句——&quot;把我昨天手动跑的选题流程打包成 skill&quot;。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-3&quot;&gt;2. &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FOthmanAdi%2Fplanning-with-files&quot; target=&quot;_blank&quot; title=&quot;https://github.com/OthmanAdi/planning-with-files&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;Planning with Files&lt;/a&gt;(社区)&lt;/h3&gt;
&lt;p&gt;写长文你肯定有过这种经历,Claude 一开始答应得好好的,结果写到三千字左右就开始自己发挥了,原来说好的结构慢慢就跑偏了,连你一开头交代过的品牌调性它也会忘。&lt;/p&gt;
&lt;p&gt;这玩意就是来修这个坑的。它会强制 Claude 动手之前先写一份 &lt;code&gt;task_plan.md&lt;/code&gt;,然后每做两步就回头更新一下 &lt;code&gt;findings.md&lt;/code&gt; 和 &lt;code&gt;progress.md&lt;/code&gt;。说白了就是给它外挂了一块硬盘记事,这样对话再长,它也不会忘自己在干什么。&lt;/p&gt;
&lt;p&gt;顺便说一下,它在 GitHub 上拿了 &lt;strong&gt;13,410 Stars&lt;/strong&gt;,是目前 skill 生态里 Star 数最高的社区 skill。开发圈对它的评价比创作者圈高很多,但我觉得其实写长文一样会踩&quot;写到后面忘开头&quot;这个坑,你别被&quot;开发向&quot;三个字劝退。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-4&quot;&gt;3. &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fanthropics%2Fskills&quot; target=&quot;_blank&quot; title=&quot;https://github.com/anthropics/skills&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;Document &amp;amp; Presentation Skills&lt;/a&gt;(官方)&lt;/h3&gt;
&lt;p&gt;PDF、Word、Excel、PowerPoint 全家桶,Anthropic 官方做的。说实话写代码的人用它可能没啥感觉,但每天都在写东西的人,这玩意能省的活真的有点多。&lt;/p&gt;
&lt;p&gt;我自己用得最多的就是把 PDF 转幻灯片。收到一份 23 页的行业报告 PDF,我让它把里面的关键数据抽出来做一份 10 页的 slide,顺便套我自己的品牌色。这种事以前我得开 3 个软件来回切来切去折腾大半天,现在一句话就完事。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/efdb81737cb94c459fcd1f37de623a89~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5bCP5aKo5ZCM5a2mYm95:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776773980&amp;amp;x-signature=hlFtFHThjdc1xyNRf4wAfTMChcc%3D&quot; alt=&quot;Gemini_Generated_Image_i3k6s7i3k6s7i3k6(1).webp&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-5&quot;&gt;创作 3 个:每天写东西绕不开&lt;/h2&gt;
&lt;p&gt;下面这 3 个我挑的时候标准其实就一个——&lt;strong&gt;哪个一次调用能替我干最多的活,我就留哪个&lt;/strong&gt;。辅助能力越强,每天我开的次数就越多。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-6&quot;&gt;4. &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DyLXLHnD4fco&quot; target=&quot;_blank&quot; title=&quot;https://www.youtube.com/watch?v=yLXLHnD4fco&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;SEO Blog Writer &amp;amp; Lead Magnet&lt;/a&gt;(社区)&lt;/h3&gt;
&lt;p&gt;这个一次能跑通 3 件事:先做一轮 Ahrefs 关键词研究,然后写一篇结构完整的 SEO 长文,最后再把长文里的干货抽出来排成一份 &lt;strong&gt;11 页 PDF 引流手册&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;写过博客的人都知道这条链有多费劲。一篇长文本身已经够累,你写完还要开 Canva 或者 Figma 手搓一份 PDF 诱饵,光排版又是两三个小时。现在三步合成一步,你只管定选题就行。&lt;/p&gt;
&lt;p&gt;说实话我留这个 skill 不是图它省时间那层。真正让我觉得它值得留下来的,是它把&lt;strong&gt;邮件诱饵&lt;/strong&gt;这件事从&quot;下次再做&quot;变成了&quot;顺手就做&quot;。博客最亏的一环就是读者看完之后走了,你连个邮箱都收不到。但你如果有 PDF 可以送,起码还能留一个联系方式回来。以前我懒得专门为每篇文章做一份诱饵,现在这一步直接折进主流程,我也没什么借口不做了。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-7&quot;&gt;5. &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DIp566JVP_30&quot; target=&quot;_blank&quot; title=&quot;https://www.youtube.com/watch?v=Ip566JVP_30&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;Newsletter Automation&lt;/a&gt;(社区)&lt;/h3&gt;
&lt;p&gt;这个更狠一点,一条指令就能跑完整条 Newsletter 运营链。&lt;/p&gt;
&lt;p&gt;具体是这么个流程:它会先让 Perplexity 去查今天的行业动态,摘 5 到 8 条要点出来,然后按你提前喂过的模板套 HTML 排版,Mailchimp 和 Substack 的样式都能直接用。配图部分你可以接 Nano Banana Pro 让它自己画,也可以直接抓你自己图床里现成的图。最后一步是存到 Gmail 草稿箱,你打开邮箱就能看到一封已经排好版的草稿躺在那,扫一眼没问题点发送就完事了。&lt;/p&gt;
&lt;p&gt;这玩意已经不算辅助写作了,是直接替你不用写。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意:这个 skill 跑起来要接 Perplexity API 和 Gmail MCP,前置比前面几个重一点,但你配置一次之后就是一劳永逸。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;独立做 Newsletter 以前是真纠结——每天一两个小时收集信息、写稿、排版,成本高得吓人,你做一个月可能就想放弃了。我自己之前也被这个门槛拦过好几次。装完之后门槛直接砍到每天 10 分钟审一遍稿就行,我第一次看真实演示的时候心里真的有点&quot;运营岗要凉了&quot;的感觉。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-8&quot;&gt;6. &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DsduaTkhIm_w&quot; target=&quot;_blank&quot; title=&quot;https://www.youtube.com/watch?v=sduaTkhIm_w&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;Content Repurposer&lt;/a&gt;(社区)&lt;/h3&gt;
&lt;p&gt;解决的是多平台分发的问题。读一篇博客长文或者视频逐字稿,它可以并行生成 Twitter 线程、LinkedIn 动态、Newsletter 开头段。素材里 &lt;strong&gt;4 到 5 个来源&lt;/strong&gt;都点名推荐了它,是非编程类 skill 里被提得最多的一个。&lt;/p&gt;
&lt;p&gt;你可能会觉得,这种事直接让 Claude 改写一下不就完了?我以前也是这么想的。但你真跑过一次就知道差别在哪——三个平台的语气、节奏、钩子完全是不同的逻辑。Twitter 第一条必须是强钩子,LinkedIn 开头要像同事在说话,Newsletter 头段又得像老朋友给你写信。你手动&quot;改写&quot;的时候其实是在重写三遍。&lt;/p&gt;
&lt;p&gt;它跟直接让 Claude 改写最大的区别,是它会先去读你过去发过的帖子,把你自己的语气特征先抓出来,然后再往各平台的语感上套。&lt;/p&gt;
&lt;p&gt;一个人运营多平台最容易在分发这一步放弃,就是因为越做越像流水线工人。装完之后整条分发链从半小时直接压到一分钟。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;延伸阅读:&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fwww.legacyvps.com%2Farchives%2Fobsidian-claude-custom-ai-writing-workflow&quot; target=&quot;_blank&quot; title=&quot;https://www.legacyvps.com/archives/obsidian-claude-custom-ai-writing-workflow&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;别让 AI 写得像 AI:用自己的 83 篇博客训练专属写作助手,顺手做成了一个 Skill&lt;/a&gt; ——创作向 skill 光装不够,还得拿自己写过的东西训一轮才真贴你的语气。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-9&quot;&gt;我的一些建议&lt;/h2&gt;
&lt;p&gt;其实别把 skill 想得太复杂,先装一个跑一次,比看十篇推荐文都管用。&lt;/p&gt;
&lt;p&gt;前面讲的 6 个已经够具体,你直接照着装就行。唯一要提醒的就一条:&lt;strong&gt;别一上来装 50 个&lt;/strong&gt;。素材里有个说法叫 &quot;Setup Porn&quot;,意思就是花好几个小时配一堆 skill,结果什么内容都没产出,本质就是拿配置当拖延借口。手动跑同一个任务 3 次以上,再让 Skill Creator 帮你打包,不要反过来。&lt;/p&gt;
&lt;p&gt;按场景选会更省事:&lt;/p&gt;
&lt;p&gt;你每天在 Claude 里反复跑同一串动作,先装 &lt;code&gt;Skill Creator&lt;/code&gt;。 你写长内容经常写到后半段就忘了开头设定,装 &lt;code&gt;Planning with Files&lt;/code&gt;。 你写博客、公众号想一份长稿出多平台,装 &lt;code&gt;Content Repurposer&lt;/code&gt;。 你才刚点进 Claude Code,连 Skills 开关都没开过,先去 Settings → Capabilities → Skills 打开,顺手把 &lt;code&gt;skill-creator&lt;/code&gt; 也开上。&lt;/p&gt;
&lt;p&gt;前面装得少,Claude 的触发准确率才稳得住,又回到开头那个 50% 的数据。别跟这条反着来。&lt;/p&gt;</description><link>https://juejin.cn/post/7628449448600600603</link><guid isPermaLink="false">https://juejin.cn/post/7628449448600600603</guid><pubDate>Tue, 14 Apr 2026 12:19:40 GMT</pubDate><author>小墨同学boy</author><category></category><category>AIGC</category></item><item><title>周下载60w，但是作者删库！我从本地 pub 缓存里把它救出来，顺手备份到了自己的 GitHub</title><description>&lt;h2 data-id=&quot;heading-0&quot;&gt;经过&lt;/h2&gt;
&lt;p&gt;今天在社区闲逛时，顺手看了一眼群里，说 &lt;code&gt;get&lt;/code&gt;作者删库跑路了！我最初保持怀疑的态度，打开&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fpub.dev%2Fpackages%2Fget&quot; target=&quot;_blank&quot; title=&quot;https://pub.dev/packages/get&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;pub&lt;/a&gt;地址还能打开，&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/b2e58bc4395e497faa62bfac46fcf7b2~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5bCP5p6X55qE57yW56iL5byA5Y-R5pel6K6w:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776756196&amp;amp;x-signature=ll9Kfe%2FRnFRMWq5xEv2FvMhruc0%3D&quot; alt=&quot;screenshot-20260414-150141.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;我看到时心里想:(这库的周下载逼近60w，收获了15.5k的star，而且&lt;code&gt;getx&lt;/code&gt;是flutter四大状态管理仓库之一，时至今日，还有很多很多用户，我自己做项目，也在拿&lt;code&gt;getx&lt;/code&gt;以及&lt;code&gt;provier&lt;/code&gt;、&lt;code&gt;riverpod&lt;/code&gt;、&lt;code&gt;bloc&lt;/code&gt;做技术选型，横向对比...)&lt;/p&gt;
&lt;p&gt;跑路了相当于&lt;code&gt;vue&lt;/code&gt;生态中的&lt;code&gt;vuex&lt;/code&gt;、&lt;code&gt;pinia&lt;/code&gt;跑路，太BT了...&lt;/p&gt;
&lt;p&gt;当我再打开&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fjonataslaw%2Fgetx&quot; target=&quot;_blank&quot; title=&quot;https://github.com/jonataslaw/getx&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github&lt;/a&gt;，迎接我的就是&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/d3bba3b0d1d9467eac8965f0a513bee0~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5bCP5p6X55qE57yW56iL5byA5Y-R5pel6K6w:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776756196&amp;amp;x-signature=qrRN2joms41D5qeGeVGLpTqVu4c%3D&quot; alt=&quot;screenshot-20260414-145938.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;我心中大喊不妙，这种时候最怕的不是“今天能不能跑”，而是过一阵子你换电脑、CI 重装、同事新拉项目的时候，大家突然开始补依赖、找源码、翻缓存，最后一圈人全卡住。&lt;/p&gt;
&lt;p&gt;我这边的处理方式很简单：既然本地还能跑，那就先把本机已经缓存下来的完整包捞出来，备份到自己的 GitHub，至少后面有个稳定来源。&lt;/p&gt;
&lt;p&gt;我已经把这份 &lt;code&gt;get 4.7.3&lt;/code&gt; 传到了这里：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://github.com/xinqingaa/get-4.7.3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果你们团队里有人懒得自己再走一遍“翻缓存、复制目录、初始化 Git、上传仓库”这套流程，其实可以直接用我这个远程地址，先把项目跑起来再说。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-1&quot;&gt;先说结论&lt;/h2&gt;
&lt;p&gt;这里有个点得先说清楚：&lt;/p&gt;
&lt;p&gt;GitHub 仓库 404，不等于 &lt;code&gt;pub.dev&lt;/code&gt; 上的 hosted 包马上就拉不下来了。&lt;/p&gt;
&lt;p&gt;只要 &lt;code&gt;pub.dev&lt;/code&gt; 上这个版本还在，你项目里原来写的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;get:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;^4.6.6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很多时候依然是可以正常 &lt;code&gt;flutter pub get&lt;/code&gt; 的。&lt;/p&gt;
&lt;p&gt;所以我这次做这件事，不是因为“今天已经彻底装不上了”，而是因为源码仓库、历史 issue、后续修补、团队兜底这些事情，一下子都变得不确定了。&lt;/p&gt;
&lt;p&gt;说白了，我不是在抢救一个已经完全消失的包，我是在给未来留后路。&lt;/p&gt;
&lt;p&gt;这次我主要确认了几件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;项目里虽然写的是 &lt;code&gt;get: ^4.6.6&lt;/code&gt;，但我本机实际锁定到的是 &lt;code&gt;4.7.3&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;本地 &lt;code&gt;pub&lt;/code&gt; 缓存里确实还有完整源码，路径在 &lt;code&gt;~/.pub-cache/hosted/pub.dev/get-4.7.3&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flutter clean&lt;/code&gt; 不会把全局的 &lt;code&gt;pub&lt;/code&gt; 缓存删掉。&lt;/li&gt;
&lt;li&gt;真正决定后面依赖从哪里拉的，不是 &lt;code&gt;flutter clean&lt;/code&gt;，而是 &lt;code&gt;pubspec.yaml&lt;/code&gt; 里到底写的是 &lt;code&gt;hosted&lt;/code&gt; 依赖还是 &lt;code&gt;git&lt;/code&gt; 依赖。&lt;/li&gt;
&lt;li&gt;真正能证明“依赖来源切换成功”的，不是口头说改了配置，而是 &lt;code&gt;pubspec.lock&lt;/code&gt; 里的 &lt;code&gt;source&lt;/code&gt; 会从 &lt;code&gt;hosted&lt;/code&gt; 变成 &lt;code&gt;git&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个区别其实挺关键的。&lt;/p&gt;
&lt;p&gt;因为很多人看到项目出问题，第一反应就是先 &lt;code&gt;flutter clean&lt;/code&gt;，但它干的事情和“依赖从哪里下载”其实不是一回事。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-2&quot;&gt;pub 缓存到底是个什么东西&lt;/h2&gt;
&lt;p&gt;Flutter 项目里只要你执行过 &lt;code&gt;flutter pub get&lt;/code&gt;，依赖一般都会被下载到本机的 &lt;code&gt;pub&lt;/code&gt; 缓存目录里。&lt;/p&gt;
&lt;p&gt;macOS 下常见路径就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;~/.pub-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;像这种从 &lt;code&gt;pub.dev&lt;/code&gt; 拉下来的包，通常会放在：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;~/.pub-cache/hosted/pub.dev/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比如我这次找到的就是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;/Users/lrq/.pub-cache/hosted/pub.dev/get-4.7.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个目录里不是一个“壳子”，而是完整包内容，里面能看到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lib&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;test&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;example&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;documentation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pubspec.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LICENSE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，只要你本机缓存还在，很多时候就还有机会把包完整救出来。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-3&quot;&gt;我这次为什么还能把 get 捞出来&lt;/h2&gt;
&lt;p&gt;因为我的项目之前已经跑过，这个包已经被下载到本地了。&lt;/p&gt;
&lt;p&gt;哪怕现在原作者仓库不稳定，或者你就是单纯不想继续依赖外部不确定因素，只要本机缓存里还留着这份代码，就还能复制出来自己维护。&lt;/p&gt;
&lt;p&gt;我这边最后确认到的版本不是 &lt;code&gt;4.6.6&lt;/code&gt;，而是 &lt;code&gt;4.7.3&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这个也很正常。&lt;/p&gt;
&lt;p&gt;因为 &lt;code&gt;pubspec.yaml&lt;/code&gt; 里写的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;get:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;^4.6.6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这表示允许拿 &lt;code&gt;4.x&lt;/code&gt; 范围内兼容的更新版本。只要锁文件已经解析到了 &lt;code&gt;4.7.3&lt;/code&gt;，项目实际跑起来用的就是 &lt;code&gt;4.7.3&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以这次我没有死磕 &lt;code&gt;4.6.6&lt;/code&gt;，而是直接把本机正在用的 &lt;code&gt;4.7.3&lt;/code&gt; 备份走了。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-4&quot;&gt;我是怎么备份到 GitHub 的&lt;/h2&gt;
&lt;p&gt;做法其实不复杂。&lt;/p&gt;
&lt;p&gt;第一步，不要直接在 &lt;code&gt;~/.pub-cache&lt;/code&gt; 里改东西。这个目录本质上还是缓存目录，不适合拿来当你自己的长期仓库。&lt;/p&gt;
&lt;p&gt;我先把它复制到一个自己维护的位置，比如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; -p /Users/lrq/work/vendor
&lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; -R /Users/lrq/.pub-cache/hosted/pub.dev/get-4.7.3 /Users/lrq/work/vendor/get-4.7.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二步，在复制出来的新目录里初始化 Git：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; /Users/lrq/work/vendor/get-4.7.3
git init
git checkout -b main
git add .
git commit -m &lt;span class=&quot;hljs-string&quot;&gt;&quot;chore: import get 4.7.3 from local pub cache&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第三步，推到自己的 GitHub：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;git remote add origin https://github.com/xinqingaa/get-4.7.3.git
git push -u origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样做完之后，这个包就不只是“还躺在我电脑缓存里”，而是已经有了一个我自己能控制的远程仓库。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-5&quot;&gt;如果大家都懒得操作，直接用我的远程地址也行&lt;/h2&gt;
&lt;p&gt;这个是最省事的办法。&lt;/p&gt;
&lt;p&gt;如果你们现在只是想先把项目跑起来，不想每个人都再自己去翻 &lt;code&gt;pub cache&lt;/code&gt;、复制源码、传一份仓库，那可以直接把依赖切到我这个地址：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://github.com/xinqingaa/get-4.7.3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pubspec.yaml&lt;/code&gt; 可以这么写：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;dependencies:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;get:&lt;/span&gt;
    &lt;span class=&quot;hljs-attr&quot;&gt;git:&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;url:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;https://github.com/xinqingaa/get-4.7.3.git&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;ref:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你们希望再稳一点，不想后面有人往 &lt;code&gt;main&lt;/code&gt; 推了东西影响现有项目，那就别写 &lt;code&gt;main&lt;/code&gt;，直接写具体提交号：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;dependencies:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;get:&lt;/span&gt;
    &lt;span class=&quot;hljs-attr&quot;&gt;git:&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;url:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;https://github.com/xinqingaa/get-4.7.3.git&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;ref:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;具体commit_sha&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个方式更适合线上项目或者多人协作。&lt;/p&gt;
&lt;p&gt;因为一旦写死 commit，后面就算仓库继续更新，你当前项目也不会跟着乱动。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-6&quot;&gt;这里顺便说清楚：&lt;code&gt;flutter clean&lt;/code&gt; 到底是干嘛的&lt;/h2&gt;
&lt;p&gt;很多人会把 &lt;code&gt;flutter clean&lt;/code&gt; 理解成“把依赖也一起清掉”，其实不是。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flutter clean&lt;/code&gt; 的主要作用，是清理&lt;strong&gt;当前项目目录&lt;/strong&gt;下面的构建产物和临时文件。&lt;/p&gt;
&lt;p&gt;它更像是在对当前工程说一句：&lt;br&gt;
“你之前编译留下来的东西先都别要了，等会儿我重新来一遍。”&lt;/p&gt;
&lt;p&gt;一般它会影响这些东西：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;build/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dart_tool/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;一些 Flutter 构建过程中生成的中间文件&lt;/li&gt;
&lt;li&gt;平台侧部分临时产物&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但有一点一定要注意：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flutter clean&lt;/code&gt; 默认&lt;strong&gt;不会删除全局的 &lt;code&gt;~/.pub-cache&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是说，你执行完 &lt;code&gt;flutter clean&lt;/code&gt; 之后：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;项目本地的构建状态会被清掉&lt;/li&gt;
&lt;li&gt;但你之前下载过的依赖缓存，通常还在&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以它不会直接把我刚才说的 &lt;code&gt;get-4.7.3&lt;/code&gt; 这个缓存目录删掉。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-7&quot;&gt;那现在执行 &lt;code&gt;flutter clean&lt;/code&gt;，具体会发生什么&lt;/h2&gt;
&lt;p&gt;如果你当前项目还是正常状态，这时候执行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;flutter clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你大概率会看到下面这种结果：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当前项目下的 &lt;code&gt;build&lt;/code&gt; 目录被清掉。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dart_tool&lt;/code&gt; 下面很多重新生成用的文件被清掉。&lt;/li&gt;
&lt;li&gt;下一次你再跑项目，Flutter 会重新做一轮初始化和构建。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;~/.pub-cache/hosted/pub.dev/get-4.7.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个目录通常还在。&lt;/p&gt;
&lt;p&gt;所以从“救包”这个角度说，&lt;code&gt;flutter clean&lt;/code&gt; 不是最危险的操作。&lt;/p&gt;
&lt;p&gt;真正危险的是你把本地缓存手动删了，或者换了新机器，而你又没有自己的备份来源。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-8&quot;&gt;那现在执行 &lt;code&gt;flutter pub get&lt;/code&gt;，又会发生什么&lt;/h2&gt;
&lt;p&gt;这个要分情况看。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-9&quot;&gt;情况一：你还没改依赖，项目里还是这样写&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;get:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;^4.6.6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那 &lt;code&gt;flutter pub get&lt;/code&gt; 做的事情大概是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读取 &lt;code&gt;pubspec.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;结合 &lt;code&gt;pubspec.lock&lt;/code&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;code&gt;.dart_tool/package_config.json&lt;/code&gt; 之类的文件&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果你的 &lt;code&gt;pubspec.lock&lt;/code&gt; 里已经锁的是 &lt;code&gt;get 4.7.3&lt;/code&gt;，而且本地缓存也还在，那这一步通常不会有什么大动静，很多时候就是直接复用本地缓存。&lt;/p&gt;
&lt;p&gt;换句话说，&lt;code&gt;flutter clean&lt;/code&gt; 之后再 &lt;code&gt;flutter pub get&lt;/code&gt;，项目大概率还是能恢复起来。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-10&quot;&gt;情况二：你已经把依赖改成 GitHub 仓库&lt;/h3&gt;
&lt;p&gt;比如你改成了：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;dependencies:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;get:&lt;/span&gt;
    &lt;span class=&quot;hljs-attr&quot;&gt;git:&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;url:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;https://github.com/xinqingaa/get-4.7.3.git&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;ref:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那这时候再执行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;flutter pub get
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;事情就变成另外一种逻辑了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pub&lt;/code&gt; 会识别到这是一个 &lt;code&gt;git&lt;/code&gt; 依赖&lt;/li&gt;
&lt;li&gt;它会去拉取这个 GitHub 仓库&lt;/li&gt;
&lt;li&gt;然后把对应版本缓存到本机&lt;/li&gt;
&lt;li&gt;更新锁文件和项目依赖映射&lt;/li&gt;
&lt;li&gt;后面项目再编译时，就会基于这份新的依赖来源来跑&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;也就是说，从这一刻开始，项目依赖的就不再是“原来那个 hosted 包来源”，而是你指定的这个 Git 仓库。&lt;/p&gt;
&lt;p&gt;这也是为什么我说，真正决定依赖来源的是 &lt;code&gt;pubspec.yaml&lt;/code&gt;，不是 &lt;code&gt;flutter clean&lt;/code&gt;。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-11&quot;&gt;所以 &lt;code&gt;flutter clean&lt;/code&gt; + &lt;code&gt;flutter pub get&lt;/code&gt; 连着执行时，应该怎么理解&lt;/h2&gt;
&lt;p&gt;这两个命令经常一起出现，但它们负责的事情完全不是一层。&lt;/p&gt;
&lt;p&gt;你可以把它理解成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;flutter clean&lt;/code&gt;：把项目现场打扫干净&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flutter pub get&lt;/code&gt;：按当前依赖配置重新把材料搬进来&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你没改依赖来源，它就继续按原来的来源取。&lt;/p&gt;
&lt;p&gt;如果你把 &lt;code&gt;get&lt;/code&gt; 改成了你自己的 GitHub 仓库，它就会按新的地址重新拉。&lt;/p&gt;
&lt;p&gt;所以这套动作本身不可怕，关键是你在执行之前，&lt;code&gt;pubspec.yaml&lt;/code&gt; 到底写成了什么样。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-12&quot;&gt;我个人建议的处理方式&lt;/h2&gt;
&lt;p&gt;如果你只是想先保命，让项目先稳住，我建议直接做两件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先把能找到的完整包备份到自己的 GitHub。&lt;/li&gt;
&lt;li&gt;再把项目依赖切到你自己能控制的地址。&lt;/li&gt;
&lt;/ol&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;CI 重建时也不会因为某个外部仓库状态异常而临时翻车&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;说白了，就是把“我电脑里刚好还有缓存”这件事，变成“团队里任何人都有稳定地址可用”。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-13&quot;&gt;如果你也想直接用我这份地址，可以抄这段&lt;/h2&gt;
&lt;p&gt;这个是最省心的版本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;dependencies:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;get:&lt;/span&gt;
    &lt;span class=&quot;hljs-attr&quot;&gt;git:&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;url:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;https://github.com/xinqingaa/get-4.7.3.git&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;ref:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;改完之后执行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;flutter clean
flutter pub get
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一般会发生这些事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当前项目构建缓存被清理。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pub&lt;/code&gt; 按新的 Git 地址重新获取 &lt;code&gt;get&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;锁文件会更新成新的依赖来源。&lt;/li&gt;
&lt;li&gt;之后项目再跑，走的就是你指定的仓库了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果项目比较重要，建议把 &lt;code&gt;ref: main&lt;/code&gt; 换成具体 commit。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-14&quot;&gt;GitHub 上的备注怎么补&lt;/h2&gt;
&lt;p&gt;这块我建议至少补两个地方，一个是仓库描述，一个是 &lt;code&gt;README.md&lt;/code&gt;。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-15&quot;&gt;1. 仓库描述&lt;/h3&gt;
&lt;p&gt;GitHub 仓库首页右侧 &lt;code&gt;About&lt;/code&gt; 那里，点编辑，简单写一句就够了，比如：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Backup mirror of get 4.7.3 recovered from local pub cache.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这个主要是让别人点进来时一眼知道这仓库是干嘛的。&lt;/p&gt;
&lt;h3 data-id=&quot;heading-16&quot;&gt;2. README&lt;/h3&gt;
&lt;p&gt;这个更重要，因为后面真正给别人复制依赖地址的时候，大家通常会先看 README。&lt;/p&gt;
&lt;p&gt;可以直接写清楚三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;这个仓库是什么&lt;/li&gt;
&lt;li&gt;它是从哪里恢复出来的&lt;/li&gt;
&lt;li&gt;在 Flutter 项目里怎么引用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下面这段 README 你可以直接拿去用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-id=&quot;heading-17&quot;&gt;README 示例&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-md&quot; lang=&quot;md&quot;&gt;&lt;span class=&quot;hljs-section&quot;&gt;# get-4.7.3&lt;/span&gt;

这是从本地 &lt;span class=&quot;hljs-code&quot;&gt;`pub cache`&lt;/span&gt; 中恢复出来的 &lt;span class=&quot;hljs-code&quot;&gt;`get 4.7.3`&lt;/span&gt; 备份仓库，用来做内部依赖兜底。

原始恢复来源：

&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-code&quot;&gt;`~/.pub-cache/hosted/pub.dev/get-4.7.3`&lt;/span&gt;

如果你想在 Flutter 项目里直接使用这个仓库，可以这样写：

&lt;span class=&quot;hljs-code&quot;&gt;```yaml
dependencies:
  get:
    git:
      url: https://github.com/xinqingaa/get-4.7.3.git
      ref: main
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果用于正式项目，建议把 &lt;code&gt;ref&lt;/code&gt; 改成具体 commit SHA，避免后续分支内容变化影响构建结果。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot; lang=&quot;yaml&quot;&gt;
&lt;span class=&quot;hljs-meta&quot;&gt;---
&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;如果你已经在本地把&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;README&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;改好了，上传方式和平时推代码一样：&lt;/span&gt;

&lt;span class=&quot;hljs-string&quot;&gt;```bash&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;README.md&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;commit&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;docs: add repository usage notes&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;push&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;origin&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这就算把“备注”一起传上去了。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-18&quot;&gt;最后&lt;/h2&gt;
&lt;p&gt;这次事情也算给我提了个醒。&lt;/p&gt;
&lt;p&gt;平时大家都觉得依赖装上了就完事了，但只要项目还在维护，关键依赖最好还是留一手。&lt;/p&gt;
&lt;p&gt;尤其是那种项目里已经深度使用、短时间又很难替换掉的包，真碰到上游不稳定，最省时间的办法不是现场慌，而是趁本机缓存还在，赶紧先救一份出来。&lt;/p&gt;
&lt;p&gt;如果你们团队现在也懒得自己再折腾一遍，那就直接先用我这份：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://github.com/xinqingaa/get-4.7.3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;先把项目稳住，比什么都重要。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-19&quot;&gt;往期文章回顾&lt;/h2&gt;
&lt;h3 data-id=&quot;heading-20&quot;&gt;Flutter 图片编辑器&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7571067260053585946&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/post/7571067260053585946&quot;&gt;掘金文章&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fpub.dev%2Fpackages%2Fflutter_img_editor&quot; target=&quot;_blank&quot; title=&quot;https://pub.dev/packages/flutter_img_editor&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;pubdev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fxinqingaa%2Fflutter_img_editor&quot; target=&quot;_blank&quot; title=&quot;https://github.com/xinqingaa/flutter_img_editor&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-id=&quot;heading-21&quot;&gt;Flutter 全链路监控 SDK&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7564977973260173338&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/post/7564977973260173338&quot;&gt;掘金文章&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fpub.dev%2Fpackages%2Fflutter_monitor_sdk&quot; target=&quot;_blank&quot; title=&quot;https://pub.dev/packages/flutter_monitor_sdk&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;pubdev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fxinqingaa%2Fflutter_monitor_sdk&quot; target=&quot;_blank&quot; title=&quot;https://github.com/xinqingaa/flutter_monitor_sdk&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-id=&quot;heading-22&quot;&gt;Flutter 全场景弹框&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7538324216594726950&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/post/7538324216594726950&quot;&gt;掘金文章&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fpub.dev%2Fpackages%2Funified_popups&quot; target=&quot;_blank&quot; title=&quot;https://pub.dev/packages/unified_popups&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;pubdev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fxinqingaa%2Funified_popups&quot; target=&quot;_blank&quot; title=&quot;https://github.com/xinqingaa/unified_popups&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-id=&quot;heading-23&quot;&gt;Flutter日历组件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7531976064496123931&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/post/7531976064496123931&quot;&gt;掘金文章&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fpub.dev%2Fpackages%2Fomni_calendar_view&quot; target=&quot;_blank&quot; title=&quot;https://pub.dev/packages/omni_calendar_view&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;pubdev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fxinqingaa%2Fomni_calendar_view&quot; target=&quot;_blank&quot; title=&quot;https://github.com/xinqingaa/omni_calendar_view&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-id=&quot;heading-24&quot;&gt;日期选择器&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7524159959480991753&quot; target=&quot;_blank&quot; title=&quot;https://juejin.cn/post/7524159959480991753&quot;&gt;掘金文章&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fpub.dev%2Fpackages%2Fomni_date_picker&quot; target=&quot;_blank&quot; title=&quot;https://pub.dev/packages/omni_date_picker&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;pubdev&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fxinqingaa%2Fomni_date_picker&quot; target=&quot;_blank&quot; title=&quot;https://github.com/xinqingaa/omni_date_picker&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description><link>https://juejin.cn/post/7628240893640376362</link><guid isPermaLink="false">https://juejin.cn/post/7628240893640376362</guid><pubDate>Tue, 14 Apr 2026 07:23:16 GMT</pubDate><author>小林的编程开发日记</author><category></category><category>前端</category><category>Flutter</category></item><item><title>做中后台业务，为什么我不建议你用 Tailwind CSS？</title><description>&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/50848d2ee0714d09be3035dfa6105114~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgRXJwYW5PbWVy:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776738808&amp;amp;x-signature=0OVbnmoBCNgnh%2B9a47ZvuSxT1t0%3D&quot; alt=&quot;1___f27S-qQF2CAASt5bOwqg.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;大家好，我又来了😁&lt;/p&gt;
&lt;p&gt;最近我接手了一个隔壁组转过来的中后台重构项目。&lt;/p&gt;
&lt;p&gt;交接的时候，对方的技术负责人特意跟我强调，说这个项目采用了最新的技术栈，全面拥抱了 &lt;code&gt;Tailwind CSS&lt;/code&gt;，开发体验极其丝滑。&lt;/p&gt;
&lt;p&gt;我当时心里还挺期待，毕竟这两年 &lt;code&gt;Tailwind&lt;/code&gt; 的风刮得太大了，各种国内外大佬都在疯狂带货。结果周末我抽空把代码拉下来，打开 &lt;code&gt;VSCode&lt;/code&gt; 准备梳理一下业务主流程。盯了屏幕不到十分钟，我感觉自己的眼睛快瞎了。&lt;/p&gt;
&lt;p&gt;光说理论没意思，我直接给你们截取一段真实的、承载了各种表单校验和状态联动的业务侧边栏组件。你们自己品鉴一下所谓的极致开发体验👇：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-jsx&quot; lang=&quot;jsx&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;OrderCard&lt;/span&gt; = (&lt;span class=&quot;hljs-params&quot;&gt;{ order, isAdmin, isExpanded }&lt;/span&gt;) =&amp;gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; (
    &lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; 
      &lt;span class=&quot;hljs-attr&quot;&gt;className&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{&lt;/span&gt;`&lt;span class=&quot;hljs-attr&quot;&gt;flex&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;flex-col&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;w-full&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;p-5&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;mb-4&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;border&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;rounded-lg&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;shadow-sm&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;transition-all&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;duration-300&lt;/span&gt; ${
        &lt;span class=&quot;hljs-attr&quot;&gt;isAdmin&lt;/span&gt; ? &#39;&lt;span class=&quot;hljs-attr&quot;&gt;bg-red-50&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;border-red-200&lt;/span&gt;&#39; &lt;span class=&quot;hljs-attr&quot;&gt;:&lt;/span&gt; &#39;&lt;span class=&quot;hljs-attr&quot;&gt;bg-white&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;border-gray-200&lt;/span&gt;&#39;
      } ${
        &lt;span class=&quot;hljs-attr&quot;&gt;isExpanded&lt;/span&gt; ? &#39;&lt;span class=&quot;hljs-attr&quot;&gt;max-h-&lt;/span&gt;[&lt;span class=&quot;hljs-attr&quot;&gt;800px&lt;/span&gt;]&#39; &lt;span class=&quot;hljs-attr&quot;&gt;:&lt;/span&gt; &#39;&lt;span class=&quot;hljs-attr&quot;&gt;max-h-24&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;overflow-hidden&lt;/span&gt;&#39;
      } &lt;span class=&quot;hljs-attr&quot;&gt;hover:shadow-md&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;cursor-pointer&lt;/span&gt;`}
    &amp;gt;&lt;/span&gt;
      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;className&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&#39;flex items-center justify-between pb-3 mb-3 border-b border-gray-100&#39;&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;className&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&#39;text-sm font-semibold text-gray-800 truncate w-[60%]&#39;&lt;/span&gt;&amp;gt;&lt;/span&gt;
          {order.id}
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt; 
          &lt;span class=&quot;hljs-attr&quot;&gt;className&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{&lt;/span&gt;`&lt;span class=&quot;hljs-attr&quot;&gt;px-2&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;py-1&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;text-xs&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;rounded-full&lt;/span&gt; ${
            &lt;span class=&quot;hljs-attr&quot;&gt;order.status&lt;/span&gt; === &lt;span class=&quot;hljs-string&quot;&gt;&#39;PAID&#39;&lt;/span&gt; 
              ? &#39;&lt;span class=&quot;hljs-attr&quot;&gt;bg-green-100&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;text-green-700&lt;/span&gt;&#39; 
              &lt;span class=&quot;hljs-attr&quot;&gt;:&lt;/span&gt; &#39;&lt;span class=&quot;hljs-attr&quot;&gt;bg-orange-100&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;text-orange-700&lt;/span&gt;&#39;
          }`}
        &amp;gt;&lt;/span&gt;
          {order.statusText}
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;
      {/* 内部极其复杂的业务字段渲染... */}
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码跑得通吗？当然跑得通。UI 还原度高吗？也挺高。
但是作为接下来要维护这个项目的组长，我只觉得一阵头皮发麻。&lt;/p&gt;
&lt;p&gt;很多前端新人，甚至是做惯了 &lt;code&gt;C 端&lt;/code&gt; 独立开发的兄弟，对 &lt;code&gt;Tailwind&lt;/code&gt; 简直是顶礼膜拜。因为它不用取名字，不用在 &lt;code&gt;JS&lt;/code&gt; 和 &lt;code&gt;CSS&lt;/code&gt; 文件之间来回切换，写起来确实有快感。&lt;/p&gt;
&lt;p&gt;但作为趟过无数中后台项目的深水区，我今天必须给这股跟风热潮泼一盆冷水：
&lt;strong&gt;在绝大多数重型中后台业务场景里，&lt;code&gt;Tailwind CSS&lt;/code&gt; 并不是什么神兵利器，而是给后期维护带来不方便。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;来，咱们拿真实代码说话，看看它到底是怎么摧毁中后台工程的👇。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-0&quot;&gt;彻底掩盖你的业务主线？&lt;/h3&gt;
&lt;p&gt;做中后台系统，最难的从来不是画 UI，而是处理极度复杂的数据状态流转。&lt;/p&gt;
&lt;p&gt;上面那段代码最大的问题在于信噪比极低。作为一个接手代码的前端，我点开这个文件，首先想看的是：这个组件在不同权限、不同展开状态下，业务逻辑是怎么走的？&lt;/p&gt;
&lt;p&gt;但在 &lt;code&gt;Tailwind&lt;/code&gt; 的体系下，我的视线全被 &lt;code&gt;flex&lt;/code&gt;、&lt;code&gt;p-5&lt;/code&gt;、&lt;code&gt;mb-4&lt;/code&gt;、&lt;code&gt;max-h-[800px]&lt;/code&gt; 这种毫无业务价值的视觉原子类给强暴了。如果退回到老古董的 &lt;code&gt;CSS Modules&lt;/code&gt; 方案，这段代码在 &lt;code&gt;JS&lt;/code&gt; 侧应该长什么样？咱们对比一下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-jsx&quot; lang=&quot;jsx&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; classNames &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;classnames&#39;&lt;/span&gt;;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; styles &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;./OrderCard.module.less&#39;&lt;/span&gt;;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;OrderCard&lt;/span&gt; = (&lt;span class=&quot;hljs-params&quot;&gt;{ order, isAdmin, isExpanded }&lt;/span&gt;) =&amp;gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; (
    &lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; 
      &lt;span class=&quot;hljs-attr&quot;&gt;className&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{classNames(styles.orderCard,&lt;/span&gt; {[&lt;span class=&quot;hljs-attr&quot;&gt;styles.adminMode&lt;/span&gt;]&lt;span class=&quot;hljs-attr&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;isAdmin&lt;/span&gt;,
        [&lt;span class=&quot;hljs-attr&quot;&gt;styles.expanded&lt;/span&gt;]&lt;span class=&quot;hljs-attr&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;isExpanded&lt;/span&gt;
      })}
    &amp;gt;&lt;/span&gt;
      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;className&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{styles.cardHeader}&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;className&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{styles.orderId}&lt;/span&gt;&amp;gt;&lt;/span&gt;{order.id}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt; 
          &lt;span class=&quot;hljs-attr&quot;&gt;className&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{classNames(styles.statusBadge,&lt;/span&gt; {
            [&lt;span class=&quot;hljs-attr&quot;&gt;styles.statusPaid&lt;/span&gt;]&lt;span class=&quot;hljs-attr&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;order.status&lt;/span&gt; === &lt;span class=&quot;hljs-string&quot;&gt;&#39;PAID&#39;&lt;/span&gt;,
            [&lt;span class=&quot;hljs-attr&quot;&gt;styles.statusPending&lt;/span&gt;]&lt;span class=&quot;hljs-attr&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;order.status&lt;/span&gt; === &lt;span class=&quot;hljs-string&quot;&gt;&#39;PENDING&#39;&lt;/span&gt;
          })}
        &amp;gt;&lt;/span&gt;
          {order.statusText}
        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;
      {/* 业务字段一目了然 */}
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现区别了吗？重构后的 &lt;code&gt;JSX&lt;/code&gt; 变得极其纯粹。我只通过 &lt;code&gt;adminMode&lt;/code&gt;、&lt;code&gt;expanded&lt;/code&gt; 这种类名，就极其清晰地传达了业务语义。至于那个订单编号到底占百分之几十的宽度，那是 &lt;code&gt;UI 层&lt;/code&gt; 该关心的事情，它安静地待在 &lt;code&gt;.less&lt;/code&gt; 文件里，绝不会来污染我的业务主逻辑。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-1&quot;&gt;跟现成的组件库水土不服&lt;/h3&gt;
&lt;p&gt;中后台业务是不可能脱离 &lt;code&gt;Ant Design&lt;/code&gt;、&lt;code&gt;Element Plus&lt;/code&gt; 这类重型组件库的。而组件库的本质，是封装好了一整套内部的 &lt;code&gt;DOM&lt;/code&gt; 结构和 &lt;code&gt;ClassName&lt;/code&gt; 规范。&lt;/p&gt;
&lt;p&gt;这就带来了一个极其致命的冲突：当你用 &lt;code&gt;Tailwind&lt;/code&gt; 去覆盖 &lt;code&gt;Ant Design&lt;/code&gt; 的内部样式时，你会写出极其恶心的 &lt;code&gt;Hack&lt;/code&gt; 代码😖。&lt;/p&gt;
&lt;p&gt;比如产品经理要求：在这个特定页面里，把 &lt;code&gt;Ant Design&lt;/code&gt; 表格的表头背景色改成浅蓝色，单元格的 &lt;code&gt;padding&lt;/code&gt; 改小一点。&lt;/p&gt;
&lt;p&gt;正常的 &lt;code&gt;Less&lt;/code&gt; 做法是用样式穿透，精确打击：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-less&quot; lang=&quot;less&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;/* 样式文件覆盖 */&lt;/span&gt;
&lt;span class=&quot;hljs-selector-class&quot;&gt;.myCustomTable&lt;/span&gt; {
  :&lt;span class=&quot;hljs-selector-tag&quot;&gt;deep&lt;/span&gt;(.ant-table-thead &amp;gt; tr &amp;gt; th) {
    &lt;span class=&quot;hljs-attribute&quot;&gt;background-color&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;#f0f8ff&lt;/span&gt;;
    &lt;span class=&quot;hljs-attribute&quot;&gt;padding&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;8px&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;16px&lt;/span&gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你知道那个拥抱 &lt;code&gt;Tailwind&lt;/code&gt; 的小伙子是怎么写的吗？为了不写 &lt;code&gt;CSS&lt;/code&gt; 文件，他强行使用了 &lt;code&gt;Tailwind v3+&lt;/code&gt; 的任意变体语法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-jsx&quot; lang=&quot;jsx&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// Tailwind 强行覆盖组件库内部样式&lt;/span&gt;
&amp;lt;&lt;span class=&quot;hljs-title class_&quot;&gt;Table&lt;/span&gt; 
  className=&lt;span class=&quot;hljs-string&quot;&gt;&#39;[&amp;amp;_.ant-table-thead&amp;gt;tr&amp;gt;th]:bg-blue-50[&amp;amp;_.ant-table-thead&amp;gt;tr&amp;gt;th]:py-2 [&amp;amp;_.ant-table-thead&amp;gt;tr&amp;gt;th]:px-4 [&amp;amp;_.ant-table-tbody&amp;gt;tr&amp;gt;td]:text-gray-600[&amp;amp;_.ant-table-tbody&amp;gt;tr&amp;gt;td]:py-2&#39;&lt;/span&gt; 
  dataSource={data} 
  columns={columns} 
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码合进主分支的时候，我都替后续维护的兄弟感到悲哀😢。&lt;/p&gt;
&lt;p&gt;这玩意儿连换行都没有，密密麻麻挤在一起。未来如果要做主题切换，或者升级 &lt;code&gt;Ant Design&lt;/code&gt; 导致内部类名变了，谁敢去动这坨连正则都极难匹配的字符串？&lt;/p&gt;
&lt;p&gt;不仅没有提高开发效率，反而为了强行凑 &lt;code&gt;Tailwind&lt;/code&gt; 的语法，写出了一堆极难维护的代码。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-2&quot;&gt;负边距引发的问题&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;一行代码被写出来的成本，远远低于它在未来三年里被维护的成本。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Tailwind&lt;/code&gt; 本质上就是披着 &lt;code&gt;ClassName&lt;/code&gt; 外衣的行内样式。它把所有的样式固化在了 &lt;code&gt;HTML&lt;/code&gt; 结构上。&lt;/p&gt;
&lt;p&gt;设想真实的维护场景，前任开发为了让一个按钮和旁边的输入框对齐，极其随意地写了一个向左偏移负间距的类名：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-jsx&quot; lang=&quot;jsx&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-title class_&quot;&gt;Button&lt;/span&gt; className=&lt;span class=&quot;hljs-string&quot;&gt;&#39;-ml-2 mt-1&#39;&lt;/span&gt;&amp;gt;提交&amp;lt;/&lt;span class=&quot;hljs-title class_&quot;&gt;Button&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;半年后你接手这个需求，产品要求在这俩元素中间加一个 &lt;code&gt;Icon&lt;/code&gt;。你看着这个 &lt;code&gt;-ml-2&lt;/code&gt; 和 &lt;code&gt;mt-1&lt;/code&gt; 会陷入极其痛苦的挣扎：他当时为什么要写负边距？是因为外层父元素加了错的 &lt;code&gt;padding&lt;/code&gt; 导致的？还是为了抵消 &lt;code&gt;Button&lt;/code&gt; 内部自带的 &lt;code&gt;margin&lt;/code&gt;？🤷‍♂️&lt;/p&gt;
&lt;p&gt;在传统 &lt;code&gt;CSS&lt;/code&gt; 中，我们往往会留有注释说明抵消输入框自带的右侧留白。但在 &lt;code&gt;Tailwind&lt;/code&gt; 里，没有注释的容身之所。&lt;/p&gt;
&lt;p&gt;为了保证不出线上 &lt;code&gt;Bug&lt;/code&gt;，你绝对不敢删掉那个 &lt;code&gt;-ml-2&lt;/code&gt;。你会选择在它后面再打个补丁，加个 &lt;code&gt;pl-4&lt;/code&gt; 试图把它顶回来。
第二年，另一个接手的同事遇到了错位，又在后面补了一个 &lt;code&gt;mt-[-5px]&lt;/code&gt;🤣🤣🤣。&lt;/p&gt;
&lt;p&gt;日积月累，&lt;code&gt;HTML&lt;/code&gt; 标签上的类名越来越长，死代码和冲突代码全堆在 &lt;code&gt;DOM&lt;/code&gt; 上，最终变成一座没人敢碰的屎山💩。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 data-id=&quot;heading-3&quot;&gt;别瞎搞，先认清你的场景🫡&lt;/h3&gt;
&lt;p&gt;说了这么多，难道 &lt;code&gt;Tailwind&lt;/code&gt; 真的就是垃圾吗？当然绝对不是。&lt;/p&gt;
&lt;p&gt;如果你在做偏 &lt;code&gt;C端&lt;/code&gt; 的炫酷落地页、做 &lt;code&gt;SaaS&lt;/code&gt; 官网，或者你是独立开发者，没有沉重的历史包袱，不需要配合复杂的重型组件库，那 &lt;code&gt;Tailwind&lt;/code&gt; 绝对是神作。它自带极其优秀的设计规范，能让你极速堆出好看的界面。&lt;/p&gt;
&lt;p&gt;但咱们讨论的是中后台。中后台是干嘛的？团队十几个人来回交接，动辄几百个页面，充斥着极其复杂的表单联动和权限校验，生命周期长达五年甚至十年。&lt;/p&gt;
&lt;p&gt;在这种重型项目中，保持业务逻辑的纯粹性，分离关注点，远比你少写几行 &lt;code&gt;CSS&lt;/code&gt; 要重要一万倍。&lt;/p&gt;
&lt;p&gt;好了，今天分享到这，谢谢大家😁&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/2f8bad954e894b9dba9605268ba35ac3~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgRXJwYW5PbWVy:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776738808&amp;amp;x-signature=oWNwjng5pZihj%2B5jQcWnulTnATQ%3D&quot; alt=&quot;谢谢大家.gif&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;</description><link>https://juejin.cn/post/7628114662257901577</link><guid isPermaLink="false">https://juejin.cn/post/7628114662257901577</guid><pubDate>Tue, 14 Apr 2026 02:33:29 GMT</pubDate><author>ErpanOmer</author><category></category><category>前端</category><category>CSS</category><category>代码规范</category></item><item><title>全面升级！看看人家的后台管理系统，确实清新优雅！</title><description>&lt;blockquote&gt;
&lt;p&gt;关注过我的mall项目的小伙伴应该有所了解，mall项目的后台管理系统一直都是Vue2版本的，主要原因是项目从Vue2升级到Vue3基本等于要重写了。
最近我花了一个月的时间，将mall项目的后台管理系统升级到了Vue3版本，今天和大家聊聊做了哪些升级！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-id=&quot;heading-0&quot;&gt;项目介绍&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mall-admin-web&lt;/code&gt;是mall电商项目后台管理系统的前端项目，基于Vue3+Element-Plus实现。主要包括商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等功能。&lt;/p&gt;
&lt;p&gt;下面是&lt;code&gt;mall-admin-web&lt;/code&gt;项目运行的效果图，界面还是很清新优雅的！如果你想体验完整功能的话，可以访问这个在线演示地址：&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fwww.macrozheng.com%2Fadmin%2F&quot; target=&quot;_blank&quot; title=&quot;https://www.macrozheng.com/admin/&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;www.macrozheng.com/admin/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/833accd7d67f43b59b10877062827e7f~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWFjcm9aaGVuZw==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776734579&amp;amp;x-signature=p%2Bm%2Foz%2BDXcT68GQIwRdHOvRmToA%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;h2 data-id=&quot;heading-1&quot;&gt;技术栈&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mall-admin-web&lt;/code&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;Vue&lt;/td&gt;&lt;td&gt;前端框架&lt;/td&gt;&lt;td&gt;3.5.25&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Element Plus&lt;/td&gt;&lt;td&gt;前端UI框架&lt;/td&gt;&lt;td&gt;2.12.0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Vue Router&lt;/td&gt;&lt;td&gt;路由框架&lt;/td&gt;&lt;td&gt;4.6.3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pinia&lt;/td&gt;&lt;td&gt;全局状态管理框架&lt;/td&gt;&lt;td&gt;3.0.4&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pinia Plugin Persistedstate&lt;/td&gt;&lt;td&gt;Pinia持久化插件&lt;/td&gt;&lt;td&gt;4.7.1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Axios&lt;/td&gt;&lt;td&gt;前端HTTP框架&lt;/td&gt;&lt;td&gt;1.13.2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Vue-charts&lt;/td&gt;&lt;td&gt;基于Echarts的图表框架&lt;/td&gt;&lt;td&gt;8.0.1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TinyMCE Vue&lt;/td&gt;&lt;td&gt;富文本编辑器&lt;/td&gt;&lt;td&gt;5.1.1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Js-cookie&lt;/td&gt;&lt;td&gt;cookie管理工具&lt;/td&gt;&lt;td&gt;3.0.5&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 data-id=&quot;heading-2&quot;&gt;升级内容&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;这里和大家聊聊&lt;code&gt;mall-admin-web&lt;/code&gt;做了哪些升级！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-id=&quot;heading-3&quot;&gt;Vue2升级Vue3&lt;/h3&gt;
&lt;p&gt;项目的Vue版本从之前的&lt;code&gt;2.7.2&lt;/code&gt;升级到了&lt;code&gt;3.5.25&lt;/code&gt;，改动还是挺大的，之前使用的选项式API都已经改成了Vue3的组合式API。&lt;/p&gt;
&lt;p&gt;我在升级项目的同时，给代码添加了更加详尽的注释，方便大家来学习。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/48904c14295741e98dbf34eb5ccb1e6f~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWFjcm9aaGVuZw==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776734579&amp;amp;x-signature=smVIssBLxzWIlB3I7ZetTWHxVRg%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;之前经常有小伙伴问接口文档在哪里，其实把后端项目运行起来，就有接口文档了，我这里给前端调用的接口方法添加了详细的注释，大家也可以直接从代码中查看接口调用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/7fd5c54632dd4cbfad81910b828ba4f2~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWFjcm9aaGVuZw==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776734579&amp;amp;x-signature=AGUzPxYz4pGOJmFdm00%2BxTriMqc%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;h3 data-id=&quot;heading-4&quot;&gt;JavaScript升级TypeScript&lt;/h3&gt;
&lt;p&gt;TypeScript我们可以把它看作是带有类型的JavaScript，JavaScript里的支持的语法，它基本都支持。&lt;/p&gt;
&lt;p&gt;项目中对于使用到的对象添加了类型支持，用起来有点Java中对象的感觉。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/5c4563d7f0c64f23b0206231e0b60a07~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWFjcm9aaGVuZw==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776734579&amp;amp;x-signature=23WDwVYkbXbjlonOB6AbF2PbT5A%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;这样我们在编写代码时就可以有属性提示了，使用TypeScript我们在编译时就可以发现错误，以便及时修正。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/d7a4fa49cbb843f4ab942e07598ef7c8~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWFjcm9aaGVuZw==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776734579&amp;amp;x-signature=u8%2FjFzFPED7VDGdff0n1iNWAyvk%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;这里有两者使用的优势对比，大家可以参考下！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/f3b35fdaff6548dba8e3883b5c2c3a8d~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWFjcm9aaGVuZw==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776734579&amp;amp;x-signature=CNBTnvsAA4O1m032EXcwOHOedDc%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;h3 data-id=&quot;heading-5&quot;&gt;Element UI升级Element Plus&lt;/h3&gt;
&lt;p&gt;由于Element UI已经停止更新，这里升级到了支持Vue3的Element Plus组件库，两者使用过程中的特性与优缺点对比如下。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/438b97de9ff94d73b1a190ca95e52689~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWFjcm9aaGVuZw==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776734579&amp;amp;x-signature=70ilmDdngvUokXDbtCfCmtSUTQo%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;h3 data-id=&quot;heading-6&quot;&gt;Vuex升级Pinia&lt;/h3&gt;
&lt;p&gt;Pinia是Vue官方开发的状态管理库，使用它API更简洁，而且完美支持Vue3和TypeScript。&lt;/p&gt;
&lt;p&gt;项目中的用户信息存储就使用了它，配合&lt;code&gt;pinia-plugin-persistedstate&lt;/code&gt;插件，还可以实现数据的持久化。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/09acea58148b4e17b8862b68f58fb8a9~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWFjcm9aaGVuZw==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776734579&amp;amp;x-signature=APSV7Aoz8MmU795yE0wjV%2BFYPVA%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;两者使用过程中的特性与优缺点对比如下。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/5287a8edfc934d95820df58bb57c38ed~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWFjcm9aaGVuZw==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776734579&amp;amp;x-signature=SP5gf5zzD%2Fsw7%2BDs9UIYMXW21%2Fw%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;h3 data-id=&quot;heading-7&quot;&gt;v-charts升级vue-charts&lt;/h3&gt;
&lt;p&gt;之前项目中使用的图表库v-charts已经停止维护，这里升级到了vue-charts，使用该库生成的图表功能也更加强大了！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/b922776bf5f846a39a3c5dc82c7832ac~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWFjcm9aaGVuZw==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776734579&amp;amp;x-signature=tAsWrCxWEnooIxfmVsqS0TgNwHk%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;两者使用过程中的特性与优缺点对比如下。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/15d3c626b89f4e72b5c72060a8516a98~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWFjcm9aaGVuZw==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776734579&amp;amp;x-signature=05jC6G467Pb3Qnkkw8EgAnXsBq0%3D&quot; alt=&quot;&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;h2 data-id=&quot;heading-8&quot;&gt;总结&lt;/h2&gt;
&lt;p&gt;今天给大家分享了mall后台管理系统前端的升级内容，主要是项目升级到了Vue3，一些过时的库也迁移到了新的库，升级之后项目更加适合学习了，感兴趣的小伙伴可以学习下！&lt;/p&gt;
&lt;h2 data-id=&quot;heading-9&quot;&gt;项目地址&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Github：&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fmacrozheng%2Fmall-admin-web&quot; target=&quot;_blank&quot; title=&quot;https://github.com/macrozheng/mall-admin-web&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;github.com/macrozheng/…&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Gitee：&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgitee.com%2Fmacrozheng%2Fmall-admin-web&quot; target=&quot;_blank&quot; title=&quot;https://gitee.com/macrozheng/mall-admin-web&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;gitee.com/macrozheng/…&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><link>https://juejin.cn/post/7628132295190790180</link><guid isPermaLink="false">https://juejin.cn/post/7628132295190790180</guid><pubDate>Tue, 14 Apr 2026 01:23:00 GMT</pubDate><author>MacroZheng</author><category></category><category>前端</category><category>Vue.js</category><category>TypeScript</category></item><item><title>写给 Claude Code 初学者的使用技巧</title><description>&lt;p&gt;大家好，我是双越。&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fwww.wangeditor.com%2F&quot; target=&quot;_blank&quot; title=&quot;https://www.wangeditor.com/&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;wangEditor&lt;/a&gt; 作者，前百度 滴滴 资深前端工程师，慕课网金牌讲师，PMP，&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fwww.mianshipai.com%2F&quot; target=&quot;_blank&quot; title=&quot;https://www.mianshipai.com/&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;前端面试派&lt;/a&gt; 作者。&lt;/p&gt;
&lt;p&gt;我正致力于两个项目的开发和升级，感兴趣的可以私信我，加入项目小组。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fwww.huashuiai.com%2F&quot; target=&quot;_blank&quot; title=&quot;https://www.huashuiai.com/&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;划水AI&lt;/a&gt; Node 全栈 AIGC 知识库，包括 AI 写作、多人协同编辑。复杂业务，真实上线。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fzhitalk.chat%2F&quot; target=&quot;_blank&quot; title=&quot;https://zhitalk.chat/&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;智语&lt;/a&gt; AI Agent 智能体项目。一个智能面试官，可以优化简历、模拟面试、解答题目等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文总结了一些 Claude Code 使用技巧，针对初学者的，“老司机”可划走～&lt;/p&gt;
&lt;h2 data-id=&quot;heading-0&quot;&gt;CLAUDE.md&lt;/h2&gt;
&lt;p&gt;用 claude 打开一个新项目，执行 &lt;code&gt;/init&lt;/code&gt; 命令，它会自动话分析项目并创建一个 CLAUDE.md 文件。&lt;/p&gt;
&lt;p&gt;CLAUDE.md 是项目记忆文件，每次会话都会携带，它包含了项目中最重要的一些信息，例如格式、规范、命令、开发者癖好等。&lt;/p&gt;
&lt;p&gt;CLAUDE.md 可放在项目根目录，也可以放在 &lt;code&gt;.claude&lt;/code&gt; 目录，或一些二级目录。二级目录的，要等 Claude 加载当前文件时，才会加载。&lt;/p&gt;
&lt;p&gt;CLAUDE.md 尽量保持在 200 行以内，如果内容太多可能会影响 AI 输出的效果，更容易出现幻觉。如果有按需加载的内容，请使用 skill ，下文会介绍。&lt;/p&gt;
&lt;p&gt;简单示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-markdown&quot; lang=&quot;markdown&quot;&gt;&lt;span class=&quot;hljs-section&quot;&gt;# Todo List Project&lt;/span&gt;

&lt;span class=&quot;hljs-section&quot;&gt;## 常用命令&lt;/span&gt;
&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; 启动: &lt;span class=&quot;hljs-code&quot;&gt;`npm run dev`&lt;/span&gt;
&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; 测试: &lt;span class=&quot;hljs-code&quot;&gt;`npm test`&lt;/span&gt;
&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; 构建: &lt;span class=&quot;hljs-code&quot;&gt;`npm run build`&lt;/span&gt;

&lt;span class=&quot;hljs-section&quot;&gt;## 代码规范&lt;/span&gt;
&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; 使用 ES Modules，不用 CommonJS
&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; 使用函数式组件 + Hooks
&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; 状态管理用 Zustand（见 src/stores）

&lt;span class=&quot;hljs-section&quot;&gt;## 文件结构&lt;/span&gt;
&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; src/components/ 放 UI 组件
&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; src/api/ 放接口调用
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还有一个 AGENTS.md 但它&lt;strong&gt;不是&lt;/strong&gt; Claude Code 原生的文件，而是来自整个 AI 编程工具生态的一个开放标准。它的定位是：&lt;strong&gt;一个文件，兼容所有 AI 编程工具&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;目前每个主流 AI 编程工具都有自己的配置文件：Claude Code 读取 &lt;code&gt;CLAUDE.md&lt;/code&gt;，Codex CLI 读取 &lt;code&gt;AGENTS.md&lt;/code&gt;，Gemini CLI 读取 &lt;code&gt;GEMINI.md&lt;/code&gt;，Cursor 用 &lt;code&gt;.cursorrules&lt;/code&gt;，GitHub Copilot 用 &lt;code&gt;copilot-instructions.md&lt;/code&gt;&lt;/p&gt;
&lt;h2 data-id=&quot;heading-1&quot;&gt;Plan Mode&lt;/h2&gt;
&lt;p&gt;使用 Claude Code 一般是：先探索，再规划，最后编码。&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;shift+tab&lt;/code&gt; 可切换到 Plan mode&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/5cd201aabe2c48099170f4550c8e74c9~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Y-M6LaKQUlfY2x1Yg==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776733193&amp;amp;x-signature=hpwOose3r17MMBwRUpqRaPBNS3o%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;此时输入一些指令让它去学习和探索，例如&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;read&lt;/span&gt; /src/auth and understand how we handle sessions and login.
also look at how we manage environment variables &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; secrets.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，让它根据探索的内容做详细规划，例如&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-css&quot; lang=&quot;css&quot;&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;I&lt;/span&gt; want &lt;span class=&quot;hljs-selector-tag&quot;&gt;to&lt;/span&gt; add Google OAuth. What files need &lt;span class=&quot;hljs-selector-tag&quot;&gt;to&lt;/span&gt; change?
What&#39;s the session &lt;span class=&quot;hljs-attribute&quot;&gt;flow&lt;/span&gt;? Create &lt;span class=&quot;hljs-selector-tag&quot;&gt;a&lt;/span&gt; plan.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，切换回 Normal Mode 并让 Claude 编码，根据其计划进行验证。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-arduino&quot; lang=&quot;arduino&quot;&gt;implement the OAuth flow from your plan. write tests &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; the
callback handler, run the test suite &lt;span class=&quot;hljs-keyword&quot;&gt;and&lt;/span&gt; fix any failures.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-id=&quot;heading-2&quot;&gt;常见命令&lt;/h2&gt;
&lt;p&gt;总结了一下 Claude Code 常见的命令。&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;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/clear&lt;/code&gt;&lt;/td&gt;&lt;td&gt;清空对话历史，开始新任务时必用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/compact&lt;/code&gt;&lt;/td&gt;&lt;td&gt;压缩上下文，保留重要信息，释放 token&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/context&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查看当前 context 使用情况&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/help&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查看帮助&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;/init&lt;/td&gt;&lt;td&gt;初始化项目，增加 CLAUDE.md&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;功能类&lt;/strong&gt;&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;命令&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/memory&lt;/code&gt;&lt;/td&gt;&lt;td&gt;打开并编辑 CLAUDE.md 文件&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/model&lt;/code&gt;&lt;/td&gt;&lt;td&gt;切换模型（Opus / Sonnet 等）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/mcp&lt;/code&gt;&lt;/td&gt;&lt;td&gt;管理 MCP 服务器连接&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/cost&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查看本次会话的 token 消耗&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;代码相关&lt;/strong&gt;&lt;/p&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;命令&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/code-review&lt;/code&gt;&lt;/td&gt;&lt;td&gt;对当前代码进行审查&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/rewind&lt;/code&gt;&lt;/td&gt;&lt;td&gt;打开检查点菜单，可回滚代码和对话&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;在对话框中还有几个特殊前缀：&lt;code&gt;/&lt;/code&gt; 触发 slash 命令，&lt;code&gt;!&lt;/code&gt; 直接执行 bash 命令，&lt;code&gt;@&lt;/code&gt; 引用文件（带自动补全）。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;!npm &lt;span class=&quot;hljs-built_in&quot;&gt;test&lt;/span&gt;          &lt;span class=&quot;hljs-comment&quot;&gt;# 直接执行 shell 命令&lt;/span&gt;
!git status
@src/index.ts      &lt;span class=&quot;hljs-comment&quot;&gt;# 把文件内容加入上下文&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-id=&quot;heading-3&quot;&gt;Skill&lt;/h2&gt;
&lt;p&gt;Skill 现在已经是 agent 非常成熟且常用的模块了，在 claude code 同样如此。&lt;br&gt;
可以自己创建，也可以安装第三方的，例如 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fvercel.com%2Fblog%2Fintroducing-react-best-practices&quot; target=&quot;_blank&quot; title=&quot;https://vercel.com/blog/introducing-react-best-practices&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;vercel.com/blog/introd…&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;执行命令&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;npx skills add vercel-labs/agent-skills
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会创建在 .claude/skills 目录下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/77e93b6bc1654befad8f9619d765419d~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Y-M6LaKQUlfY2x1Yg==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776733193&amp;amp;x-signature=aWMWmkEqA0zowwiJTM5QDVRRd9w%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;如下图，下面三个是我安装的 skill ，上面几个是 claude code 内置的 skill&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/a3566196f3674904b756143229322623~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Y-M6LaKQUlfY2x1Yg==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776733193&amp;amp;x-signature=7XjUNq3%2BJJ4CDaJlf95wNfLRr30%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;这样 Cluade Code 在写 React 或前端代码时，就会参考这些 skill 写的更好。&lt;/p&gt;
&lt;p&gt;这些第三方的都是全世界开发者通用的技能，在你的实际工作中，你应该还需要积累一些自己或公司内部使用的 skill ，一般要一边应用一边积累，慢慢改善。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-4&quot;&gt;MCP&lt;/h2&gt;
&lt;p&gt;MCP server 是 agent 联通外部世界的方式。几个推荐安装的 MCP server&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Context7&lt;/strong&gt; — 解决文档过时问题 Context7 把版本精确的库文档直接注入 Claude Code 会话。Claude 不再靠训练数据猜 API，而是拿到你项目实际使用的 React、Next.js、Prisma 那个版本的真实文档。有效解决 Claude 写出已废弃方法的问题。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-sql&quot; lang=&quot;sql&quot;&gt;claude mcp &lt;span class=&quot;hljs-keyword&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;s project context7 npx &lt;span class=&quot;hljs-variable&quot;&gt;@upstash&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;context7&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;mcp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的 &lt;code&gt;project&lt;/code&gt; 是安装在项目里，也可以改为 &lt;code&gt;user&lt;/code&gt;&lt;br&gt;
安装以后会在项目根目录 &lt;code&gt;.mcp.json&lt;/code&gt; 文件中看到&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/3125abcc37534cddbef91634d3fe984c~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Y-M6LaKQUlfY2x1Yg==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776733193&amp;amp;x-signature=X1YfWPYAjrsFDx1oR813MfA31H8%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Playwright&lt;/strong&gt; — 浏览器自动化测试 Playwright MCP 让 Claude Code 可以打开浏览器、点击页面、截图验证 UI，这个反馈闭环能捕获单元测试发现不了的问题。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-sql&quot; lang=&quot;sql&quot;&gt;claude mcp &lt;span class=&quot;hljs-keyword&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;s project playwright npx &lt;span class=&quot;hljs-variable&quot;&gt;@playwright&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;mcp&lt;span class=&quot;hljs-variable&quot;&gt;@latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. GitHub&lt;/strong&gt; — 无缝 PR 和 issue 管理 GitHub MCP 消除了编辑器和 GitHub 网页之间最多的上下文切换，创建分支、开 PR、搜索代码、管理 issue 和 Actions workflow 全在对话里完成。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;claude mcp add -s project github npx @modelcontextprotocol/server-github
&lt;span class=&quot;hljs-comment&quot;&gt;# 需要设置 GITHUB_PERSONAL_ACCESS_TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装以后，启动 claude 时候需要选择&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/3756d2282acd4c44a2e87c94fbaf0f79~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Y-M6LaKQUlfY2x1Yg==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776733193&amp;amp;x-signature=M8A%2FwEHT7ayGGGQkttSQSr0tri8%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以让 claude code 帮我打开网页访问 localhost:3000 来测试网页，它会自动分析网页元素，进行测试。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/294b7c6c43a341e49a5273b8a7d1a902~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Y-M6LaKQUlfY2x1Yg==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776733193&amp;amp;x-signature=YvpUiWHpwYFriGwH6d4HsUwhSyo%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;现在 claude code 有 tool search 功能，默认只家在 tool name ，其他内容都是按需加载，不会占用太多 token&lt;/p&gt;
&lt;h2 data-id=&quot;heading-5&quot;&gt;Hooks&lt;/h2&gt;
&lt;p&gt;Hooks 是用户定义的 shell 命令，在 Claude Code 生命周期中的特定点执行。它们对 Claude Code 的行为提供确定性控制，确保某些操作始终发生，而不是依赖 LLM 选择运行它们。使用 hooks 来强制执行项目规则、自动化重复任务，并将 Claude Code 与现有工具集成。&lt;/p&gt;
&lt;p&gt;例如，阻止受保护的文件被编辑，如&amp;nbsp;&lt;code&gt;.env&lt;/code&gt;、&lt;code&gt;package-lock.json&lt;/code&gt;&amp;nbsp;或&amp;nbsp;&lt;code&gt;.git/&lt;/code&gt;&amp;nbsp;中的任何内容。&lt;/p&gt;
&lt;p&gt;编辑 .claude/settings.json 文件，增加&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-python&quot; lang=&quot;python&quot;&gt;{
  &lt;span class=&quot;hljs-string&quot;&gt;&quot;hooks&quot;&lt;/span&gt;: {
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;PreToolUse&quot;&lt;/span&gt;: [
      {
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;matcher&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;Edit|Write&quot;&lt;/span&gt;,
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;hooks&quot;&lt;/span&gt;: [
          {
            &lt;span class=&quot;hljs-string&quot;&gt;&quot;type&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;command&quot;&lt;/span&gt;,
            &lt;span class=&quot;hljs-string&quot;&gt;&quot;command&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;$CLAUDE_PROJECT_DI&lt;span class=&quot;hljs-string&quot;&gt;R&quot;/.claude/hooks/protect-files.sh&quot;&lt;/span&gt;
          }
        ]
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;PreToolUse&lt;/code&gt; 就是在工具使用增加 Hook ，这里的 sh 文件内容如下&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot; lang=&quot;bash&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;# protect-files.sh&lt;/span&gt;

INPUT=$(&lt;span class=&quot;hljs-built_in&quot;&gt;cat&lt;/span&gt;)
FILE_PATH=$(&lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;&lt;span class=&quot;hljs-variable&quot;&gt;$INPUT&lt;/span&gt;&quot;&lt;/span&gt; | jq -r &lt;span class=&quot;hljs-string&quot;&gt;&#39;.tool_input.file_path // empty&#39;&lt;/span&gt;)

PROTECTED_PATTERNS=(&lt;span class=&quot;hljs-string&quot;&gt;&quot;.env&quot;&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;package-lock.json&quot;&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;.git/&quot;&lt;/span&gt;)

&lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; pattern &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;&lt;span class=&quot;hljs-variable&quot;&gt;${PROTECTED_PATTERNS[@]}&lt;/span&gt;&quot;&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; [[ &lt;span class=&quot;hljs-string&quot;&gt;&quot;&lt;span class=&quot;hljs-variable&quot;&gt;$FILE_PATH&lt;/span&gt;&quot;&lt;/span&gt; == *&lt;span class=&quot;hljs-string&quot;&gt;&quot;&lt;span class=&quot;hljs-variable&quot;&gt;$pattern&lt;/span&gt;&quot;&lt;/span&gt;* ]]; &lt;span class=&quot;hljs-keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;Blocked: &lt;span class=&quot;hljs-variable&quot;&gt;$FILE_PATH&lt;/span&gt; matches protected pattern &#39;&lt;span class=&quot;hljs-variable&quot;&gt;$pattern&lt;/span&gt;&#39;&quot;&lt;/span&gt; &amp;gt;&amp;amp;2
    &lt;span class=&quot;hljs-built_in&quot;&gt;exit&lt;/span&gt; 2
  &lt;span class=&quot;hljs-keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;done&lt;/span&gt;

&lt;span class=&quot;hljs-built_in&quot;&gt;exit&lt;/span&gt; 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hook 事件类型有很多，例如 &lt;code&gt;SessionStart&lt;/code&gt; &lt;code&gt;SessionEnd&lt;/code&gt; &lt;code&gt;PreToolUse&lt;/code&gt; &lt;code&gt;PostToolUse&lt;/code&gt; 具体看文档 &lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fcode.claude.com%2Fdocs%2Fzh-CN%2Fhooks-guide&quot; target=&quot;_blank&quot; title=&quot;https://code.claude.com/docs/zh-CN/hooks-guide&quot; ref=&quot;nofollow noopener noreferrer&quot;&gt;code.claude.com/docs/zh-CN/…&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;总之，Hook 允许用户在 claude code 多种行为的开始和结束增加监测机制，保障安全稳定。&lt;/p&gt;
&lt;h2 data-id=&quot;heading-6&quot;&gt;SubAgents&lt;/h2&gt;
&lt;p&gt;sub agent 单独的环境，单独的上下文，运行单独的任务，不影响主 agent 。&lt;br&gt;
例如代码审查、单元测试、排查 bug 等。&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;/agents&lt;/code&gt; 命令查看当前 sub agents （cc 内置了几个 agent），或者创建一个新的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/c59ff9aebeb04829a382fb6034c6719b~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Y-M6LaKQUlfY2x1Yg==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776733193&amp;amp;x-signature=mDvwBxy26ELNINxDqtO4Me5u8c0%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;创建以后会在 &lt;code&gt;.claude/agents/&lt;/code&gt; 目录下创建一个 md 文件，描述这个 agent ，你可以修改&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/3d0c3d99243f47c1bba1d7ad153d7cf0~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Y-M6LaKQUlfY2x1Yg==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776733193&amp;amp;x-signature=JTIklI%2BqWIyEhLS%2BFw26aJxPHz8%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;p&gt;运行一个 sub agent 让它帮我写单元测试&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/4d03f1b0c0f94018bfa6cde8ac133e8b~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Y-M6LaKQUlfY2x1Yg==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776733193&amp;amp;x-signature=2yLKjdU0sgY0HPXrngzdLg%2FAcpA%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;h2 data-id=&quot;heading-7&quot;&gt;Context Window&lt;/h2&gt;
&lt;p&gt;一次聊天太长，上下文内容过多，就会消耗过多 token，且让 AI 产生幻觉。这是所有 Agent 都无法避免的事情。&lt;br&gt;
所以，开发新的功能时，要及时清理聊天记录，使用 &lt;code&gt;/clear&lt;/code&gt; 命令。&lt;br&gt;
恢复上次会话 &lt;code&gt;claude --continue&lt;/code&gt; ，选择会话 &lt;code&gt;claude --resume&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;下图是 claude code 各个功能对于上下文的影响，可以看到&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLAUDE.md 是每次请求都携带&lt;/li&gt;
&lt;li&gt;MCP servers 是只携带工具名称，其他按需 —— 但如果 &lt;code&gt;base_url&lt;/code&gt; 不是官方 claude ，这里就不确定了！！！&lt;/li&gt;
&lt;li&gt;Skills 只写代码 name desc ，内容按需加载&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/1ba1a5b245cc4ed5b8fc90f045d85cbe~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Y-M6LaKQUlfY2x1Yg==:q75.awebp?rk3s=f64ab15b&amp;amp;x-expires=1776733193&amp;amp;x-signature=O1D52O6xtO7ER4EBKqlVKTkE7gA%3D&quot; alt=&quot;image.png&quot; loading=&quot;lazy&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;
&lt;h2 data-id=&quot;heading-8&quot;&gt;最后&lt;/h2&gt;
&lt;p&gt;之前和同学沟通，有人提到一句话非常好：如果 AI 写错了，不是纠正他，而是考虑自己的环境、规则和计划是否合理，这也是 harness 编程和 vibe coding 的区别。&lt;/p&gt;
&lt;p&gt;Claude Code 的功能和使用就很契合这一点，Plan Mode + Skill + Hook 就是环境、规则和计划。&lt;/p&gt;</description><link>https://juejin.cn/post/7628116434649088009</link><guid isPermaLink="false">https://juejin.cn/post/7628116434649088009</guid><pubDate>Tue, 14 Apr 2026 00:59:53 GMT</pubDate><author>双越AI_club</author><category>人工智能</category><category>AI编程</category><category>Claude</category><category>Agent</category></item></channel></rss>