If problem
1. How to copy a lot of nested objects
2. What kind of dark copy is qualified
Shallow copy principle and implementation
Create a new object of your own to accept the object values you want to copy or reference again. If the object property is a primitive data type, the value of the primitive type is copied to the new object. But if the property is a reference data type, the copy is the address in memory, and if one object changes the address in memory, the other object must be affected.
Method 1: Object.assign
Object. assign is an object method in ES6. This method can be used to merge JS objects. The first argument to this method is the target object to copy, followed by the source object (or multiple sources) to copy.
The syntax of object.assign is:
Object.assign(target, ... sources)Copy the code
Example code for object.assign is as follows:
let target = {};
let source = { a: { b: 1 } };
Object.assign(target, source);
console.log(target); // { a: { b: 1 } };
Copy the code
Object. Assign a shallow copy. The target object is the new copy object.
let target = {};
let source = { a: { b: 2 } };
Object.assign(target, source);
console.log(target); // { a: { b: 10 } };
source.a.b = 10;
console.log(source); // { a: { b: 10 } };
console.log(target); // { a: { b: 10 } };
Copy the code
As you can see from the code above, first copy the source to the target Object via object. assign, and then try to change the b attribute in the source Object from 2 to 10.
Object.assign temporarily achieves the desired copy effect, as you can see from the console, the b attribute in all three targets has changed to 10.
But there are a few caveats to using the object.assign method:
1. It does not copy inherited properties of objects;
2. It does not copy the object’s non-enumerable properties;
3. You can copy properties of type Symbol.
The object. assign loop iterates over the properties of the original Object and assigns them to the corresponding properties of the target Object by copying them.
let obj1 = { a:{ b:1 }, sym:Symbol(1)}; Object.defineproperty (obj1, 'innumerable',{value:' unenumerable ', Enumerable :false}); let obj2 = {}; Object.assign(obj2,obj1) obj1.a.b = 2; console.log('obj1',obj1); console.log('obj2',obj2);Copy the code
As we can see from the above sample code, we can also copy objects of type Symbol using object.assign. However, if we reach the object’s second attribute obj1.a.b, the value of the first attribute will also affect the value of the second attribute. This means that there is still a problem with accessing common heap memory, which means that this method cannot be copied further, but only completes the function of shallow copy.
Method 2: Extend the operator mode
JS extension operators can be used to construct objects while completing the function of shallow copy.
The syntax of the extension operator is:
let cloneObj = { ... obj };Copy the code
The code is shown below.
/ * a copy of the object * / let obj = {a: 1, b: {c: 1}} let obj2 = {... obj} obj.a = 2 console.log(obj) //{a:2,b:{c:1}} console.log(obj2); //{a:1,b:{c:1}} obj.b.c = 2 console.log(obj) //{a:2,b:{c:2}} console.log(obj2); // let arr = [1, 2, 3]; let newArr = [...arr]; // The same effect as arr.slice()Copy the code
The extension operator has the same drawback as object.assign, that is, it performs almost the same function as shallow copies, but it is more convenient to use the extension operator for shallow copies if the attributes are values of primitive types.
Method 3: Concat copies arrays
The concat method of arrays is a shallow copy, so when concating an array with a reference type, you need to be careful to modify the attributes of the elements in the original array, because this will affect the concatenated array.
However, concat can only be used for shallow copies of arrays, which can be very limited.
The code is shown below.
let arr = [1, 2, 3]; let newArr = arr.concat(); newArr[1] = 100; console.log(arr); // [ 1, 2, 3 ] console.log(newArr); // [1, 100, 3]Copy the code
Method 4: Slice copies arrays
The Slice method is also somewhat limited because it only works with array types.
The slice method returns a new array object that uses the first two arguments of the method to determine the start and end times of the original array and does not affect or alter the original array.
The syntax for slice is:
arr.slice(begin, end);
Copy the code
Slice uses the code shown below.
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr); //[ 1, 2, { val: 1000 } ]
Copy the code
As you can see from the code above, this is where shallow copy is limited — it copies only one layer of objects. If there is nesting of objects, shallow copies are useless. Therefore, deep copy is born to solve this problem, it can solve the problem of multi-layer object nesting, complete copy.
Implement a shallow copy manually
Understanding of shallow copy, if you let yourself implement a shallow copy, the general idea is divided into two points:
-
Make a basic copy of the base type;
-
Create a new store for reference types and copy a layer of object properties.
const shallowClone = (target) => {
if (typeof target === ‘object’ && target ! == null) {
const cloneTarget = Array.isArray(target) ? [] : {}; for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = target[prop]; } } return cloneTarget;Copy the code
} else {
return target; Copy the code
}
}
Using type judgment, a for loop for an object that references a type iterates through the attributes assigned to the target object, and you can basically manually implement a shallow copy of the code
Principles and implementation of deep copy
A shallow copy simply creates a new object and copies the values of the original object’s primitive type, while a reference data type copies only one layer of properties. Deep copy is different. For complex reference data types, a full memory address is created in the heap and the original object is copied over.
The two objects are independent and unaffected, completely separating them from memory. In general, the principle of deep copy can be summarized as follows:
A complete copy of an object from memory to the target object, and a new space in the heap memory to store the new object, and the modification of the new object does not change the original object, the two achieve true separation.
Now that we know the principle, how do we implement deep copy? Several methods are summarized
Method 1: Beggar edition (json.stringify)
Json.stringify () is the simplest deep-copy method currently in development. It serializes an object into a JSON string, converts the contents of the object into a string, and then generates a new object from the JSON string using json.parse (). The sample code is shown below.
Let STR = {a:1, b:[1,2,3]} let STR = json.stringify (obj1); Let obj2 = json.parse (STR); console.log(obj2); //{a:1,b:[1,2,3]} obj1. obj1.b.push(4); console.log(obj1); / / {2, a: b: [1, 2, 3, 4]} the console. The log (obj2); / / {a: 1, b: [1, 2, 3]}Copy the code
As you can see from the above code, we can initially implement a deep copy of an object with json.stringify. By changing the B property of obj1, we can actually see that obj2 is not affected either.
But there are a few things worth noting about using json.stringify to implement deep copy, and I’ve summarized them here:
-
If the value of the copied object is function, undefined, or symbol, the key/value pair will disappear in the string serialized by json. stringify.
-
Copying the Date reference type becomes a string;
-
Unable to copy non-enumerable properties;
-
Unable to copy object’s prototype chain
-
Copying a RegExp reference type becomes an empty object.
-
Object containing NaN, Infinity, and -infinity, the result of JSON serialization is null;
-
Looping applications that cannot copy objects, i.e. objects are looped (obj[key] = obj).
To address these problems, try the following code to see what would happen to such a complex object if you implemented deep copy with json.stringify.
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 the code
With the above code, you can see the result as shown below.
Implementing deep-copy objects using the json.stringify method is the easiest and fastest way to meet everyday development needs, although there are many features that have not been implemented so far. Json.stringify is not yet available for other types of data that correspond to more cumbersome attributes that are also intended to implement deep copies, so the following methods are needed.
Method 2: Basic version (handwritten recursive implementation)
The following is an example of a deepClone function wrapped by iterating through the attribute value of the passed parameter with for in, recursively calling the function again if the value is a reference type, or copying directly if it is an underlying data type, as shown below.
Let obj1 = {a:{b:1}} function deepClone(obj) {let cloneObj = {} for(let key in obj) {typeof obj[key] ==='object') {cloneObj[key] = deepClone(obj[key]) else {cloneObj[key] = obj[key]} return cloneObj } let obj2 = deepClone(obj1); obj1.a.b = 2; console.log(obj2); // {a:{b:1}}Copy the code
Although it is possible to implement a deep copy using recursion, as with json.stringify above, there are still some problems that are not completely resolved, such as:
-
This deep-copy function does not copy non-enumerable attributes and Symbol types;
-
This method only copies the values of ordinary reference types recursively. It does not copy the values of Array, Date, RegExp, Error, and Function correctly.
-
Object property inside the loop, i.e. circular reference is not resolved.
This basic version is also simpler to write and can handle most applications. But if you can only write a flawed deep-copy method like this during the interview process, you probably won’t get it.
So in order to “fix” these flaws, I’m going to take you through a modified version so that you can present a better deep-copy approach to your interview and win over the interviewer.
Method 3: Improved version (improved recursive implementation)
In view of the above problems to be solved, I will first tell you how to do respectively through four relevant theories.
-
For non-enumerable properties that iterate over objects and for Symbol types, we can use the reflect.ownkeys method;
-
If the parameter type is Date or RegExp, a new instance is generated and returned.
-
Using Object getOwnPropertyDescriptors method can obtain all attributes of the Object, and the corresponding feature, combined with the Object, by the way of the create method to create a new Object, and introduced into the original Object inherit the prototype chain;
-
WeakMap type is used as a Hash table. Because WeakMap is a weak reference type, it can effectively prevent memory leaks (you can pay attention to the key difference between Map and WeakMap, WeakMap is used here). It is very helpful to detect cyclic references. The reference returns the value stored by WeakMap directly.
As for WeakMap in point 4, there is not too much popular science explanation. If it is not clear, you can learn about it by yourself through relevant materials.
When you do not know the real role of WeakMap, it is recommended not to write such code in the interview, if you just memorize, you will dig a hole for yourself. Every line of code you write needs to be well thought out and clear so that you stand up to the interviewer.
Of course, if after considering the problem of circular reference, WeakMap can be used to solve it well and explain the purpose of doing so to the interviewer, then the code you have shown and the comprehensiveness of your thinking about the problem should be qualified in the eyes of the interviewer.
So for these problems, let’s take a look at what the deep-copy code of the improved recursive implementation would look like, as shown below.
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj ! == null) const deepClone = function (obj, Hash = new WeakMap()) {if (obj.constructor === Date) return new Date(obj) // The Date object returns a new Date object if (obj.constructor === If (hash.has(obj)) return hash.get(obj) let (weakMap) if (hash.has(obj)) return hash.get(obj) let AllDesc = Object. GetOwnPropertyDescriptors (obj) / / traverse incoming parameters all the key features of the let cloneObj = Object. The create (Object. GetPrototypeOf (obj), AllDesc) // Inherit the prototype chain hash.set(obj, cloneObj) for (let key of Reflect.ownKeys(obj)) { cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] ! == 'function') ? DeepClone (obj[key], hash) : obj[key]} return cloneObj} let obj = {num: 0, STR: ", Boolean: true, unf: Arr: [0, 1, 2], func: undefined constant, nul: null, obj: {id: 1}, arr: [0, 1, 2], func: Function () {console.log(' I am a function ')}, date: new date (0), reg: new RegExp('/ I am a regular /ig'), [Symbol('1')]: 1,}; Object.defineproperty (obj, 'innumerable', {enumerable: false, value: 'unenumerable'}); obj = Object.create(obj, Object. GetOwnPropertyDescriptors (obj) obj. Loop = obj / / set loop into the attribute of a circular reference let cloneObj = deepClone (obj) cloneObj.arr.push(4) console.log('obj', obj) console.log('cloneObj', cloneObj)Copy the code
The arR array in cloneObj has been modified without affecting the change of obj. Arr.
As you can see from this screenshot, the improved deepClone function has improved the problems of the basic version, and also verified the four theories I mentioned above.
So that’s it for deep copy.
conclusion
How to implement a light copy. In daily development, many people don’t know the details of how to implement deep copy because there are libraries available to implement deep copy. However, if you study carefully, you will find that this part of the content is very helpful for you to understand the underlying principle of JS. If you need to implement a front-end related tool or library yourself in the future, the depth of your understanding of JS will determine how well you can do it.
In fact, in the end we can see that their complete implementation of a deep copy, or a lot of knowledge and programming ability, summed up broadly divided into these points, please see the figure below.
It can be seen that there are a lot of abilities that can be examined through this question, so do not use the lowest standard to ask yourself, should use a similar method to analyze each question in-depth investigation is what on earth, so as to better improve their basic skills.