preface

Before we start writing code we need to know what deep copy is and what the difference is between pre-copy and pre-copy.

In JS, data types are divided into basic data types and reference data types. For basic data types, its value is stored directly in stack memory, while for reference types, it only stores a reference in stack memory, while the real data is stored in heap memory

Deep copy and front copy

  • A shallow copy creates a new object with an exact copy of the original object’s property values. If the property is a primitive type, it copies the value of the primitive type, and if the property is a reference type, it copies the memory address, so if one object changes the address, it affects the other object.

  • A deep copy is a complete copy of an object from the heap, creating a new area of the heap to store the new object, and modifying the new object does not affect the original object.

The following is a graphic picture.

The difference between deep and shallow copies

  • Shallow copy: The basic data types of objects before and after the copy do not affect each other. However, the reference types of objects before and after the copy do affect each other because they share the same memory block.

  • Deep copy: creates a new area of heap memory to store the new object, and makes a recursive copy of the child objects in the object. The two objects before and after the copy do not affect each other.

So once we know what deep copy is we can write code, right
function deepClone(origin, target) {
  target = target || {};
  // Determine the data type used
  var toString = Object.prototype.toString;
  var isArr = "[object Array]";

  / / traverse the origin
  for (var k in origin) {
    // Make sure the attribute is not inherited from the stereotype
    if (origin.hasOwnProperty(k)) {
      // If the property is a complex data type and is not null
      if (typeof origin[k] === "object"&& origin[k] ! = =null) {
        // Use the custom toString method above to determine whether the property is array or object
        target[k] = toString(origin[k]) === isArr ? [] : {};
        / / recursion
        deepClone(origin[k], target[k]);
      } else{ target[k] = origin[k]; }}}return target;
}
Copy the code
Testing:

var p = {
  info: {
    name: "zhang san".age: 18.list: [1.2.3.4],}};var child = deepClone(p);
child.info.list = [4.3.2.1];
console.log(child); 
/* info: age: 18 list: (4) [4, 3, 2, 1] name: "zhang san" __proto__: Object __proto__: Object */
console.log(p);

/* info: age: 18 list: (4) [1, 2, 3, 4] name: "zhang san" __proto__: Object __proto__: Object */


Copy the code

As you can see, the copy was successful ^ ^

But here’s the problem:


var p = {
  info: {
    name: "zhang san".age: 18.list: [1.2.3.4].regex: /\w/,
    date: new Date(),}};Copy the code

We added Date and Regex data types, failed to copy properly:


    info:
    age: 18
    date: {}
    list: (4) [4.3.2.1]
    name: "zhang san"
    regex: {}
    __proto__: Object
    __proto__: Object

Copy the code

Therefore, these two attribute types will be treated separately:


    function deepClone(origin, target) {
      if (origin === undefined || typeoforigin ! = ="object") {
          return origin;
      }
      if (origin instanceof Date) {
        return new Date(origin);
      }
      if (origin instanceof RegExp) {
        return new RegExp(origin); }... }Copy the code
The tests are as follows:

var p = {
  info: {
    name: "zhang san".age: 18.list: [1.2.3.4].regex: /\w/,
    date: new Date(),}};var child = deepClone(p);
child.info.list = [4.3.2.1];
console.log(child);  
Info: age: 18 Date: Sun Jun 27 2021 15:36:39 GMT+0800 {} List: (4) [4, 3, 2, 1] Name: "Zhang SAN" regex: /\w/ __proto__: Object __proto__: Object */
console.log(p);

/* Info: age: 18 Date: Sun Jun 27 2021 15:36:39 GMT+0800 {} List: (4) [1, 2, 3, 4] Name: zhang SAN regex /\w/ __proto__: Object __proto__: Object */

Copy the code

Copy succeeded

But goose…


    var test1 = {};
    var test2 = {};
    test1.test2 = test2;
    test2.test1 = test1;
    console.log(deepClone(test2));

Copy the code

It is derived from log because of stack overflow caused by copy-dead loops

Then a new API WeakMap of ES6 is generated;

First, the similarities and differences of common object Map WeakMap are introduced:

{} : key => string The key value of an ordinary object is a normal string;

Map: key => any Any data type including {} / []

WeakMap: Key => Object The key value of a WeakMap must be an object

And:

WeakMap key names refer to objects that are weak references

What does that mean?

The code:

    // A basic set of code
    // In normal case:
    
    var btn1 = document.getElementsByClassName('btn1');
    var btn2 = document.getElementsByClassName('btn2');
    
    btn1.addEventListener('click',handleBtn1Click,false);
    btn2.addEventListener('click',handleBtn2Click,false);
        
    function handleBtn1Click() {... }function handleBtn2Click() {... }// If we remove two nodes but two event handlers are not destroyed
    btn1.remove();
    btn2.remove();
    
    // So we need to manually destroy
    handleBtn1Click = null;
    handleBtn2Click = null;

Copy the code

So at this time, we can use our WeakMap:

    var btn1 = document.getElementsByClassName('btn1');
    var btn2 = document.getElementsByClassName('btn2');
       
    const btnMap = new WeakMap(a);/ / define WeakMap
    
    / / weak references
    btnMap.set(btn1,handleBtn1Click);
    btnMap.set(btn2,handleBtn2Click);
    
    // This place is directly btnmap.key
    btn1.addEventListener('click',btnMap.btn1,false);
    btn2.addEventListener('click',btnMap.btn2,false);
        
    function handleBtn1Click() {... }function handleBtn2Click() {... }/* When we remove btn1 and btn2 from btnMap, the two key values of btn1 and btn2 will be destroyed automatically. The key value is destroyed, and the value no longer exists. Therefore, rather than saying that a WeakMap is a weak reference, it is better to say that a WeakMap key is a weak reference. * /
    btn1.remove();
    btn2.remove();
Copy the code

In this case, we need to modify the code:

                                / / use WeakMap
    function deepClone(origin, hash = new WeakMap(a)) {...if (origin instanceof RegExp) {
        return new RegExp(origin);
       }
       
       var hashMap = hash.get(origin);
       // Check whether there are repeated references
       if (hashMap) {
        return hashMap;
       }
       
       const target = new origin.constructor();
       
       // Every call is logged
       hash.set(origin, target);
       for (const key in origin) {
           ...
       }
       return target;
    }

Copy the code

The result is as follows:

Some of you may have found this line of code:

const target = new origin.constructor();

Let me explain:

    var obj = {};
    var newObj = obj.constructor();
    obj.a = 1;
    console.log(obj);
    console.log(newObj);

    var arr = [];
    arr.push(1);
    var newArr = arr.constructor();
    console.log(arr);
    console.log(newArr);

Copy the code

With these lines of code, we don’t care whether Origin is {} or [], just duplicate a {} or [] according to origin’s datatype.construcotr ().

The complete code is as follows:

  function deepClone(origin, hash = new WeakMap(a)) {
      if (origin === undefined || typeoforigin ! = ="object") {
        return origin;
      }
      if (origin instanceof Date) {
        return new Date(origin);
      }
      if (origin instanceof RegExp) {
        return new RegExp(origin);
      }
      
      var hashMap = hash.get(origin);
      
      if (hashMap) {
        return hashMap;
      }
      
      const target = new origin.constructor();
      hash.set(origin, target);
      for (const key in origin) {
          if(origin.hasOwnProperty(key)) { target[key] = deepClone(origin[key], hash); }}return target;
}

Copy the code

If you find any errors, please point them out in the comments section