mcpField
W
web-utilities-native-js

web-utilities-native-js

github.com/agents-inc开发2644 次安装2 天前更新
代码仓库

关于 web-utilities-native-js

面向 JavaScript 和 TypeScript 开发者的实用模式指南,说明如何用原生 API 替代常见工具库函数,并提醒克隆与不可变更新的边界。

主要功能

  1. 01原生 API 替代工具库函数
  2. 02深拷贝与结构化克隆
  3. 03不可变数组更新
  4. 04数组分组与 Set 运算
  5. 05对象转换与函数工具模式

适用场景

  1. 01减少实用工具依赖
  2. 02安全复制包含日期或集合的数据
  3. 03对共享数组执行不可变更新
  4. 04按键分组数据并执行集合运算

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents-inc/skills --skill web-utilities-native-js

安装后在 Claude Code 中输入/web-utilities-native-js确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.yaml
reference.md
examples/
arrays.md
core.md
objects.md

详细介绍

原生 JavaScript 工具模式

快速指南: 大多数工具库调用现在都有原生等效方法,且不会增加打包体积。 会改变代码编写方式的有四项:用于深拷贝的 structuredClone、ES2023 不可变数组方法(toSorted、toReversed、toSpliced、with)、Object.groupBy / Map.groupBy,以及 ES2025 Set 操作。两个容易让人意外的事实:结构化克隆会丢弃原型,遇到函数时会抛出异常;不可变数组方法只会进行一层浅拷贝。

详细资源:

  • examples/core.md — 克隆、不可变更新、负索引、从末尾搜索
  • examples/arrays.md — 去重、集合操作、分组、分块、排序、清理
  • examples/objects.md — 类型化的 pick/omit/mapValues、深度合并、查找映射、冻结
  • reference.md — lodash 到原生方法对照表、函数工具(debounce、throttle、memoize)、可用性

<critical_requirements>

编写工具代码之前

优先使用原生方法,再考虑添加依赖。 reference.md 中的 lodash 到原生方法对照表可以通过一次查找解决大多数问题,而且原生调用不会带来额外内容。

使用 structuredClone 进行深拷贝。 JSON 往返转换会将 Date 变成字符串,将 Map 或 Set 变成 {},而且不会报错——问题会在之后首次调用 .getTime() 时才暴露。

数组会被多个地方共用时,使用 to* 数组方法。 sort、reverse 和 splice 会原地改写数组,因此,使用 sort 来“返回排序后的副本”的函数实际上修改了调用方的数据。

反复检查成员关系之前,先转换为 Set。 在 filter 中使用 includes 会导致 O(n²) 的复杂度,元素达到几千个时就会显现差异。

</critical_requirements>


自动检测: structuredClone、Object.groupBy、Map.groupBy、toSorted、toReversed、toSpliced、 Array.prototype.with、findLast、findLastIndex、at(-1)、Object.hasOwn、Object.fromEntries、 Set.prototype.union、Set.prototype.intersection、Set.prototype.difference、symmetricDifference、 isSubsetOf、isDisjointFrom、lodash 替代方案、原生 JavaScript 工具

适用范围:

  • 数组操作 — 去重、分组、分块、扁平化、不可变排序、从末尾搜索
  • 对象操作 — 挑选、排除、合并、键/值转换、查找映射
  • 集合代数 — 并集、交集、差集、子集测试
  • 无需依赖即可进行深拷贝
  • 手写函数工具 — debounce、throttle、memoize、once

其他内容:

  • 日期运算、解析和格式化 — 单靠 Date 无法满足需求
  • 响应式更新 — 这些方法会生成新的引用,而如何处理新引用取决于负责管理状态的对象
  • 模式验证和运行时解析 — 这些工具会重塑已可信的数据

<decision_framework>

原生方法还是依赖

按顺序考虑以下三种情况:

  • 存在原生方法 — 使用它。reference.md 中的对照表可供查阅。
  • 没有原生方法,但实现只需十行代码 — 自行编写。本技能中提供了 chunk、pick、omit、 compact、deepMerge、debounce、throttle、memoize 和 once 的完整类型化实现。
  • 边缘情况本身就是功能的一部分 — 使用依赖。支持 cancel、flush 和前沿/后沿触发的 debounce;支持按键采用不同数组策略的深度合并;对足够大的数据集进行惰性求值,使中间数组会造成影响。重新实现这些功能却处理不当,代价远高于它们占用的字节数。

</decision_framework>


核心模式

模式 1:使用 structuredClone 进行深拷贝

保留 Date、Map、Set、RegExp、类型化数组和循环引用——这些都是 JSON 往返转换会破坏的内容。

