编码母题 / P2 / Node 与 JavaScript / 03
支持循环引用的深拷贝
先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。
重要程度岗位专项难度困难预计完成50–70 分钟
题目是什么
深拷贝普通对象、集合和常见内置对象,保留循环与共享引用;普通对象还需保留原型、Symbol key 和属性描述符。明确函数等不可复制值的边界。
输入输出示例
同一个 ArrayBuffer 被两个视图共享时,克隆后的两个视图也必须共享同一份新缓冲区。
练习起手式与验证
复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。
/*
* 题目:支持循环引用的深拷贝
* 要求:深拷贝普通对象、集合和常见内置对象,保留循环与共享引用;普通对象还需保留原型、
* Symbol key 和属性描述符。明确函数等不可复制值的边界。
* 示例:同一个 ArrayBuffer 被两个视图共享时,
* 克隆后的两个视图也必须共享同一份新缓冲区。
*/
function deepClone(value, seen = new WeakMap()) {}
const symbol = Symbol("s");
const shared = { n: 1 };
const source = Object.create({ inherited: true });
Object.assign(source, {
sharedA: shared,
sharedB: shared,
map: new Map([[shared, new Set([shared])]]),
date: new Date(1),
});
source.self = source;
source[symbol] = /a/gi;
const copy = deepClone(source);
console.log(copy !== source); // 预期:true
console.log(copy.self); // 预期:copy
console.log(copy.sharedA); // 预期:copy.sharedB
/*
* 题目:支持循环引用的深拷贝
* 要求:深拷贝普通对象、集合和常见内置对象,保留循环与共享引用;普通对象还需保留原型、
* Symbol key 和属性描述符。明确函数等不可复制值的边界。
* 示例:同一个 ArrayBuffer 被两个视图共享时,
* 克隆后的两个视图也必须共享同一份新缓冲区。
*/
function deepClone(value, seen = new WeakMap()) {}
const symbol = Symbol("s");
const shared = { n: 1 };
const source = Object.create({ inherited: true });
Object.assign(source, {
sharedA: shared,
sharedB: shared,
map: new Map([[shared, new Set([shared])]]),
date: new Date(1),
});
source.self = source;
source[symbol] = /a/gi;
const copy = deepClone(source);
console.log(copy !== source); // 预期:true
console.log(copy.self); // 预期:copy
console.log(copy.sharedA); // 预期:copy.sharedB解题思路
展开思路、不变量与常见边界
关键切入点:WeakMap 在递归前登记源与目标;视图必须复用同一个已克隆缓冲区。
核心模型:WeakMap 必须在递归子属性前记录 source->target,否则自引用无限递归。 使用 Reflect.ownKeys 与 descriptor,数据属性递归复制 value,访问器保留 getter/ setter。时间和空间 O(n),n 为可达属性及集合元素数。
参考代码与验证
“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。
// 关键机制:WeakMap 在递归前登记源与目标;视图必须复用同一个已克隆缓冲区。
function deepClone(value, seen = new WeakMap()) {
if (value === null || typeof value !== "object") return value;
if (seen.has(value)) return seen.get(value);
if (value instanceof Date) {
const copy = new Date(value.getTime());
seen.set(value, copy);
return copy;
}
if (value instanceof RegExp) {
const copy = new RegExp(value.source, value.flags);
copy.lastIndex = value.lastIndex;
seen.set(value, copy);
return copy;
}
if (value instanceof ArrayBuffer) {
const copy = value.slice(0);
seen.set(value, copy);
return copy;
}
if (ArrayBuffer.isView(value)) {
const buffer = deepClone(value.buffer, seen);
const copy = value instanceof DataView
? new DataView(buffer, value.byteOffset, value.byteLength)
: new value.constructor(buffer, value.byteOffset, value.length);
seen.set(value, copy);
return copy;
}
if (
value instanceof WeakMap ||
value instanceof WeakSet ||
value instanceof Promise
)
return value;
if (value instanceof Map) {
const copy = new Map();
seen.set(value, copy);
for (const [k, v] of value)
copy.set(deepClone(k, seen), deepClone(v, seen));
return copy;
}
if (value instanceof Set) {
const copy = new Set();
seen.set(value, copy);
for (const item of value) copy.add(deepClone(item, seen));
return copy;
}
const copy = Array.isArray(value)
? []
: Object.create(Object.getPrototypeOf(value));
seen.set(value, copy);
for (const key of Reflect.ownKeys(value)) {
const descriptor = Object.getOwnPropertyDescriptor(value, key);
if (Object.hasOwn(descriptor, "value"))
descriptor.value = deepClone(descriptor.value, seen);
Object.defineProperty(copy, key, descriptor);
}
return copy;
}
// 三组以上可直接运行的验证。
const symbol = Symbol("s");
const shared = { n: 1 };
const source = Object.create({ inherited: true });
Object.assign(source, {
sharedA: shared,
sharedB: shared,
map: new Map([[shared, new Set([shared])]]),
date: new Date(1),
});
source.self = source;
source[symbol] = /a/gi;
const copy = deepClone(source);
console.log(copy !== source); // 预期:true
console.log(copy.self); // 预期:copy
console.log(copy.sharedA); // 预期:copy.sharedB
console.log(copy.sharedA !== shared); // 预期:true
console.log(copy.map.get(copy.sharedA).has(copy.sharedA)); // 预期:true
console.log(copy[symbol].flags); // 预期:"gi"
console.log(Object.getPrototypeOf(copy)); // 预期:Object.getPrototypeOf(source)
const buffer = new ArrayBuffer(8);
const bytes = new Uint8Array(buffer, 1, 3);
const data = new DataView(buffer, 2, 2);
const views = deepClone({ buffer, bytes, data, again: bytes });
console.log(views.buffer !== buffer); // 预期:true
console.log(views.bytes.buffer); // 预期:views.buffer
console.log(views.data.buffer); // 预期:views.buffer
console.log(views.again); // 预期:views.bytes
console.log(views.bytes.byteOffset); // 预期:1
console.log(views.data.byteLength); // 预期:2
const date = new Date(1);
const dates = deepClone([date, date]);
console.log(dates[0]); // 预期:dates[1]
// 关键机制:WeakMap 在递归前登记源与目标;视图必须复用同一个已克隆缓冲区。
function deepClone(value, seen = new WeakMap()) {
if (value === null || typeof value !== "object") return value;
if (seen.has(value)) return seen.get(value);
if (value instanceof Date) {
const copy = new Date(value.getTime());
seen.set(value, copy);
return copy;
}
if (value instanceof RegExp) {
const copy = new RegExp(value.source, value.flags);
copy.lastIndex = value.lastIndex;
seen.set(value, copy);
return copy;
}
if (value instanceof ArrayBuffer) {
const copy = value.slice(0);
seen.set(value, copy);
return copy;
}
if (ArrayBuffer.isView(value)) {
const buffer = deepClone(value.buffer, seen);
const copy = value instanceof DataView
? new DataView(buffer, value.byteOffset, value.byteLength)
: new value.constructor(buffer, value.byteOffset, value.length);
seen.set(value, copy);
return copy;
}
if (
value instanceof WeakMap ||
value instanceof WeakSet ||
value instanceof Promise
)
return value;
if (value instanceof Map) {
const copy = new Map();
seen.set(value, copy);
for (const [k, v] of value)
copy.set(deepClone(k, seen), deepClone(v, seen));
return copy;
}
if (value instanceof Set) {
const copy = new Set();
seen.set(value, copy);
for (const item of value) copy.add(deepClone(item, seen));
return copy;
}
const copy = Array.isArray(value)
? []
: Object.create(Object.getPrototypeOf(value));
seen.set(value, copy);
for (const key of Reflect.ownKeys(value)) {
const descriptor = Object.getOwnPropertyDescriptor(value, key);
if (Object.hasOwn(descriptor, "value"))
descriptor.value = deepClone(descriptor.value, seen);
Object.defineProperty(copy, key, descriptor);
}
return copy;
}
// 三组以上可直接运行的验证。
const symbol = Symbol("s");
const shared = { n: 1 };
const source = Object.create({ inherited: true });
Object.assign(source, {
sharedA: shared,
sharedB: shared,
map: new Map([[shared, new Set([shared])]]),
date: new Date(1),
});
source.self = source;
source[symbol] = /a/gi;
const copy = deepClone(source);
console.log(copy !== source); // 预期:true
console.log(copy.self); // 预期:copy
console.log(copy.sharedA); // 预期:copy.sharedB
console.log(copy.sharedA !== shared); // 预期:true
console.log(copy.map.get(copy.sharedA).has(copy.sharedA)); // 预期:true
console.log(copy[symbol].flags); // 预期:"gi"
console.log(Object.getPrototypeOf(copy)); // 预期:Object.getPrototypeOf(source)
const buffer = new ArrayBuffer(8);
const bytes = new Uint8Array(buffer, 1, 3);
const data = new DataView(buffer, 2, 2);
const views = deepClone({ buffer, bytes, data, again: bytes });
console.log(views.buffer !== buffer); // 预期:true
console.log(views.bytes.buffer); // 预期:views.buffer
console.log(views.data.buffer); // 预期:views.buffer
console.log(views.again); // 预期:views.bytes
console.log(views.bytes.byteOffset); // 预期:1
console.log(views.data.byteLength); // 预期:2
const date = new Date(1);
const dates = deepClone([date, date]);
console.log(dates[0]); // 预期:dates[1]
复杂度分析
时间复杂度:O(n)。每个可达对象、属性及集合元素只克隆有限次。
空间复杂度:O(n)。WeakMap 和新对象图均与可达结构规模成正比。