preface

For many friends, in the actual development business, we will encounter a variety of data structures and transformation and copy between different data. If we do not notice the error, we may not find the reason quickly, which will delay our development cycle.

I was similar to this without a specific complete knowledge system, encountered problems to look up information, very scattered, finally this determined to make sure that the depth of the copy understand, I hope this article is helpful to you.

What is shallow copy and what is deep copy

Js heap and stack

As we all know, in JS, the space of data storage is divided into “stack” and “heap”, that is, basic data types are stored in the “stack”, and reference types are stored in the “heap”.

The stack will automatically allocate memory space, will automatically release. The memory allocated dynamically by the heap is of an arbitrary size and is not automatically released.

For heap, copying a value of a reference type from one variable to another is actually a pointer, so both variables end up pointing to the same object. That is, addresses in the stack are copied, not objects in the heap.

Copying a value of a primitive type from one variable to another creates a copy of that value.

Shallow copy and deep copy concepts

Shallow copy: Object A copies only a pointer to object B, not the object itself. Old and new objects they still share the same memory.

Deep copy: if object N is a deep copy object M, then they no longer reference the same memory address stored in the stack, that is, they do not interact with each other.

Shallow copy implementation

1. Assign

Js is the most basic and the simplest shallow copy

Let a = {age: 18, sex: "male"}; let b = a; console.log(b); //{age: 18, sex: "male"} a.age = 20; console.log("a:", a, "b:", b); } a: {age: 20, sex: "male "} b: {age: 20, sex:" male "}Copy the code

In this case, changing the value of B directly affects the value of a, and vice versa

2.Object.assign(target, … sources)

The object.assign () method is used to assign the values of all enumerable properties from one or more source objects to target objects. It will return the target object.

Parameter: target Target object Sources Source object

Return value: Target object target

const target = {}; Const source = {a: 4, b: 5, c:{d:6}}; const returnedTarget = Object.assign(target, source); console.log(target); //{a: 4, b: 5, c:{d:6}} console.log(returnedTarget); //{a: 4, b: 5, c:{d:6}} const target = {}; const source = { a: 4, b: 5, c: { d: 6 } }; const returnedTarget = Object.assign(target, source); console.log(returnedTarget); //{a: 4, b: 5, c:{d:20}} source.c.d = 20; console.log(source); //{a: 4, b: 5, c:{d:20}} console.log(target); //{a: 4, b: 5, c:{d:20}}Copy the code

But there are a few caveats to using the object.assign method:

  • Can handle a layer of copies
  • It does not copy the object’s inherited properties;
  • It does not copy the object’s non-enumerable properties;
  • You can copy a Symbol attribute;
  • Object.assign will not throw an error if the source Object is null or undefined.

3. Extend the operator mode

JS extension operator, in the construction of objects at the same time to complete the function of shallow copy

/ * 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

4. Concat copies arrays

The concat() method is used to merge two or more arrays. This method does not change an existing array, but returns a new array.

const array1 = ['a', 'b', 'c']; const array2 = ['d', 'e', 'f']; const array3 = array1.concat(array2); console.log(array3); const array1 = ["a", "b", "c", { g: 10 }]; const array2 = ["d", "e", "f"]; const array3 = array1.concat(array2); console.log(array3); // ["a", "b", "c", { g: 20 }] array1[3].g = 20; console.log(array1); //["a", "b", "c", { g: 20 }] console.log(array2); //["d", "e", "f"] console.log(array3); //["a", "b", "c", { g: 20 }]Copy the code

Concat can only be used for shallow copies of arrays, which is somewhat limited and has the same pitfalls as Object. assign

5. 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.

var arr1 = ["1","2","3"];
var arr2 = arr1.slice(0);
arr2[1] = "9";
console.log("arr1 :" + arr1 );console.log("arr2:" + arr2 );
Copy the code

Write a shallow copy by hand

  function shallowClone(target) {    
    if (typeof target === "object" && target !== null) {     
      const cloneTarget = Array.isArray(target) ? [] : {};     
        for (const key in target) {         
         if (target.hasOwnProperty(key)) {          
           cloneTarget[key] = target[key];        
       }      
   }     
    return cloneTarget;    
} else {     
   return target;   
  }  
}
Copy the code

Detection:

let m = { a: 10, b: 20, c: { d: 30 } };  
let n = shallowClone(m);  
console.log(n);
Copy the code

Deep copy implementation

1. Use parse and stringfy of JSON objects

Json.stringfy () is the simplest deep-copy method in development. It serializes an object into a JSON string, converts the contents of the object into a string, and generates a new object from the JSON string using json.parse ()

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

Disadvantages 1:

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.

Object containing NaN, Infinity, and -infinity will result in NULL JSON serialization

const test = { name: "a", undefined, NaN, date: function hehe() { console.log("fff"); }}; const copyed = JSON.parse(JSON.stringify(test)); test.name = "test"; console.log("test:", test); console.log("copyed:", copyed);Copy the code

Defect 2:

If the RegExp or Error object is present, the serialized result will be empty.

  const test = {
       name: "a",    
       Error,    
       date: new RegExp("\\w+"), 
 };  
  const copyed = JSON.parse(JSON.stringify(test));  
  test.name = "test";  console.log("test:", test); 
  console.log("copyed:", copyed);
Copy the code

Disadvantages:

Parse json. stringify if the object contains a Date reference type, then json. stringify will be followed by json. parse and the time will be a string, not a time object.

  var test = {
     date: [new Date(1536627600000), new Date(1540047600000)],  
}; 
 let copyed = JSON.parse(JSON.stringify(test)); 
 console.log("test:", test);  
 console.log("copyed:", copyed);
Copy the code

2. Handwritten recursion

function deepClone(obj) { let cloneObj = {}; For (let key in obj) {// if (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

Disadvantages:

  1. 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.

  2. Object property inside the loop, i.e. circular reference is not resolved.

2. Improved version: handwritten recursive implementation

/ / checktype function to check the type of Object function checktype (obj) {return Object. The prototype. ToString. Call (obj). Slice (8. -1).toLocaleLowerCase() } function deepCopy(target, hash = new WeakMap()) { if (target.constructor === Date) return new Date(target); If (target.constructor === RegExp) return new RegExp(target); // The regex object returns a new regex object directly. // Hash acts as a checker to prevent ring references from popping the stack in the deep copy of the object. let type = checktype(target) let result = null if (type == 'object') { result = {} } else if (type == 'array') { result = []} else {return target} if (hash.has(target)) {// Check if the same object has been copied before, Return hash. Get (target)} // The backup is stored in the hash. Result is currently an empty object or array. Set (target, result) is the stack of the object. for (let i in target) { if (checktype(target[i]) == "object" || checktype(target[i]) == "array") { result[i] = deepCopy(target[i], hash); } else {result[I] = target[I];} else {result[I] = target[I]; }} return result}Copy the code