自定义迭代器不直接克隆,而是提供流式深度克隆支撑:解耦遍历与克隆逻辑,按需处理、内存友好、可中断定制;通过暴露节点值、路径、类型等元信息,驱动独立克隆处理器逐层构建新结构。

自定义迭代器本身不直接执行克隆,但它能为“流式深度克隆”提供关键支撑:把遍历逻辑与克隆逻辑解耦,让克隆过程按需发生、内存友好、可中断、可定制。核心思路是——用迭代器逐层暴露节点(含路径、类型、引用关系),再由克隆处理器根据这些信息动态构造新节点。
为什么需要“流式”而非一次性深拷贝
一次性深克隆(如 structuredClone 或递归函数)会立即加载整个结构到内存,对超大嵌套对象、远程数据流、或含不可序列化字段的混合结构容易OOM或失败。“流式”意味着:
- 只在当前层级需要时才访问子节点,不预加载全部后代
- 可配合
for...of或yield*逐步处理,便于暂停、过滤、转换 - 天然支持循环引用检测(通过 WeakMap 缓存已见节点)
- 允许对不同字段应用差异化策略(如跳过函数、扁平化 Date、重命名 key)
设计一个支持克隆的深度遍历迭代器
重点不是返回值,而是暴露足够信息:节点值、父上下文、路径、是否为叶子、是否已见过(防循环)。示例结构:
class CloneTraverser {
constructor(root, options = {}) {
this.root = root;
this.getChildren = options.getChildren || (node => node.children);
this.isLeaf = options.isLeaf || (node => !Array.isArray(node) && typeof node !== 'object' || node === null);
}
<p><em>[Symbol.iterator]() {
const seen = new WeakMap();
function</em> walk(node, path = []) {
if (node == null || typeof node !== 'object') {
yield { value: node, path, type: 'primitive', isLeaf: true };
return;
}
if (seen.has(node)) {
yield { value: node, path, type: 'reference', refId: seen.get(node) };
return;
}
const id = Symbol('ref');
seen.set(node, id);
yield { value: node, path, type: 'object', id, isLeaf: this.isLeaf(node) };</p><pre class='brush:php;toolbar:false;'> if (!this.isLeaf(node)) {
const children = this.getChildren(node) || [];
for (let i = 0; i < children.length; i++) {
yield* walk(children[i], [...path, i]);
}
}
}
yield* walk(this.root);} }
这个迭代器不生成克隆体,只输出带元信息的“克隆指令流”。每个 yield 是一个待处理单元。
用迭代器驱动流式克隆处理器
克隆逻辑独立于遍历逻辑,接收迭代器输出,边读边建新结构:
- 遇到
type: 'primitive'→ 直接赋值(数字、字符串等) - 遇到
type: 'object'→ 创建新空对象/数组,记录id → 新实例映射,等待其子项填充 - 遇到
type: 'reference'→ 查映射表,填入对应新实例(解决循环引用) - 可插入自定义规则:如
if (path[0] === 'meta') skip跳过元字段
示例片段:
function streamClone(root, options = {}) {
const traverser = new CloneTraverser(root, options);
const refMap = new WeakMap(); // 原对象 → 新对象
const resultRef = { current: null }; // 存最终结果引用
<p>for (const { value, path, type, id, isLeaf } of traverser) {
if (type === 'primitive') {
setByPath(resultRef.current, path, value);
} else if (type === 'object') {
const newInstance = Array.isArray(value) ? [] : {};
if (path.length === 0) resultRef.current = newInstance;
refMap.set(value, newInstance);
setByPath(resultRef.current, path, newInstance);
} else if (type === 'reference') {
const target = refMap.get(value);
setByPath(resultRef.current, path, target);
}
}</p><p>return resultRef.current;
}</p><p>function setByPath(obj, path, value) {
for (let i = 0; i < path.length - 1; i++) {
obj = Array.isArray(obj) ? obj[path[i]] : obj[path[i]];
}
const lastKey = path[path.length - 1];
if (Array.isArray(obj)) obj[lastKey] = value;
else obj[lastKey] = value;
}实际使用场景与优势
适合以下情况:
- 前端解析大型 JSON Schema,需克隆但跳过
$ref循环字段 - 白板工具(如 boardmix)中复制带状态的嵌套组件树,需重置 ID 但保留层级关系
- 服务端流式处理数据库嵌套文档,边读边脱敏、转换、写入新集合
- 调试时只克隆某条路径下的子树,而非整棵树
它不替换 structuredClone,而是在它力所不及之处提供可控、可观察、可组合的替代路径。

















