跳到主要内容

深拷贝与浅拷贝

问题

什么是深拷贝和浅拷贝?如何实现一个完整的深拷贝函数?

答案

浅拷贝只复制对象的第一层属性,嵌套对象仍然是引用。深拷贝递归复制所有层级,创建完全独立的副本。

浅拷贝 vs 深拷贝

const original = {
name: 'Alice',
profile: { age: 25 }
};

// 浅拷贝
const shallow = { ...original };
shallow.profile.age = 30;
console.log(original.profile.age); // 30 - 被修改了!

// 深拷贝
const deep = JSON.parse(JSON.stringify(original));
deep.profile.age = 35;
console.log(original.profile.age); // 30 - 不受影响

浅拷贝方法

展开运算符

const arr = [1, 2, { a: 3 }];
const arrCopy = [...arr];

const obj = { a: 1, b: { c: 2 } };
const objCopy = { ...obj };

Object.assign

const obj = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, obj);

// 多对象合并
const merged = Object.assign({}, obj1, obj2, obj3);

Array.from / Array.slice

const arr = [1, 2, 3];
const copy1 = Array.from(arr);
const copy2 = arr.slice();
const copy3 = [].concat(arr);

深拷贝方法

JSON.parse(JSON.stringify())

const obj = {
name: 'Alice',
profile: { age: 25 },
hobbies: ['reading', 'coding']
};

const deep = JSON.parse(JSON.stringify(obj));

局限性

const obj = {
// ❌ 无法处理
fn: () => {}, // 函数丢失
sym: Symbol('id'), // Symbol 丢失
undef: undefined, // undefined 丢失
date: new Date(), // 变成字符串
regexp: /test/g, // 变成空对象
map: new Map(), // 变成空对象
set: new Set(), // 变成空对象
nan: NaN, // 变成 null
infinity: Infinity, // 变成 null
circular: null as any // 循环引用报错
};
obj.circular = obj;

// 以下会报错或丢失数据
// JSON.parse(JSON.stringify(obj));

structuredClone(推荐)

// 现代浏览器和 Node.js 17+ 支持
const obj = {
date: new Date(),
map: new Map([['a', 1]]),
set: new Set([1, 2, 3]),
regexp: /test/g,
circular: null as any
};
obj.circular = obj; // 循环引用

const deep = structuredClone(obj);
console.log(deep.date instanceof Date); // true
console.log(deep.map instanceof Map); // true
console.log(deep.circular === deep); // true

// ❌ 仍然不支持
// - 函数
// - Symbol
// - DOM 节点

手写深拷贝

function deepClone<T>(obj: T, map = new WeakMap()): T {
// 处理原始类型
if (obj === null || typeof obj !== 'object') {
return obj;
}

// 处理循环引用
if (map.has(obj as object)) {
return map.get(obj as object);
}

// 处理特殊对象
if (obj instanceof Date) {
return new Date(obj.getTime()) as T;
}

if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags) as T;
}

if (obj instanceof Map) {
const clonedMap = new Map();
map.set(obj as object, clonedMap);
obj.forEach((value, key) => {
clonedMap.set(deepClone(key, map), deepClone(value, map));
});
return clonedMap as T;
}

if (obj instanceof Set) {
const clonedSet = new Set();
map.set(obj as object, clonedSet);
obj.forEach((value) => {
clonedSet.add(deepClone(value, map));
});
return clonedSet as T;
}

// 处理数组和普通对象
const cloned = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));
map.set(obj as object, cloned);

// 复制所有属性(包括 Symbol)
const keys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)];

for (const key of keys) {
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (descriptor) {
if (descriptor.value !== undefined) {
cloned[key] = deepClone((obj as any)[key], map);
} else {
Object.defineProperty(cloned, key, descriptor);
}
}
}

return cloned;
}

完整版深拷贝

type CloneableValue = 
| null
| undefined
| boolean
| number
| string
| symbol
| bigint
| Date
| RegExp
| Map<unknown, unknown>
| Set<unknown>
| Array<unknown>
| Record<string | symbol, unknown>
| Function;

function deepCloneComplete<T extends CloneableValue>(
obj: T,
options: {
cloneFunctions?: boolean;
cloneSymbols?: boolean;
} = {},
map = new WeakMap()
): T {
const { cloneFunctions = false, cloneSymbols = true } = options;

// 原始类型
if (obj === null || typeof obj !== 'object') {
// 函数处理
if (typeof obj === 'function' && cloneFunctions) {
// 使用 bind 创建新函数
return obj.bind({}) as T;
}
return obj;
}

// 循环引用
if (map.has(obj as object)) {
return map.get(obj as object);
}

// Date
if (obj instanceof Date) {
return new Date(obj.getTime()) as T;
}

// RegExp
if (obj instanceof RegExp) {
const cloned = new RegExp(obj.source, obj.flags);
cloned.lastIndex = obj.lastIndex;
return cloned as T;
}

// Map
if (obj instanceof Map) {
const cloned = new Map();
map.set(obj, cloned);
obj.forEach((value, key) => {
cloned.set(
deepCloneComplete(key as CloneableValue, options, map),
deepCloneComplete(value as CloneableValue, options, map)
);
});
return cloned as T;
}

// Set
if (obj instanceof Set) {
const cloned = new Set();
map.set(obj, cloned);
obj.forEach((value) => {
cloned.add(deepCloneComplete(value as CloneableValue, options, map));
});
return cloned as T;
}

// ArrayBuffer
if (obj instanceof ArrayBuffer) {
const cloned = obj.slice(0);
map.set(obj, cloned);
return cloned as T;
}

// TypedArray
if (ArrayBuffer.isView(obj) && !(obj instanceof DataView)) {
const TypedArrayConstructor = obj.constructor as new (
buffer: ArrayBuffer
) => typeof obj;
const cloned = new TypedArrayConstructor(
deepCloneComplete(obj.buffer as CloneableValue, options, map) as ArrayBuffer
);
map.set(obj, cloned);
return cloned as T;
}

// Array
if (Array.isArray(obj)) {
const cloned: unknown[] = [];
map.set(obj, cloned);
obj.forEach((item, index) => {
cloned[index] = deepCloneComplete(item as CloneableValue, options, map);
});
return cloned as T;
}

// 普通对象
const cloned = Object.create(Object.getPrototypeOf(obj));
map.set(obj, cloned);

// 获取所有属性键
const keys: (string | symbol)[] = Object.keys(obj);
if (cloneSymbols) {
keys.push(...Object.getOwnPropertySymbols(obj));
}

for (const key of keys) {
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (descriptor) {
if ('value' in descriptor) {
descriptor.value = deepCloneComplete(
descriptor.value as CloneableValue,
options,
map
);
}
Object.defineProperty(cloned, key, descriptor);
}
}

return cloned;
}

