preface
Deep cloning (deep copy) has always been the beginning, intermediate front-end interview is often asked the topic, the implementation of online introduction has its own advantages, generally can be summarized as three ways:
JSON.stringify+JSON.parse
That’s easy to understand;- Fully determine the type, according to the type of different processing
- 2, simplify the type judgment process
The first two are more common and basic, so today we’re going to focus on the third.
Read the full article and you will learn:
- More concise deep clone way
Object.getOwnPropertyDescriptors()
api- General method of type determination
Problem analysis
Deep copy is, of course, relative to shallow copy. We all know that a reference datatype variable stores a reference to the data, that is, a pointer to the memory space, so if we assign the same way as a simple datatype, we can only copy a reference to the pointer, and we are not really cloning the data.
It’s easy to understand from this example:
const obj1 = {
name: 'superman'
}
const obj2 = obj1;
obj1.name = 'Front end cutter';
console.log(obj2.name); // cut the front end
Copy the code
Therefore, deep cloning is to solve the problem that reference data types cannot be copied through assignment.
Reference data type
Let’s list the types of reference data:
- Before ES6: Object, Array, Date, RegExp, Error,
- After ES6: Map, Set, WeakMap, WeakSet,
Therefore, if we want to clone deeply, we need to traverse the data and adopt the corresponding clone mode according to the type. Of course, recursion is a good choice because data can be nested in multiple layers.
Rough version
function deepClone(obj) {
let res = {};
// General method of type determination
function getType(obj) {
return Object.prototype.toString.call(obj).replaceAll(new RegExp(/\[|\]|object /g), "");
}
const type = getType(obj);
const reference = ["Set"."WeakSet"."Map"."WeakMap"."RegExp"."Date"."Error"];
if (type === "Object") {
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) { res[key] = deepClone(obj[key]); }}}else if (type === "Array") {
console.log('array obj', obj);
obj.forEach((e, i) = > {
res[i] = deepClone(e);
});
}
else if (type === "Date") {
res = new Date(obj);
} else if (type === "RegExp") {
res = new RegExp(obj);
} else if (type === "Map") {
res = new Map(obj);
} else if (type === "Set") {
res = new Set(obj);
} else if (type === "WeakMap") {
res = new WeakMap(obj);
} else if (type === "WeakSet") {
res = new WeakSet(obj);
}else if (type === "Error") {
res = new Error(obj);
}
else {
res = obj;
}
return res;
}
Copy the code
This is actually the second way we mentioned earlier, which is silly, right? You can see at a glance that there is a lot of redundant code that can be merged.
Let’s start with the most basic optimization:
Merge redundant code
Merge code that is immediately redundant.
function deepClone(obj) {
let res = null;
// General method of type determination
function getType(obj) {
return Object.prototype.toString.call(obj).replaceAll(new RegExp(/\[|\]|object /g), "");
}
const type = getType(obj);
const reference = ["Set"."WeakSet"."Map"."WeakMap"."RegExp"."Date"."Error"];
if (type === "Object") {
res = {};
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) { res[key] = deepClone(obj[key]); }}}else if (type === "Array") {
console.log('array obj', obj);
res = [];
obj.forEach((e, i) = > {
res[i] = deepClone(e);
});
}
// Optimize this part of the redundancy judgment
// else if (type === "Date") {
// res = new Date(obj);
// } else if (type === "RegExp") {
// res = new RegExp(obj);
// } else if (type === "Map") {
// res = new Map(obj);
// } else if (type === "Set") {
// res = new Set(obj);
// } else if (type === "WeakMap") {
// res = new WeakMap(obj);
// } else if (type === "WeakSet") {
// res = new WeakSet(obj);
// }else if (type === "Error") {
// res = new Error(obj);
/ /}
else if (reference.includes(type)) {
res = new obj.constructor(obj);
} else {
res = obj;
}
return res;
}
Copy the code
To verify the correctness of the code, let’s use the following data:
const map = new Map(a); map.set("key"."value");
map.set("ConardLi"."coder");
const set = new Set(a); set.add("ConardLi");
set.add("coder");
const target = {
field1: 1.field2: undefined.field3: {
child: "child",},field4: [2.4.8].empty: null,
map,
set,
bool: new Boolean(true),
num: new Number(2),
str: new String(2),
symbol: Object(Symbol(1)),
date: new Date(),
reg: /\d+/,
error: new Error(),
func1: () = > {
let t = 0;
console.log("coder", t++);
},
func2: function (a, b) {
returna + b; }};// Test the code
const test1 = deepClone(target);
target.field4.push(9);
console.log('test1: ', test1);
Copy the code
Execution Result:
Is there room for further refinement?
The answer, of course, is yes.
// The method to determine the type is moved outside to avoid multiple executions in the recursive process
const judgeType = origin= > {
return Object.prototype.toString.call(origin).replaceAll(new RegExp(/\[|\]|object /g), "");
};
const reference = ["Set"."WeakSet"."Map"."WeakMap"."RegExp"."Date"."Error"];
function deepClone(obj) {
// Define a new object and return it
// Create objects from obj's prototype
const cloneObj = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
// Iterate over the object and clone the properties
for (let key of Reflect.ownKeys(obj)) {
const val = obj[key];
const type = judgeType(val);
if (reference.includes(type)) {
newObj[key] = new val.constructor(val);
} else if (typeof val === "object"&& val ! = =null) {
// Recursive clone
newObj[key] = deepClone(val);
} else {
// Basic data types and functionsnewObj[key] = val; }}return newObj;
}
Copy the code
The result is as follows:
Object.getOwnPropertyDescriptors()
The getDescriptor () method is used to get the descriptors of all of an object’s own properties.- Returns a descriptor for all of the specified object’s own properties, or an empty object if there are none.
See MDN for detailed explanation and content
This has the advantage of being able to define in advance the type of data that will eventually be returned.
This implementation refers to the implementation of a big guy on the Internet, I think the cost of understanding a bit high, and the array type of processing is not particularly elegant, return class array.
I modified the code based on my above code, and the modified code is as follows:
function deepClone(obj) {
let res = null;
const reference = [Date.RegExp.Set.WeakSet.Map.WeakMap.Error];
if(reference.includes(obj? .constructor)) { res =new obj.constructor(obj);
} else if (Array.isArray(obj)) {
res = [];
obj.forEach((e, i) = > {
res[i] = deepClone(e);
});
} else if (typeof obj === "Object" && typeofobj ! = =null) {
res = {};
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) { res[key] = deepClone(obj[key]); }}}else {
res = obj;
}
return res;
}
Copy the code
Although there is no advantage in the amount of code, I think the overall understanding cost and your clarity will be better. So what do you think?
Finally, there are circular references to avoid the problem of wireless loops.
We use hash to store loaded objects and return them if they already exist.
function deepClone(obj, hash = new WeakMap(a)) {
if (hash.has(obj)) {
return obj;
}
let res = null;
const reference = [Date.RegExp.Set.WeakSet.Map.WeakMap.Error];
if(reference.includes(obj? .constructor)) { res =new obj.constructor(obj);
} else if (Array.isArray(obj)) {
res = [];
obj.forEach((e, i) = > {
res[i] = deepClone(e);
});
} else if (typeof obj === "Object" && typeofobj ! = =null) {
res = {};
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) { res[key] = deepClone(obj[key]); }}}else {
res = obj;
}
hash.set(obj, res);
return res;
}
Copy the code
conclusion
There may be many different implementations of deep copy, but the key is to understand the principles and remember the one that is easiest to understand and implement, so that you can be calm when faced with similar problems. Which of the above implementations do you think is better? Welcome to the comments section
More difficult to read, remember to click a “like” to support oh ~ this will be my writing power source ~