当我们说"深拷贝"时,我们到底在说什么?为什么简单的 JSON.parse(JSON.stringify(obj)) 不够用?如何优雅地处理循环引用、特殊对象、函数引用?本文将深入深拷贝的每一个角落,构建一个真正"完全"的深拷贝函数。
前言:一个看似简单的问题
我们先来看一个最简单的深拷贝:
const obj = { name: '张三', age: 25 };
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy); // { name: '张三', age: 25 }
但这真的够用吗?如果我们有一个复杂对象:
const complexObj = {
date: new Date(),
regex: /test/gi,
func: () => console.log('hello'),
undef: undefined,
inf: Infinity,
nan: NaN,
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
symbol: Symbol('test'),
error: new Error('错误')
};
这时候应该如何处理呢?这就是为什么我们需要一个真正完善的深拷贝解决方案。
深拷贝的基础概念
深拷贝 vs 浅拷贝
- 深拷贝:完全独立的副本,修改拷贝对象的属性值,不会影响原对象
- 浅拷贝:只复制一层,修改拷贝对象的属性值,会影响原对象
浅拷贝示例
const original = {
name: '张三',
address: {
city: '北京',
street: '长安街'
}
};
// 浅拷贝示例
const shallowCopy1 = Object.assign({}, original);
const shallowCopy2 = { ...original };
shallowCopy1.address.city = '上海';
console.log('original.address.city:', original.address.city); // 上海
console.log('shallowCopy1.address.city:', shallowCopy1.address.city); // 上海
深拷贝示例
const original = {
name: '张三',
address: {
city: '北京',
street: '长安街'
}
};
function simpleDeepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
const deepCopy = simpleDeepCopy(original);
deepCopy.address.city = '广州';
console.log('original.address.city:', original.address.city); // 北京
console.log('deepCopy.address.city:', deepCopy.address.city); // 广州
为什么要深拷贝?
- 状态管理要求不可变数据
- 撤销/重做功能需要保存历史快照
- 复杂表单的草稿保存
- 数据处理的隔离环境
- 跨线程/跨进程通信
深拷贝的挑战
- 循环引用:对象相互引用导致无限递归
- 特殊对象:需要保留原型链和构造函数
- 函数:通常不拷贝,但需要处理
- Symbol:作为属性名和值的处理
- 不可枚举属性:需要遍历所有属性描述符
- 原型链:是否需要继承
- 性能:大量数据的拷贝效率
JSON 方法的全面评估
JSON 序列化的局限性
- undefined 会被忽略
- symbol 会被忽略
- function 会被忽略
- 特殊数值会变成 null
- Date / RegExp / Error 等对象会转成字符串
- Map / Set / WeakMap / WeakSet 等会变成空对象
- TypedArray / ArrayBuffer 会变成对象
- 循环引用会导致错误
JSON 方法的适用场景
- 纯数据对象(只有普通对象、数组、字符串、数字、布尔值)
- 与后端 API 通信的数据交换
- 简单的本地存储(localStorage)
- 不需要保持原对象类型的临时副本
- 数据结构已知且可控的内部模块
完整深拷贝要考虑的问题
1. 基础功能
- 支持所有原始类型
- 支持普通对象和数组
- 处理循环引用
- 处理原型链
2. 内置对象
- Date:保持 Date 对象
- RegExp:保持正则表达式
- Map:保持键值对结构
- Set:保持集合结构
- Error:保留错误信息
- Promise:处理状态
- Symbol:作为值和属性名
3. 二进制数据
- ArrayBuffer
- TypedArray 所有类型
- DataView
- SharedArrayBuffer
4. 其他特性
- 支持不可枚举属性
- 支持属性 getter/setter
- 支持冻结/密封对象
- 支持自定义类实例
- 性能优化
递归实现与循环引用检测
基础递归实现
function basicDeepCopy(source) {
// 原始类型直接返回
if (source === null || typeof source !== 'object') {
return source;
}
// 数组或对象
const target = Array.isArray(source) ? [] : {};
// 递归复制每个属性
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = basicDeepCopy(source[key]);
}
}
return target;
}
循环引用检测
function deepCopyWithCycleDetection(source, cache = new WeakMap()) {
// 处理原始类型
if (source === null || typeof source !== 'object') {
return source;
}
// 检测循环引用
if (cache.has(source)) {
console.log('检测到循环引用,返回已缓存的对象');
return cache.get(source);
}
// 创建目标对象
const target = Array.isArray(source) ? [] : {};
// 缓存当前对象
cache.set(source, target);
// 递归复制属性
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = deepCopyWithCycleDetection(source[key], cache);
}
}
return target;
}
性能优化版本
function optimizedDeepCopy(source, cache = new Map()) {
// 快速路径:原始类型
if (source === null || typeof source !== 'object') {
return source;
}
// 快速路径:Date
if (source instanceof Date) {
return new Date(source);
}
// 快速路径:RegExp
if (source instanceof RegExp) {
return new RegExp(source.source, source.flags);
}
// 循环引用检测
if (cache.has(source)) {
return cache.get(source);
}
// 根据类型创建目标对象
let target;
if (Array.isArray(source)) {
target = [];
} else if (source instanceof Map) {
target = new Map();
} else if (source instanceof Set) {
target = new Set();
} else if (source instanceof WeakMap || source instanceof WeakSet) {
// WeakMap/WeakSet 无法遍历,返回新实例
return new source.constructor();
} else {
// 普通对象:使用原对象的构造函数
target = Object.create(Object.getPrototypeOf(source));
}
// 缓存当前对象
cache.set(source, target);
// 处理数组
if (Array.isArray(source)) {
for (let i = 0; i < source.length; i++) {
target[i] = optimizedDeepCopy(source[i], cache);
}
return target;
}
// 处理 Map
if (source instanceof Map) {
for (let [key, value] of source) {
target.set(
optimizedDeepCopy(key, cache),
optimizedDeepCopy(value, cache)
);
}
return target;
}
// 处理 Set
if (source instanceof Set) {
for (let value of source) {
target.add(optimizedDeepCopy(value, cache));
}
return target;
}
// 处理普通对象
const keys = [...Object.keys(source), ...Object.getOwnPropertySymbols(source)];
for (let key of keys) {
const descriptor = Object.getOwnPropertyDescriptor(source, key);
if (descriptor) {
// 复制属性描述符
Object.defineProperty(target, key, {
...descriptor,
value: optimizedDeepCopy(descriptor.value, cache)
});
}
}
return target;
}
内置对象的深拷贝
class BuiltInCopier {
// Date 对象
static copyDate(date) {
return new Date(date.getTime());
}
// RegExp 对象
static copyRegExp(regexp) {
const flags =
(regexp.global ? 'g' : '') +
(regexp.ignoreCase ? 'i' : '') +
(regexp.multiline ? 'm' : '') +
(regexp.dotAll ? 's' : '') +
(regexp.unicode ? 'u' : '') +
(regexp.sticky ? 'y' : '');
return new RegExp(regexp.source, flags);
}
// Error 对象
static copyError(error) {
const copy = new error.constructor(error.message);
copy.stack = error.stack;
copy.name = error.name;
return copy;
}
// Map 对象
static copyMap(map, copyFn) {
const result = new Map();
map.forEach((value, key) => {
result.set(copyFn(key), copyFn(value));
});
return result;
}
// Set 对象
static copySet(set, copyFn) {
const result = new Set();
set.forEach(value => {
result.add(copyFn(value));
});
return result;
}
// WeakMap 对象
static copyWeakMap(weakMap) {
// WeakMap 不可遍历,返回空实例
return new WeakMap();
}
// WeakSet 对象
static copyWeakSet(weakSet) {
// WeakSet 不可遍历,返回空实例
return new WeakSet();
}
// ArrayBuffer 对象
static copyArrayBuffer(arrayBuffer) {
const copy = arrayBuffer.slice(0);
return copy;
}
// TypedArray 对象
static copyTypedArray(typedArray) {
return new typedArray.constructor(typedArray);
}
// DataView 对象
static copyDataView(dataView) {
return new DataView(
this.copyArrayBuffer(dataView.buffer),
dataView.byteOffset,
dataView.byteLength
);
}
// Promise 对象
static copyPromise(promise) {
// Promise 无法复制,返回新的 pending Promise
return new Promise(() => { });
}
}
处理自定义类和原型链
自定义类的深拷贝
function copyCustomClass(instance, cache = new WeakMap()) {
if (cache.has(instance)) {
return cache.get(instance);
}
// 获取构造函数
const Constructor = instance.constructor;
// 创建新实例
let copy;
try {
// 尝试使用构造函数创建新实例
copy = Object.create(Constructor.prototype);
Constructor.apply(copy, []);
} catch (error) {
// 如果构造函数需要参数,则使用 Object.create
copy = Object.create(Constructor.prototype);
}
cache.set(instance, copy);
// 复制所有属性
const allKeys = Reflect.ownKeys(instance);
for (const key of allKeys) {
const descriptor = Object.getOwnPropertyDescriptor(instance, key);
if (descriptor) {
if (descriptor.value !== undefined) {
descriptor.value = comprehensiveDeepCopy(descriptor.value, cache);
}
Object.defineProperty(copy, key, descriptor);
}
}
return copy;
}
原型链的完整处理
function deepCopyWithPrototype(source, cache = new WeakMap()) {
if (source === null || typeof source !== 'object') {
return source;
}
if (cache.has(source)) {
return cache.get(source);
}
let target;
// 获取完整的原型链
const getPrototypeChain = (obj) => {
const chain = [];
let proto = Object.getPrototypeOf(obj);
while (proto && proto !== Object.prototype) {
chain.unshift(proto);
proto = Object.getPrototypeOf(proto);
}
return chain;
};
// 重建原型链
const buildPrototypeChain = (obj, chain) => {
if (chain.length === 0) {
return obj;
}
let current = obj;
for (let i = 0; i < chain.length; i++) {
const proto = chain[i];
const protoCopy = Object.create(Object.getPrototypeOf(proto));
// 复制原型上的属性
const keys = Reflect.ownKeys(proto);
for (const key of keys) {
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
if (descriptor) {
if (descriptor.value !== undefined) {
descriptor.value = deepCopyWithPrototype(descriptor.value, cache);
}
Object.defineProperty(protoCopy, key, descriptor);
}
}
Object.setPrototypeOf(current, protoCopy);
current = protoCopy;
}
return obj;
};
// 处理不同类型
if (source instanceof Date) {
target = new Date(source);
} else if (source instanceof RegExp) {
target = new RegExp(source);
} else if (Array.isArray(source)) {
target = [];
} else if (source instanceof Map) {
target = new Map();
} else if (source instanceof Set) {
target = new Set();
} else {
// 普通对象:先创建空对象,再设置原型链
target = {};
}
cache.set(source, target);
// 获取并重建原型链
const protoChain = getPrototypeChain(source);
buildPrototypeChain(target, protoChain);
// 复制自身属性
const allKeys = Reflect.ownKeys(source);
for (const key of allKeys) {
const descriptor = Object.getOwnPropertyDescriptor(source, key);
if (descriptor) {
if (descriptor.value !== undefined) {
descriptor.value = deepCopyWithPrototype(descriptor.value, cache);
}
Object.defineProperty(target, key, descriptor);
}
}
return target;
}
最终完整解决方案
// 深拷贝配置选项
class DeepCopyOptions {
constructor({
copySymbols = true,
copyNonEnumerables = true,
preservePrototype = true,
copyFunctions = false,
copyWeakCollections = false,
maxDepth = Infinity,
onError = (error, key, value) => console.warn(`拷贝 ${key} 时出错:`, error)
} = {}) {
this.copySymbols = copySymbols;
this.copyNonEnumerables = copyNonEnumerables;
this.preservePrototype = preservePrototype;
this.copyFunctions = copyFunctions;
this.copyWeakCollections = copyWeakCollections;
this.maxDepth = maxDepth;
this.onError = onError;
}
}
// 最终版深拷贝
function cloneDeep(source, options = new DeepCopyOptions(), depth = 0, cache = new WeakMap()) {
// 深度限制
if (depth >= options.maxDepth) {
return source;
}
// 处理原始类型
if (source === null || typeof source !== 'object') {
// 处理函数(可选)
if (typeof source === 'function' && options.copyFunctions) {
// 简单的函数复制,不保证完全等价
return new Function('return ' + source.toString())();
}
return source;
}
// 循环引用检测
if (cache.has(source)) {
return cache.get(source);
}
let target;
try {
// 根据类型创建目标对象
const constructor = source.constructor;
// 内置对象处理
switch (constructor) {
case Date:
target = new Date(source);
break;
case RegExp:
target = new RegExp(source.source, source.flags);
target.lastIndex = source.lastIndex;
break;
case Error:
target = new source.constructor(source.message);
target.stack = source.stack;
target.name = source.name;
break;
case Map:
target = new Map();
cache.set(source, target);
source.forEach((value, key) => {
target.set(
cloneDeep(key, options, depth + 1, cache),
cloneDeep(value, options, depth + 1, cache)
);
});
return target;
case Set:
target = new Set();
cache.set(source, target);
source.forEach(value => {
target.add(cloneDeep(value, options, depth + 1, cache));
});
return target;
case WeakMap:
target = options.copyWeakCollections ? new WeakMap() : source;
cache.set(source, target);
return target;
case WeakSet:
target = options.copyWeakCollections ? new WeakSet() : source;
cache.set(source, target);
return target;
case ArrayBuffer:
target = source.slice(0);
break;
case DataView:
target = new DataView(
cloneDeep(source.buffer, options, depth + 1, cache),
source.byteOffset,
source.byteLength
);
break;
default:
// 检查 TypedArray
if (ArrayBuffer.isView(source) && !(source instanceof DataView)) {
target = new constructor(
cloneDeep(source.buffer, options, depth + 1, cache),
source.byteOffset,
source.length
);
break;
}
// 普通对象或数组
if (constructor === Object || constructor === Array) {
target = Array.isArray(source) ? [] : {};
} else if (options.preservePrototype) {
// 保持原型链
target = Object.create(constructor.prototype);
} else {
target = {};
}
break;
}
} catch (error) {
options.onError(error, 'constructor', source);
target = Array.isArray(source) ? [] : {};
}
// 缓存当前对象
cache.set(source, target);
// 处理数组
if (Array.isArray(source)) {
for (let i = 0; i < source.length; i++) {
try {
target[i] = cloneDeep(source[i], options, depth + 1, cache);
} catch (error) {
options.onError(error, i, source[i]);
target[i] = undefined;
}
}
return target;
}
// 获取所有属性键
const allKeys = [];
if (options.copySymbols) {
allKeys.push(...Object.getOwnPropertySymbols(source));
}
if (options.copyNonEnumerables) {
allKeys.push(...Object.getOwnPropertyNames(source));
} else {
allKeys.push(...Object.keys(source));
}
// 复制属性
for (const key of allKeys) {
try {
const descriptor = Object.getOwnPropertyDescriptor(source, key);
if (descriptor) {
if (descriptor.value !== undefined) {
descriptor.value = cloneDeep(descriptor.value, options, depth + 1, cache);
}
Object.defineProperty(target, key, descriptor);
}
} catch (error) {
options.onError(error, key, source[key]);
}
}
return target;
}
// 便利函数
const deepClone = {
// 快速克隆(适用于大多数场景)
quick: (obj) => cloneDeep(obj),
// 完整克隆(保留所有特性)
full: (obj) => cloneDeep(obj, new DeepCopyOptions({
copySymbols: true,
copyNonEnumerables: true,
preservePrototype: true,
copyFunctions: false,
copyWeakCollections: false
})),
// 严格克隆(尽可能完整)
strict: (obj) => cloneDeep(obj, new DeepCopyOptions({
copySymbols: true,
copyNonEnumerables: true,
preservePrototype: true,
copyFunctions: true,
copyWeakCollections: true
})),
// 数据克隆(只保留可序列化的数据)
data: (obj) => cloneDeep(obj, new DeepCopyOptions({
copySymbols: false,
copyNonEnumerables: false,
preservePrototype: false,
copyFunctions: false,
copyWeakCollections: false
}))
};
结语
最好的深拷贝是不需要深拷贝。通过良好的架构设计、使用不可变数据、避免深层嵌套等方式,可以减少对深拷贝的需求。当必须使用时,选择合适的实现方案,既满足需求又不过度设计。对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!