const snapshot = structuredClone(state);
// Preserved: Date, Map, Set, RegExp, ArrayBuffer, circular refs
// Throws on: functions, DOM nodes, Proxy, symbols
// Silently lost: the prototype — a class instance clones to a plain object

完整代码:examples/core.md


### 模式 2:不可变数组方法(ES2023)

四种 to* 方法会返回新数组,而经典方法会改写原数组。

items.toSorted((a, b) => a.price - b.price); // vs .sort()
items.toReversed(); // vs .reverse()
items.toSpliced(index, 1); // vs .splice()
items.with(index, { ...items[index], done: true }); // vs items[i] = x

复制是浅复制:新数组持有相同的元素对象,因此上面的更新会先展开再写入。

完整代码:examples/core.md


模式 3:Object.groupBy 和 Map.groupBy(ES2024)
const byStatus = Object.groupBy(orders, (order) => order.status);
// { pending: [...], shipped: [...] } — null-prototype, values are `T[] | undefined`

const byCustomer = Map.groupBy(orders, (order) => order.customer);
// Map keyed by the object itself — use this when the key is not a string

Object.groupBy 会将键强制转换为字符串,并返回一个无原型对象,因此应使用 Object.hasOwn(byStatus, key),而不是 byStatus.hasOwnProperty(key)。

完整代码:examples/arrays.md


模式 4:Set 操作(ES2025)
setA.union(setB);
setA.intersection(setB);
setA.difference(setB); // in A, not in B
setA.symmetricDifference(setB);
setA.isSubsetOf(setB);
setA.isSupersetOf(setB);
setA.isDisjointFrom(setB);

每种操作都会返回一个 Set,因此需要数组时请展开。参数可以是任何具备 size、 has 和 keys 的类 Set 对象——Map 符合要求,普通数组则不符合。

完整代码:examples/arrays.md


模式 5:从末尾搜索和索引
logs.findLast((log) => log.level === "error"); // ES2023, single pass
logs.findLastIndex((log) => log.level === "error");
items.at(-1); // ES2022, undefined when empty
"file.backup.txt".split(".").at(-1); // "txt" — strings too

完整代码:examples/core.md


模式 6:将 Object.fromEntries 作为转换枢纽

entries → map/filter → fromEntries 这一模式可涵盖 pick、omit、invert、mapValues、mapKeys 和数组转查找表, 因此这些辅助函数各只需四行代码,无须引入依赖。

const keyed = Object.fromEntries(items.map((item) => [item.id, item]));
const withTax = Object.fromEntries(
  Object.entries(prices).map(([k, v]) => [k, v * 1.1]),
);
const passing = Object.fromEntries(
  Object.entries(scores).filter(([, score]) => score >= 90),
);

完整代码:examples/objects.md


<red_flags>

注意事项

运行时会出错:

  • 对包含函数、DOM 节点或 Proxy 的内容调用 structuredClone 会抛出 DataCloneError——请将数据与行为分开克隆。
  • JSON.parse(JSON.stringify(x)) 不会抛出异常,而是会破坏数据。Date 会变成字符串,Map 或 Set 会变成 {},undefined 成员会消失,而问题会在对结果进行首次方法调用时显现。
  • Object.groupBy 返回无原型对象,因此 result.hasOwnProperty(...) 会抛出异常。请使用 Object.hasOwn。
  • Set 方法会拒绝普通数组——它们需要具备 size、has 和 keys 的对象。

行为可能出乎意料:

  • structuredClone 会丢弃原型。类实例会被克隆为字段相同但不包含任何方法的普通对象。
  • 不传比较器时,toSorted() 会按字符串排序,因此 [10, 9, 1].toSorted() 的结果是 [1, 10, 9]——这与 sort() 一直以来的行为完全相同。
  • to* 方法只会复制一层。修改新数组中的元素,也会修改旧数组中的同一个对象。
  • 无论编译器选项如何,arr.at(-1) 的类型都是 T | undefined,因此必须处理空数组的情况。除非启用了 noUncheckedIndexedAccess,否则 arr[arr.length - 1] 的类型是 T——正是这种形式会在未进行检查时让 undefined 流入。
  • 对 Error 调用 structuredClone 会成功并保留标准字段,但附加在其上的 code、status 或其他内容不会出现在副本中。
  • filter(Boolean) 会连同空值一起过滤掉 0、"" 和 false,而 TypeScript 不会收窄结果类型——filter((x): x is T => x != null) 可以正确完成这两项工作。
  • Object.freeze 是浅冻结;在严格模式之外,对冻结对象的写入会静默失败,而不是抛出异常。
  • Object.keys 会先按升序返回类似整数的键,无论插入顺序如何——只有字符串键会保留插入顺序。

</red_flags>

评论

支持 Markdown