首页
/ Ponytail 的深克隆方案:用 structuredClone 内置 API 替代 lodash.cloneDeep

Ponytail 的深克隆方案:用 structuredClone 内置 API 替代 lodash.cloneDeep

2026-09-06 12:35:38作者:盛欣凯Ernestine

当你对 AI 编程助手说"深克隆这个对象"时,它最常见的两个回答是:npm install lodash 然后用 cloneDeep,或者 JSON.parse(JSON.stringify(obj)) 这个经典但脆弱的 hack。Ponytail——一个让 AI Agent 以"办公室里最懒的资深工程师"思维方式写代码的技能包——对这个任务给出的答案是运行时内置的 structuredClone():一行代码、零依赖。本文以仓库中 examples/deep-clone.md 这个真实基准对比为核心,讲清"无 Ponytail"与"有 Ponytail"两种输出各自的问题与价值,并结合仓库中的 平台原生存根清单技能定义 与基准复现方式,说明这套"先查平台、再查标准库、最后才写代码"的决策流程是如何落到具体代码上的。

任务背景:深克隆是典型的"过度依赖"陷阱

examples/ 目录下的每篇文档都不是人工编写的示例,而是基准测试中同一模型、同一提示词在"无技能"与"带 Ponytail"两种模式下的逐字真实输出(模型为 Claude Haiku 4.5,temperature 1,来源 benchmarks/output.json),见 examples/README.md。读者可以自行复现这些对比:

npx promptfoo@latest eval -c benchmarks/promptfooconfig.yaml

深克隆恰好是"一个函数就能解决却动辄拉来整个依赖包"的典型场景。下面完整继承原文档的三组代码,逐一分析。

Without Ponytail:两个默认选项的代价

选项一:安装 lodash

无技能模式下,模型的直接回答是给整个 lodash 库安装依赖:

npm install lodash
import { cloneDeep } from "lodash";

const copy = cloneDeep(original);

问题不在 cloneDeep 本身——它实现得没错——而在于为了一个函数引入了整个依赖:包体积、供应链安全面、版本升级维护,全部为了一个 cloneDeep。Ponytail 的原话是:"Pull lodash in when you need the rest of it, not for one function."(等你需要 lodash 的其他功能时再引入,而不是为了一个函数。)

选项二:JSON 往返 hack

另一个"零依赖"的经典做法:

// fragile: loses Date, undefined, Map, Set, circular refs, functions
const copy = JSON.parse(JSON.stringify(original));

原文档的注释点明了它的脆弱性:JSON.parse/stringify静默丢弃 DateundefinedMapSet、循环引用、函数——而且不报错,只是悄悄丢数据。静默丢数据比抛错更危险:new Date(2026, 0, 1) 经 JSON 往返后会变成一个字符串,Map 会变成空对象 {},循环引用则直接栈溢出。这类 bug 往往在很久之后才在下游代码中暴露。

With Ponytail:一个内置 API 收编全部场景

带 Ponytail 时,模型的输出收敛为一行:

// ponytail: structuredClone does this
const copy = structuredClone(original);

原文档的结论句是:"1 dependency (or a fragile hack) → 1 built-in." structuredClone 是 Web 平台标准的深克隆 API,它正确处理 DateMapSetArrayBufferRegExp、循环引用等所有 JSON.parse/stringify 静默丢弃的类型。可用性方面,原文档给出的前提是:2022 年起所有主流浏览器、Node.js v17 起均内置该 API——如果你的目标运行时仍在此之下(例如要兼容 IE11 或很老的 Node),才需要考虑回退方案,而那属于"原生方案确实不够用"的例外情形,仓库中 平台原生存根清单 对这类例外的态度也是明确的:

