JS中关于深拷贝和浅拷贝
JS 有基本数据类型和复合数据类型,基本数据类型包括 null,undefined,boolean,number,string,symbol。针对这些数据类型都是值拷贝。复合数据类型包括 object,以及衍生的内置数据类型,Array, Map, Set 等等。默认对复合数据类型的赋值操作都是引用拷贝,就是浅拷贝。
# JSON.stringify 实现深拷贝
一种常用的方式是使用 JSON 接口先转换成字符串再解析成对象。
const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));
虽然 V8 引擎优化了 JSON 的解析速度,但是这种方式还是有一些限制条件的,如果数据中存在 Map, Date 等 JS 内置数据结构就没法正常复制。
# 手动实现 deepClone
export const isObject = (obj: any): boolean => {
return obj !== null && typeof obj === 'object';
};
export const objectToString = Object.prototype.toString;
export const toTypeString = (value: unknown): string =>
objectToString.call(value);
export const isMap = (val: unknown): val is Map<any, any> =>
toTypeString(val) === '[object Map]';
export const isSet = (val: unknown): val is Set<any> =>
toTypeString(val) === '[object Set]';
export const isDate = (val: unknown): val is Date =>
toTypeString(val) === '[object Date]';
export const isRegExp = (val: unknown): val is RegExp =>
toTypeString(val) === '[object RegExp]';
export function deepClone<T>(val: T): T {
if (!isObject(val)) {
return val;
}
if (Array.isArray(val)) {
return val.map(deepClone) as typeof val;
}
if (isDate(val)) {
return new Date(val) as typeof val;
}
if (isMap(val)) {
return new Map(val) as typeof val;
}
if (isSet(val)) {
return new Set(val) as typeof val;
}
if (isRegExp(val)) {
return new RegExp(val.source, val.flags) as typeof val;
}
return Object.keys(val).reduce((acc, key) => {
acc[key as keyof T] = deepClone(val[key as keyof T]);
return acc;
}, {} as T);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
手动实现 deepClone 可以处理 Date, Map, Set, RegExp 这些数据结构,但这也不是完美的解决方案,例如二进制的数据结构 ArrayBuffer
就没在处理范围,但对于绝大多数场景也足够用了。
# 使用 structuredClone
const myDeepCopy = structuredClone(myOriginal);
最新的浏览器标准添加了 structuredClone
函数,可以实现常见数据结构的复制,但是有些数据结构也是不能复制的,例如 Function
。不过 Function 也没有复制的必要。
由于这个函数最近才添加,实际使用中可能还需要搭配 polyfill (opens new window)。
# 总结
实际开发工作中大家可以根据自己的需要选择合适的深拷贝解决方案。
# 参考
structuredClone (opens new window)
Deep-copying in JavaScript using structuredClone (opens new window)