About copying , It's also a classic interview question in the front-end interview , In our daily development, we often encounter scenes that need to use deep copy or shallow copy . Next, let's go through this article , To master thoroughly JavaScript Deep and light copies of .
data type
Before we start talking about deep and shallow copy , We need to know first JavaScript Data type of , Mainly as shown in the figure below 8 Kind of :
Object Is a reference type , other 7 It is the basic type .
JavaScript All the data types of will be put in different memory after initialization , Therefore, the above data types can be roughly divided into two categories for storage :
- The underlying type is stored in stack memory , When quoted or copied , Will create an exactly equal variable
- Reference types are stored in heap memory , What's stored is the address , Multiple references point to the same address , There will be a “ share ” The concept of .
Let's take a look at the shallow copy first .
Welcome to the official account : Front end geek Technology (FrontGeek), We learn together and make progress together .
The principle and implementation of shallow copy
Definition of light copy :
Create an object to accept the value of the object to be copied or referenced again . If the object property is the basic data type , What is copied is the value of the basic type to the new object ; If the property is a reference data type , The copied address is the address in memory , If one of the objects changes the address in memory , It could affect another object .
Let's see JavaScript What are the ways to achieve shallow copy in the .
Object.assign
object.assign yes ES6 in object One way , This method can be used for JS Object merging and so on , One of the purposes is to make shallow copies . The first parameter of this method is the target object of the copy , The next parameter is the source object of the copy ( It can also be multiple sources ).
object.assign The grammar of is :Object.assign(target, ...sources)
object.assign The sample code of is as follows :
let target = {}
let source = {
a: {
b: 1
}
}
Object.assign(target, source)
source.a.b = 10;
console.log(source); // { a: { b: 10 } };
console.log(target); // { a: { b: 10 } };
Copy code
From the above code we can see , First, through Object.assign take source copy to target In the object , take source Object b Attribute from 1 It is amended as follows 10. As can be seen from the execution results target and source Of the two objects b All attributes change to 10 了 , prove Object.assign For the time being, the copy effect we wanted .
Use Object.assign Method to achieve shallow copy, there are several points to note :
- Object.assign Method does not copy the inherited properties of the object
- Object.assign Method does not copy the object's non enumerable properties
- You can copy Symbol Properties of type
let obj1 = { a: { b: 1 }, sym: Symbol(1) };
Object.defineProperty(obj1, "innumerable", {
value: " Cannot enumerate properties ",
enumerable: false,
});
let obj2 = {};
Object.assign(obj2, obj1);
obj1.a.b = 10;
console.log("obj1", obj1); // obj1 { a: { b: 10 }, sym: Symbol(1) }
console.log("obj2", obj2); // obj1 { a: { b: 10 }, sym: Symbol(1) }
Copy code
As you can see from the sample code above , utilize object.assign It can also be copied Symbol Object of type , But if you get to the second layer of the object's properties obj1.a.b When it's here , The change of the value of the former will also affect the value of the second layer attribute of the latter , It shows that there is still the problem of accessing common heap memory , In other words, this method can not be further copied , And just finished the shallow copy function .
Extension operator methods
We can also use it JS The extension operator of , In the construction of objects at the same time to complete the shallow copy function .
let obj = {a: 1, b: {c: 10}}
let obj2 = {...obj}
obj2.a = 10
console.log(obj);
console.log(obj2);
let arr = [1, 3, 4, 5, [10, 20]]
let arr2 = [...arr]
console.log(arr2)
Copy code
Extension operator and object.assign With the same flaws , That is to say, the function of shallow copy is almost the same , But if the properties are all values of the base type , It's more convenient to use extension operators for shallow copy .
concat Copy an array
Array of concat The method is also shallow copy , So when you join an array with a reference type , You need to pay attention to modifying the attributes of the elements in the original array , Because it will affect the array connected after copying .
let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1]= 10;
console.log(arr);
console.log(newArr);
Copy code
slice Copy an array
slice The method also has limitations , Because it's only for array types .
slice Method returns a new array object , This object determines the start and end time of the original array interception by the first two parameters of the method , Will not affect and change the original array .
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr); //[ 1, 2, { val: 1000 } ]
Copy code
As you can see from the code above , This is the limit of shallow copy —— It can only copy one layer of objects . If there is nesting of objects , So there's nothing a shallow copy can do . So deep copy was created to solve this problem , It can solve the problem of multi-layer object nesting , Complete copy . Later in this lecture, I will introduce the content related to deep copy .
Make a shallow copy by hand
In addition to the methods mentioned above , We can also write a shallow copy method , Ideas as follows :
- Make a basic copy of the basic data type ;
- Open up a new store for reference types , And copy a layer of object properties .
const shallowCopy = (target) => {
if (typeof target === "object" && target !== null) {
const copyTarget = Array.isArray(target) ? [] : {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
copyTarget[key] = target[key];
}
}
return copyTarget;
} else {
return target;
}
};
Copy code
The principle and implementation of deep copy
Shallow copy just creates a new object , Copy the value of the basic type of the original object , The reference data type only copies one layer of properties , No matter how deep it is, it still can't be copied . Deep copies are different , For complex reference data types , It completely opens up a memory address in heap memory , And copy the original object completely .
These two objects are independent of each other 、 Unaffected , Complete separation of memory . in general , The principle of deep copy can be summarized as follows :
A complete copy of an object from memory and a copy to the target object , And from the heap memory to open up a new space to store new objects , And the modification of the new object does not completely change the source object , There is a real separation between the two .
After knowing the principle of deep copy , Let's see what deep copy methods are available :
The beggar version :JSON.stringfy
JSON.stringfy() Method is the most common and simplest deep copy method in development , It's basically serializing an object to JSON String , And convert the content of the object to a string , Last but not least JSON.parse() Methods will JSON String to generate a new object .
let obj = {a: 1, b: [1, 2, 3]};
let str = JSON.stringify(obj);
let newObj = JSON.parse(str);
obj.a = 2;
obj.b.push(4)
console.log(obj)
console.log(newObj)
Copy code
As you can see from the code above , adopt JSON.stringfy Can initially achieve a deep copy of the object , By changing obj Of a attribute , In fact, we can see that newObj This object is not affected either .
Let's take a look at the following uses JSON.stringify Method for deep copy :
function Obj() {
this.func = function () { alert(1) };
this.obj = {a:1};
this.arr = [1,2,3];
this.und = undefined;
this.reg = /123/;
this.date = new Date(0);
this.NaN = NaN;
this.infinity = Infinity;
this.sym = Symbol(1);
}
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{
enumerable:false,
value:'innumerable'
});
console.log('obj1',obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log('obj2',obj2);
Copy code
From the execution results above ,JSON.stringify The way to achieve deep copy is not perfect , Let's take a look JSON.stringify The problems in implementing deep copy :
- If the copied object has a function 、undefined、symbol There are several types , after JSON.stringify The key value pair disappears in the serialized string ;
- Copy Date The reference type becomes a string
- Can't copy properties that can't be enumerated
- Can't copy prototype chain of object
- Copy RegExp Reference type becomes empty
- Object contains NaN、Infinity、-Infinity,JSON The result of serialization would be null
- Can't copy circular application of object , That is to say, the object forms a ring , for example
obj[key]=obj
use JSON.stringify Method to implement deep copy object , Although so far there are many functions that can't be realized , But this method is enough to meet the daily development needs , And it's the easiest and quickest .
If the deep copy method needs to support Function、Symbol And so on , We can only implement a deep copy method by ourselves .
Handwriting for deep copy ( Basic Edition )
Next, we implement a deep copy method ourselves , Ideas as follows :
adopt for in Traverse the property value of the incoming parameter , If the value refers to the type, the method is called recursively again , If so, the underlying type is copied directly .
function deepCopy(target) {
let copyTarget = Array.isArray(target) ? [] : {};
for (let key in target) {
if (typeof target[key] === "object") {
copyTarget[key] = deepCopy(target[key]);
} else {
copyTarget[key] = target[key];
}
}
return copyTarget;
}
let obj1 = {
a: {
b: 1,
},
c: 10,
};
let obj2 = deepCopy(obj1);
obj1.a.b = 100;
obj1.c = 22
console.log(obj2);
Copy code
Above, we implemented deep copy recursively , But there are still some problems to be solved :
- Cannot copy non enumerable properties and Symbol type ;
- Only the values of common reference types are recursively copied , But for the Array、Date、RegExp、Error、Function These reference types don't copy properly .
- The properties of the object are looped , That is, circular reference is not solved .
Next, let's improve the deep copy method :
Improved deep copy method
In view of the above problems , Let's see what the solution is :
- For enumerable properties and Symbol type , have access to
Reflect.ownKeys
Method ; - Parameter is Date、RegExp Type , Directly generate a new instance and return ;
- utilize Object Of getOwnPropertyDescriptors Method to get all the properties of an object , And the corresponding characteristics , By the way Object.create() Method creates a new object , And inherits the chain of prototypes passed in to the original object ;
- utilize WeakMap Type as Hash surface , because WeakMap Weak reference type , Can effectively prevent memory leakage , It helps to detect circular references , If there is a cycle , Then the reference returns WeakMap Stored values .
According to the above , Let's rewrite the deep copy method :
function deepCopy(obj, hash = new WeakMap()) {
// The date object directly returns a new date object
if (obj.constructor === Date) return new Date(obj);
// If it is a regular object, return a new regular object directly
if (obj.constructor === RegExp) return new RegExp(obj);
// If a loop reference is made, use WeakMap To solve
if (hash.has(obj)) return hash.get(obj);
// Traverses the properties of all keys in the incoming parameter
let allDesc = Object.getOwnPropertyDescriptor(obj);
// Inherit the prototype chain
let copyObj = Object.create(Object.getPrototypeOf(obj), allDesc);
hash.set(obj, copyObj)
for (let key of Reflect.ownKeys(obj)) {
copyObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepCopy(obj[key], hash) : obj[key]
}
return copyObj
}
function isComplexDataType(obj) {
return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
// Here's the validation code
let obj = {
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: { name: ' I'm an object ', id: 1 },
arr: [0, 1, 2],
func: function () { console.log(' I'm a function ') },
date: new Date(0),
reg: new RegExp('/ I'm a regular /ig'),
[Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {
enumerable: false, value: ' Cannot enumerate properties ' }
);
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj // Set up loop Properties referenced in a loop
let cloneObj = deepCopy(obj)
cloneObj.arr.push(4)
console.log('obj', obj)
console.log('cloneObj', cloneObj)
Copy code
Performance issues
Although using deep copy can completely clone a new object , No side effects , But because the deep copy method uses recursion , Performance is not as good as shallow copy , In the actual development process , We should choose according to the actual situation .
If you think this is going to help you :
1、 give the thumbs-up Support me , So that more people can see the content
2、 Official account : Front end geek Technology (FrontGeek), We learn together and make progress together .