使用示例

// 测试数据
const original = {
// 基础类型
str: 'hello',
num: 42,
bool: true,
nil: null,
undef: undefined,

// 特殊类型
date: new Date('2024-01-01'),
regexp: /test/gi,

// 集合类型
arr: [1, 2, { nested: true }],
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),

// 嵌套对象
nested: {
deep: {
value: 'deep value'
}
},

// Symbol 键
[Symbol('id')]: 'symbol-value',

// 循环引用
self: null as any
};
original.self = original;

// 深拷贝
const cloned = deepCloneComplete(original);

// 验证
console.log(cloned.date !== original.date); // true
console.log(cloned.date.getTime() === original.date.getTime()); // true
console.log(cloned.self === cloned); // true
console.log(cloned.nested !== original.nested); // true

方法对比

方法循环引用函数DateRegExpMap/SetSymbol性能
展开运算符引用引用引用引用
JSON❌报错丢失变字符串空对象空对象丢失
structuredClone❌报错
手写可选

实际应用

React 状态更新

// 不可变更新
const [state, setState] = useState({
user: { name: 'Alice', profile: { age: 25 } }
});

// 方式 1: 展开运算符(浅拷贝)
setState(prev => ({
...prev,
user: {
...prev.user,
profile: {
...prev.user.profile,
age: 26
}
}
}));

// 方式 2: structuredClone
setState(prev => {
const next = structuredClone(prev);
next.user.profile.age = 26;
return next;
});

// 方式 3: 使用 immer
import produce from 'immer';
setState(produce(draft => {
draft.user.profile.age = 26;
}));

数据缓存

class DataCache<T> {
private cache = new Map<string, T>();

set(key: string, value: T): void {
// 存储深拷贝,防止外部修改
this.cache.set(key, structuredClone(value));
}

get(key: string): T | undefined {
const value = this.cache.get(key);
// 返回深拷贝,防止修改缓存
return value ? structuredClone(value) : undefined;
}
}

常见面试问题

Q1: 浅拷贝和深拷贝的区别?

答案

特性浅拷贝深拷贝
复制层级仅第一层所有层级
嵌套对象共享引用独立副本
性能
内存

Q2: JSON.parse(JSON.stringify()) 有什么问题?

答案

  1. 函数丢失:函数会被忽略
  2. Symbol 丢失:Symbol 键和值都会丢失
  3. undefined 丢失:undefined 会被忽略
  4. Date 变字符串:Date 对象变成 ISO 字符串
  5. RegExp 变空对象:正则表达式变成 {}
  6. Map/Set 变空对象:集合类型变成 {}
  7. 循环引用报错:会抛出 TypeError
  8. 特殊值变 null:NaN、Infinity 变成 null

Q3: 如何处理循环引用?

答案

使用 WeakMap 记录已克隆的对象:

function deepClone<T>(obj: T, map = new WeakMap()): T {
if (typeof obj !== 'object' || obj === null) {
return obj;
}

// 检查是否已克隆
if (map.has(obj as object)) {
return map.get(obj as object);
}

const cloned = Array.isArray(obj) ? [] : {};

// 记录已克隆
map.set(obj as object, cloned);

for (const key of Object.keys(obj)) {
(cloned as any)[key] = deepClone((obj as any)[key], map);
}

return cloned as T;
}

Q4: structuredClone 的优缺点?

答案

优点

  • 原生支持,性能好
  • 支持循环引用
  • 支持 Date、RegExp、Map、Set 等

缺点

  • 不支持函数
  • 不支持 Symbol
  • 不支持 DOM 节点
  • 不支持原型链
  • 不支持属性描述符

Q5: 实现一个简单的深拷贝?

答案

function deepClone<T>(obj: T, map = new WeakMap()): T {
// 原始类型
if (obj === null || typeof obj !== 'object') {
return obj;
}

// 循环引用
if (map.has(obj as object)) {
return map.get(obj as object);
}

// Date
if (obj instanceof Date) {
return new Date(obj) as T;
}

// RegExp
if (obj instanceof RegExp) {
return new RegExp(obj) as T;
}

// Array 或 Object
const cloned = Array.isArray(obj)
? []
: Object.create(Object.getPrototypeOf(obj));

map.set(obj as object, cloned);

for (const key of Reflect.ownKeys(obj as object)) {
cloned[key] = deepClone((obj as any)[key], map);
}

return cloned;
}

相关链接