前言
深拷贝(Deep Copy)是指创建一个对象或数据结构的完全独立副本,使得新对象与原对象之间没有任何引用关系。对于嵌套的对象或数组,深拷贝会递归地复制每个层级的内容。
本文汇总了在实践中积累的方法与经验,若其中有任何不准确或有待商榷之处,欢迎各位读者批评指正。
1. 使用 structuredClone()
从 ECMAScript 2023 (ES14) 开始引入,structuredClone() 方法可以执行结构化克隆算法,适用于大多数内置类型的数据结构。
let original = { a: 1, b: { c: 2 } };
let clone = structuredClone(original);
2. 使用 JSON 序列化和反序列化
通过 JSON.stringify() 和 JSON.parse() 来实现深拷贝。
let original = { a: 1, b: { c: 2 } };
let clone = JSON.parse(JSON.stringify(original));
3. 使用第三方库(如 Lodash 的 _.cloneDeep)
let _ = require('lodash');
let original = { a: 1, b: { c: 2 }, d: [3, 4] };
let clone = _.cloneDeep(original);
4. 自定义递归函数
编写自己的递归函数来遍历对象并逐层复制。
// path ../determineDataType/index.mjs
/**
* @description 数据类型
* @readonly
* @enum {String}
*/
export const DataType = {
Object: "Object",
Array: "Array",
Function: "Function",
String: "String",
Number: "Number",
Boolean: "Boolean",
Undefined: "Undefined",
Null: "Null",
Symbol: "Symbol",
BigInt: "BigInt",
Map: "Map",
Set: "Set",
WeakMap: "WeakMap",
WeakSet: "WeakSet",
Date: "Date",
Error: "Error",
Window: "Window",
Global: "Global",
RegExp: "RegExp",
};
/**
* @description 类型映射表
* @readonly
* @type {Map<string,DataType>}
*/
const DataTypeMap = new Map([
["[object Object]", DataType.Object],
["[object Array]", DataType.Array],
["[object Function]", DataType.Function],
["[object String]", DataType.String],
["[object Number]", DataType.Number],
["[object Boolean]", DataType.Boolean],
["[object Undefined]", DataType.Undefined],
["[object Null]", DataType.Null],
["[object Symbol]", DataType.Symbol],
["[object BigInt]", DataType.BigInt],
["[object Map]", DataType.Map],
["[object Set]", DataType.Set],
["[object WeakMap]", DataType.WeakMap],
["[object WeakSet]", DataType.WeakSet],
["[object Date]", DataType.Date],
["[object Error]", DataType.Error],
["[object Window]", DataType.Window],
["[object Global]", DataType.Global],
["[object RegExp]", DataType.RegExp],
]);
/**
* @description 判断数据类型
* @param {any} obj
* @returns {DataType}
*/
export const determineDataType = (obj) =>
DataTypeMap.get(Object.prototype.toString.call(obj));
import { DataType, determineDataType } from "../determineDataType/index.mjs";
/**
* @description 支持的数据类型
* @type {Array<DataType>}
*/
const supportDataType = [
DataType.Object,
DataType.Array,
DataType.String,
DataType.Number,
DataType.Boolean,
DataType.Undefined,
DataType.Null,
DataType.BigInt,
DataType.Date,
DataType.Map,
];
/**
* @description 深拷贝
* @param {Object} obj
* @returns {Object | null}
*/
export const deepClone = (obj) => {
const dataType = determineDataType(obj);
if (
dataType !== DataType.Object &&
dataType !== DataType.Array &&
dataType !== DataType.Map
) {
console.warn("It only supports Object,Array,Map.");
return null;
}
/**
* @description 克隆结果
* @type {any}
*/
let result;
/**
*
* @param {any} value
* @param {string | number} key
* @param {any} newTarget
*/
const cloneLogic = (value, key, newTarget) => {
const newTargetDataType = determineDataType(newTarget);
/**
* @description 写值
* @param {any} _key
* @param {any} _value
*/
const setNewTargetValue = (_key, _value) => {
if (
newTargetDataType === DataType.Object ||
newTargetDataType === DataType.Array
) {
newTarget[_key] = _value;
} else if (newTargetDataType === DataType.Map) {
newTarget.set(_key, _value);
}
};
/**
* @description 读值
* @param {any} _key
* @returns {any}
*/
const getNewTargetValue = (_key) => {
if (
newTargetDataType === DataType.Object ||
newTargetDataType === DataType.Array
) {
return newTarget[_key];
} else if (newTargetDataType === DataType.Map) {
return newTarget.get(_key);
}
};
const valueDataType = determineDataType(value);
if (supportDataType.includes(valueDataType)) {
if (valueDataType === DataType.Object) {
if (value === obj) {
setNewTargetValue(key, newTarget);
} else {
setNewTargetValue(key, {});
deepCloneObject(value, getNewTargetValue(key));
}
} else if (valueDataType === DataType.Array) {
setNewTargetValue(key, []);
deepCloneArray(value, getNewTargetValue(key));
} else if (valueDataType === DataType.Date) {
setNewTargetValue(key, new Date(value));
} else {
setNewTargetValue(key, value);
}
} else {
console.warn(
`${key}'s DataType is ${valueDataType}. It only supports ${supportDataType.join(
","
)}`
);
}
};
/**
* @description 深拷贝对象
* @param {Object} target
* @param {Object} newObj
*/
const deepCloneObject = (target, newObj) => {
const keys = Object.keys(target);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
cloneLogic(target[key], key, newObj);
}
};
/**
* @description 深拷贝数组
* @param {Array} target
* @param {Array} newArr
*/
const deepCloneArray = (target, newArr) => {
for (let index = 0; index < target.length; index++) {
cloneLogic(target[index], index, newArr);
}
};
/**
* @description 深拷贝Map
* @param {Map} target
* @param {Map} newMap
*/
const deepCloneMap = (target, newMap) => {
for (const [key, value] of target) {
cloneLogic(value, key, newMap);
}
};
if (dataType === DataType.Object) {
result = {};
deepCloneObject(obj, result);
} else if (dataType === DataType.Array) {
result = [];
deepCloneArray(obj, result);
} else if (dataType === DataType.Map) {
result = new Map();
deepCloneMap(obj, result);
}
return result;
};
// test
import { deepClone } from "./index.mjs";
let original = { a: 1, b: { c: 2 } };
original.self = original;
let clone = deepClone(original);