preface
Why do JS object assignments sometimes need to be deep-copied?
Basic data typesand
Reference data type. Basic data types include Undefined, Null, Number, String, Boolean, Symbol; The reference data type is Object, and the Array, Set, and Map data also belong to Object.
// Base data type assignment
var a = 'aaa';
var b = a;
console.log(a); // 'aaa'
console.log(b); // 'aaa'
b = 'bbb';
console.log(a); // 'aaa'
console.log(b); // 'bbb'
// Reference data type assignment
var a = { name: 'Joe'};
var b = a;
console.log(a); // {name: ""}
console.log(b); // {name: ""}
b.name = 'bill';
b.age = 18;
console.log(a); // {name: "li ", age: 18}
console.log(b); // {name: "li ", age: 18}Copy the code
As you can see from the above code and figure, the assignment of the base data type is actually a copy, and there is no relationship between the values identified by the two variables. And created a reference data after the type of object, the object is stored in memory, as is the object the variable a points to address in memory, then the variable assignment to a variable b, is the a and b points to the same object, so either modify variables a or b, they will output the same content.
Deep copy method
1. Json.parse (json.stringify ()) serialization and antisequence
// Test data vartest = { name: "test"};
var data = { a: "123",
b: 123,
c: true,
d: [43, 2],
e: undefined,
f: null,
g: function() { console.log("g"); },
h: new Set([3, 2, null]),
i: Symbol("fsd"),
j: test,
k: new Map([ ["name"."Zhang"], ["title"."Author"]])}; JSON.stringify(data)Copy the code
The execution result
Can see the data in the object’s properties basically contains all data types, but through after the JSON string, the return value is missing, the reason is that JSON when performing string of this process, will be a JSON format, access to safe JSON values, so if a secure JSON value, will be discarded. The values of undefined, function and symbol are unsafe (including the property of the object which is cyclically assigned to the object), so they are filtered out after formatting, and the data format objects such as set and map are not properly processed, but are treated as empty objects.
Let’s take another extreme example
Var data = {name:'foo',
child: null,
}
data.child = data
Copy the code
Cyclic references to the properties of this object form a closed loop, perform the serialization, and see the result
As you can see, the JSON serialization of an object with a closed loop popped an error
So when using JSON serialization, be careful not to include any of the above data types, but there are a few advantages to this method, so let’s take a look at the examples.
// Test data vartest = { name: "test"};
var data = { a: "123",
b: 123,
c: true,
d: [43, 2],
e: test,
f: {
name: 'Joe',
age: 18,
likes: {
ball: ['football'.'basketball']}}}; JSON.stringify(data)Copy the code
It has the advantage of handling nested objects or property values that are references to another object without losing data.
The specific methods
function deepCopy(obj){
if(typeof obj === 'function'){
throw new TypeError('Please pass in the correct data type format')
}
try {
let data = JSON.stringify(obj)
let newData = JSON.parse(data)
return newData
} catch(e) {
console.log(e)
}
}Copy the code
2. Object.assign(target, source1, source2)
var data = {
a: "123",
b: 123,
c: true,
d: [43, 2],
e: undefined,
f: null,
g: function() { console.log("g"); },
h: new Set([3, 2, null]),
i: Symbol("fsd"),
k: new Map([ ["name"."Zhang"], ["title"."Author"]])}; var newData = Object.assign({},data) console.log(newData)Copy the code
You can see that the API copies all the data type attribute values from the source object into a new object. Is this the perfect deep copy we are looking for? The answer is, is it only a partial deep copy, or is it a shallow copy, why is that? Let’s move on.
var test = { name: 'Joe' }
var data = {
a: 123,
b: test
}
var newData = Object.assign({},data)
console.log(newData)
// { a: 123, b: { name: 'Joe' }}
test.age = 18
console.log(newData)
// { a: 123, b: { name: 'Joe', age: 18 }}Copy the code
3. Iterative recursive methods
Without saying more, first attach their own code
function deepCopy(data) {
if(typeof data ! = ='object' || data === null){
throw new TypeError('Passed argument is not an object')}letnewData = {}; const dataKeys = Object.keys(data); dataKeys.forEach(value => { const currentDataValue = data[value]; // Copy the values and functions directly assigned to the basic data typesif(typeof currentDataValue ! = ="object" || currentDataValue === null) {
newData[value] = currentDataValue;
} else if(array.isarray (currentDataValue)) {// Implement a deep copy of Array newData[value] = [...currentDataValue]; }else ifCurrentDataValue Instanceof Set {// implementsetNewData [value] = new Set([...currentDataValue]); }else if(currentDataValue instanceof Map) {newData[value] = new Map([...currentDataValue]); }elseNewData [value] = deepCopy(currentDataValue); }});return newData;
}Copy the code
Then write a test data to actually test it
Var data = {age: 18, name:"liuruchao",
education: ["Primary school"."Junior"."High school"."University", undefined, null],
likesFood: new Set(["fish"."banana"]),
friends: [
{ name: "summer", sex: "woman"},
{ name: "daWen", sex: "woman"},
{ name: "yang", sex: "man" } ],
work: {
time: "2019",
project: { name: "test",obtain: ["css"."html"."js"]}
},
play: function() { console.log("Skateboarding"); }}Copy the code
The execution result
Basically can satisfy the common data structure of the value of the deep copy, but because of the js object more data structure, so not all covered, such as new Number(), the basic data type of the wrapper object, is not processed. Therefore, when using it, you can first make a prediction of the object to be deep-copied to determine which method to use.
Again, try this extreme example of a closed loop object in the first method
Var data = {name:'foo',
child: null,
}
data.child = data
deepCopy(data)
Copy the code
Oh, direct stack overflow, stack burst!! The recursion method should be used with great care, because it can be pushed too much if you are not careful, so you can only optimize this function.
4. Iterative recursive method (solving closed-loop problems)
function deepCopy(data, hash = new WeakMap()) {
if(typeof data ! = ='object' || data === null){
throw new TypeError('Passed argument is not an object'} // Determine if the passed reference to the object to be copied existshashIn theif(hash.has(data)) {
return hash.get(data)
}
letnewData = {}; const dataKeys = Object.keys(data); dataKeys.forEach(value => { const currentDataValue = data[value]; // Copy the values and functions directly assigned to the basic data typesif(typeof currentDataValue ! = ="object" || currentDataValue === null) {
newData[value] = currentDataValue;
} else if(array.isarray (currentDataValue)) {// Implement a deep copy of Array newData[value] = [...currentDataValue]; }else ifCurrentDataValue Instanceof Set {// implementsetNewData [value] = new Set([...currentDataValue]); }else if(currentDataValue instanceof Map) {newData[value] = new Map([...currentDataValue]); }else{// Store a reference to the object to be copied inhashNewData [value] = deepCopy(currentDataValue,hash); }});return newData;
}Copy the code
There is more container WeakMap for storing objects than the previous version 1.0. The idea is that when deepCopy is called for the first time, the parameter will create a WeakMap object. One of the characteristics of this data structure is that the keys in the stored key-value pair must be object types.
- When called for the first time, weakMap is empty, and the above if(hash.has()) statement will not be used. If the object to be copied has properties that are also objects, the object to be copied will be stored in weakMap. At this time, the key value and key name are references to the object to be copied
- The function is then called recursively
- Enter the function again, pass in the object attribute reference of the last object to be copied and store the weakMap of the last object to be copied, because if it is a closed loop generated by circular reference, then the two references point to the same object, so it will enter if(hash.has()) statement, and then return, exit the function, So you don’t keep recursively pushing the stack, so you don’t overflow the stack.
conclusion
Regardless of their advantages and disadvantages, the common point is that only enumerable properties of objects can be copied, but not properties that are not enumerable or prototypical, which is sufficient for basic use. Finally, if there are mistakes or omissions in the article or you have better suggestions, welcome to communicate with me
If this article has so a little help to you, please help to point a praise oh, is also a pair of a kind of encouragement 😀!