When the native solution is genuinely insufficient (old browser support, edge cases it doesn't handle, ergonomics that matter at scale), the library earns its place. Install it then, not before.

也就是说:lodash 在"原生确实不够用"时才挣得它的位置,而不是之前。

它命中了 Ponytail "决策梯子"的哪一级?

深克隆这个例子不只是"代码变短了",它演示了 Ponytail 技能定义中的核心机制。skills/ponytail/SKILL.md 规定 Agent 在写代码前必须爬一遍七级梯子,停在第一级能站住的地方:

1. 这玩意儿需要存在吗?        → 不需要:跳过(YAGNI)
2. 本代码库里已经有了?          → 复用,别重写
3. 标准库能做吗?               → 用标准库
4. 平台原生功能覆盖吗?         → 用原生功能
5. 已安装的依赖能解决吗?        → 用它,别为几行代码新装依赖
6. 能一行写完吗?               → 一行
7. 最后才是:能跑的最小代码

对"深克隆这个对象"这个任务:第 1 级通过(确实需要一份独立副本)、第 2 级跳过(代码库里没有现成工具)、第 4 级直接站住——平台原生的 structuredClone 就是答案,根本没机会走到"装依赖"或"手写递归"那一步。仓库中 docs/platform-native.md 的 JavaScript/Browser APIs 对照表把这条决策固化成了可查的速查表:

You think you need What the platform has
query-string / qs new URLSearchParams(location.search)
lodash.clonedeep structuredClone(obj)
lodash.groupby Object.groupBy(arr, fn)
uuid (v4) crypto.randomUUID()

这张表横跨 HTML 元素、CSS 能力、浏览器 API、Swift/SwiftUI、Node 标准库、Python 标准库和数据库多个层面(完整清单见 docs/platform-native.md),深克隆只是其中一行,但它与同目录下的 examples/deep-clone.mdexamples/group-by.md 等示例共同构成一套模式:你以为是需求的东西,往往是平台早就内置的能力

值得注意的是梯子运行在"理解问题之后",而不是代替理解。SKILL.md 明确写着:先读任务触及的代码、追完真实流程,再爬梯子;且"懒"从不用于省略信任边界处的输入校验、防数据丢失的错误处理、安全与无障碍——深克隆场景中对应的就是:该 API 对无法克隆的类型(如 DOM 节点、含函数的对象)会直接抛 DataCloneError 而不是静默截断,行为边界清晰可测。

与其他示例横向对照:短到多少才算"对"?

深克隆的 1 行输出放在整个 examples 目录里并不是孤例。examples/README.md 给出了无/有 Ponytail 的行数对比,同为"装依赖 vs 用内置"模式还有:

Example Without (LOC) With (LOC)
Email Validation 75 3
Debounce 116 10
CSV Sum 20 3
Countdown Timer 267 9
Rate Limiting 128 10

其中 react-countdown.md 展示了无技能模式下的失控形态(267 行:四个变体、styled-components、CSS 动画、"Features" 清单),而有技能模式只交付任务要求的最小实现并附带一句 "Skipped: pause/resume, formatted display, … add when needed"。深克隆则是同类压缩的极致版:不是 267→9,而是"1 个依赖 → 1 个内置"。

如何验证:从仓库复现这条对比

上面的对比全部可验证,不需要相信任何人的转述:

  1. 查看原始对比文档:examples/deep-clone.md
  2. 复现基准:在仓库根目录运行 npx promptfoo@latest eval -c benchmarks/promptfooconfig.yaml(配置见 benchmarks/promptfooconfig.yaml),示例输出即来自该基准的 benchmarks/output.json
  3. 查看决策梯度的完整规则文本:skills/ponytail/SKILL.md
  4. 查看"库 → 平台内置"的完整速查表:docs/platform-native.md

小结:深克隆问题的可迁移结论

  • 默认答案排序:遇到"深克隆"先想到 structuredClone,前提是浏览器(2022+)或 Node.js v17+;更老的运行时才评估 JSON hack(知道它丢什么)或 lodash;
  • 依赖引入的门槛:为单个函数安装整个库是过线行为,"等需要这个库的其他功能时再装";
  • 静默失败优于显式失败的反面JSON.parse(stringify) 的危险在于不报错地丢数据,选内置 API 时优先选行为边界明确的;
  • 方法论层面:这一例是 Ponytail 七级梯子的标准走法——先确认任务真实存在,再优先复用代码库、标准库、平台原生能力,最后才动笔写代码。梯子不是"少写代码"的借口,"懒于方案、不懒于读代码"是 SKILL.md 的原文要求。
登录后查看全文
热门项目推荐
相关项目推荐