Implementation and advanced writing of deep copy
version one
function deepClone(target) {
return JSON.parse(JSON.stringify(target));
}
const a = { name: "fryao", age: 18 };
const b = deepClone(a);
console.log(b); // { name: 'fryao', age: 18}
console.log(b === a); // false
While this is fine to use most of the time, there are a number of downsides to this approach:
- If there is a field value in the object that is undefined, the field will disappear directly after conversion
- If the object has a field value as a RegExp object, the field value will become {} after conversion
- If the object has a field value of NaN, +-Infinity, the field value becomes null after conversion
- If the object has a ring reference, the conversion will directly report an error
version two
Since it is a deep copy of the object, we can create an empty object and copy the values of the original object that need to be copied one by one.
function deepClone(target) {
if (typeof target !== "object") {
return target;
}
const temp = Array.isArray(target) ? [] : {};
for (const key in target) {
temp[key] = typeof target[key] == "object" ? deepCopy(target[key] ) : target[key];
}
return temp;
}
const a = {
name: "fryao",
age: 23,
hobbies: { sports: "basketball"},
color:['red','green']
};
const b = deepClone(a);
console.log(b === a); // false
</script>
version three
None of the previously implemented methods solved the problem of ring references
JSON.parse(JSON.stringify(target)) reports an error TypeError: Converting circular structure to JSON, which means that the ring reference cannot be processed
The recursive method reports an error Maximum call stack size exceeded, which means that the recursion is endless and the stack overflows
// ring reference
const a = {}
a.key = a
So how to solve the ring reference? In fact, it is not difficult, you need to use ES6 data structure Map
- Every time you traverse to a reference data type, put it as a
key
and put it in theMap
, and the correspondingvalue
is the newly createdobject temp
-
Every time there is a reference data type in the traversal, go to the Map to find out if there is a corresponding
key
. If there is, it means that this object has been registered before, and now it is the second time, it must be a ring reference.Directly according to key gets value and returns value
function deepClone(target, map = new Map()) { if (typeof target !== "object") { return target; } const temp = Array.isArray(target) ? [] : {}; if (map. get(target)) { // If it exists, return directly return map. get(target); } // If it does not exist, set it for the first time map.set(target, temp); for (const key in target) { temp[key] = deepClone(target[key], map); } return temp; } const a = { name: "fryao", age: 23, hobbies: { sports: "basketball" }, color: ["red", "green"], }; a.key = a; // ring reference const b = deepClone(a); console.log(b); // { // name: "fryao", // age: 23, // hobbies: { sports: "basketball"}, // color:['red','green'] // key: [Circular] // } console.log(b === a); // false
final version
Previously, we only realized the copying of basic data types and the copying of arrays and objects in reference data types, but we still have to solve the problem of copying reference types
We first divide the above reference type data into two categories
- Traversable data types
-
non-traversable data types
// iterable type const mapTag = '[object Map]'; const setTag = '[object Set]'; const arrayTag = '[object Array]'; const objectTag = '[object Object]'; // non-traversable type const symbolTag = '[object Symbol]'; const regexpTag = '[object RegExp]'; const funcTag = '[object Function]'; // Store traversable types in an array const canForArr = ['[object Map]', '[object Set]', '[object Array]', '[object Object]'] // store non-traversable types in an array const noForArr = ['[object Symbol]', '[object RegExp]', '[object Function]'] // Function to judge type function checkType(target) { return Object.prototype.toString.call(target) } // Determine the temp of the reference type function checkTemp(target) { const c = target.constructor return new c() }
Traversable reference types: Mainly deal with the following four types
- map
- set
- object
- Array
non-traversable reference types: Mainly deal with the following types
- Symbol
- RegExp
- function
First write out the method of copying these three types:
// method to copy Function
function cloneFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func. prototype) {
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
if (param) {
const paramArr = param[0]. split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
// method to copy Symbol
function cloneSymbol(target) {
return Object(Symbol.prototype.valueOf.call(target));
}
// method to copy RegExp
function cloneReg(target) {
const reFlags = /\w*$/;
const result = new target.constructor(target.source, reFlags.exec(target));
result.lastIndex = target.lastIndex;
return result;
}
final integrated version
function deepClone(target, map = new Map()) {
// get type
const type = checkType(target);
// Basic data types return directly
if (!canForArr.concat(noForArr).includes(type)) return target;
// Judge Function, RegExp, Symbol
if (type === funcTag) return cloneFunction(target);
if (type === regexpTag) return cloneReg(target);
if (type === symbolTag) return cloneSymbol(target);
// Special handling for reference data types
const temp = checkTemp(target);
if (map.get(target)) {
// If it exists, return directly
return map.get(target);
}
// If it does not exist, set it for the first time
map.set(target, temp);
// Handle the Map type
if (type === mapTag) {
target.forEach((value, key) => {
temp.set(key, deepClone(value, map));
});
return temp;
}
// handle Set type
if (type === setTag) {
target.forEach((value) => {
temp.add(deepClone(value, map));
});
return temp;
}
// handle data and objects
for (const key in target) {
// recursion
temp[key] = deepClone(target[key], map);
}
return temp;
}
const a = {
name: "fryao",
age: 23,
hobbies: { sports: "basketball" },
color: ["red", "green"],
map: new Map([
["hey", 111],
["yep", 222],
]),
set: new Set([1, 2, 3]),
func: (name, age) => `${name} is ${age} years old this year`,
sym: Symbol(123),
reg: new RegExp(/haha/g),
};
a.key = a; // ring reference
const b = deepClone(a);
console.log(b);
// {
// name: 'fryao',
// age: 18,
// hobbies: { sports: 'basketball'},
// color: ["red", "green"],
// map: Map { 'hey' => 111, 'yep' => 222 },
// set: Set { 1, 2, 3 },
// func: [Function],
// sym: [Symbol: Symbol(123)],
// reg: /haha/g,
// key: [Circular]
// }
console.log(b === a); // false