Implementation and advanced writing of deep copy

    version one

    function deepClone(target) {
        return JSON.parse(JSON.stringify(target));
      }
    
      const a = { name: "fryao", age: 18 };
      const b = deepClone(a);
    
      console.log(b); // { name: 'fryao', age: 18}
      console.log(b === a); // false
    

    While this is fine to use most of the time, there are a number of downsides to this approach:

    1. If there is a field value in the object that is undefined, the field will disappear directly after conversion
    2. If the object has a field value as a RegExp object, the field value will become {} after conversion
    3. If the object has a field value of NaN, +-Infinity, the field value becomes null after conversion
    4. If the object has a ring reference, the conversion will directly report an error


    version two

    Since it is a deep copy of the object, we can create an empty object and copy the values ​​of the original object that need to be copied one by one.

      function deepClone(target) {
        if (typeof target !== "object") {
          return target;
        }
        const temp = Array.isArray(target) ? [] : {};
        for (const key in target) {
          temp[key] = typeof target[key] == "object" ? deepCopy(target[key] ) : target[key];
        }
        return temp;
      }
    
      const a = {
        name: "fryao",
        age: 23,
        hobbies: { sports: "basketball"},
        color:['red','green']
      };
      const b = deepClone(a);
      console.log(b === a); // false
    </script>
    


    version three

    None of the previously implemented methods solved the problem of ring references

    JSON.parse(JSON.stringify(target)) reports an error TypeError: Converting circular structure to JSON, which means that the ring reference cannot be processed
    The recursive method reports an error Maximum call stack size exceeded, which means that the recursion is endless and the stack overflows

    // ring reference
    const a = {}
    a.key = a
    

    So how to solve the ring reference? In fact, it is not difficult, you need to use ES6 data structure Map

    • Every time you traverse to a reference data type, put it as a key and put it in the Map, and the corresponding value is the newly created object temp
    • Every time there is a reference data type in the traversal, go to the Map to find out if there is a corresponding key. If there is, it means that this object has been registered before, and now it is the second time, it must be a ring reference. Directly according to key gets value and returns value

      function deepClone(target, map = new Map()) {
           if (typeof target !== "object") {
             return target;
           }
      
           const temp = Array.isArray(target) ? [] : {};
      
           if (map. get(target)) {
             // If it exists, return directly
             return map. get(target);
           }
           // If it does not exist, set it for the first time
           map.set(target, temp);
           for (const key in target) {
             temp[key] = deepClone(target[key], map);
           }
           return temp;
         }
      
         const a = {
           name: "fryao",
           age: 23,
           hobbies: { sports: "basketball" },
           color: ["red", "green"],
         };
         a.key = a; // ring reference
        const b = deepClone(a);
        console.log(b);
        // {
        //      name: "fryao",
        // age: 23,
        //  hobbies: { sports: "basketball"},
        //  color:['red','green']
        //     key: [Circular]
        // }
        console.log(b === a); // false
      


    final version

    Previously, we only realized the copying of basic data types and the copying of arrays and objects in reference data types, but we still have to solve the problem of copying reference types

    We first divide the above reference type data into two categories

    • Traversable data types
    • non-traversable data types

      // iterable type
      const mapTag = '[object Map]';
      const setTag = '[object Set]';
      const arrayTag = '[object Array]';
      const objectTag = '[object Object]';
      
      // non-traversable type
      const symbolTag = '[object Symbol]';
      const regexpTag = '[object RegExp]';
      const funcTag = '[object Function]';
      
      // Store traversable types in an array
      const canForArr = ['[object Map]', '[object Set]',
                          '[object Array]', '[object Object]']
      
      // store non-traversable types in an array
      const noForArr = ['[object Symbol]', '[object RegExp]', '[object Function]']
      
      // Function to judge type
      function checkType(target) {
           return Object.prototype.toString.call(target)
      }
      
      // Determine the temp of the reference type
      function checkTemp(target) {
           const c = target.constructor
           return new c()
      }
      

    Traversable reference types: Mainly deal with the following four types

    • map
    • set
    • object
    • Array

    non-traversable reference types: Mainly deal with the following types

    • Symbol
    • RegExp
    • function

    First write out the method of copying these three types:

    // method to copy Function
    function cloneFunction(func) {
         const bodyReg = /(?<={)(.|\n)+(?=})/m;
         const paramReg = /(?<=\().+(?=\)\s+{)/;
         const funcString = func.toString();
         if (func. prototype) {
             const param = paramReg.exec(funcString);
             const body = bodyReg.exec(funcString);
             if (body) {
                 if (param) {
                     const paramArr = param[0]. split(',');
                     return new Function(...paramArr, body[0]);
                 } else {
                     return new Function(body[0]);
                 }
             } else {
                 return null;
             }
         } else {
             return eval(funcString);
         }
    }
    
    // method to copy Symbol
    function cloneSymbol(target) {
         return Object(Symbol.prototype.valueOf.call(target));
    }
    
    // method to copy RegExp
    function cloneReg(target) {
         const reFlags = /\w*$/;
         const result = new target.constructor(target.source, reFlags.exec(target));
         result.lastIndex = target.lastIndex;
         return result;
    }
    


    final integrated version

     function deepClone(target, map = new Map()) {
            // get type
            const type = checkType(target);
            // Basic data types return directly
            if (!canForArr.concat(noForArr).includes(type)) return target;
    
            // Judge Function, RegExp, Symbol
            if (type === funcTag) return cloneFunction(target);
            if (type === regexpTag) return cloneReg(target);
            if (type === symbolTag) return cloneSymbol(target);
    
            // Special handling for reference data types
            const temp = checkTemp(target);
    
            if (map.get(target)) {
              // If it exists, return directly
              return map.get(target);
            }
            // If it does not exist, set it for the first time
            map.set(target, temp);
    
            // Handle the Map type
            if (type === mapTag) {
              target.forEach((value, key) => {
                temp.set(key, deepClone(value, map));
              });
              return temp;
            }
    
            // handle Set type
            if (type === setTag) {
              target.forEach((value) => {
                temp.add(deepClone(value, map));
              });
              return temp;
            }
    
            // handle data and objects
            for (const key in target) {
              // recursion
              temp[key] = deepClone(target[key], map);
            }
            return temp;
          }
    
          const a = {
            name: "fryao",
            age: 23,
            hobbies: { sports: "basketball" },
            color: ["red", "green"],
            map: new Map([
              ["hey", 111],
              ["yep", 222],
            ]),
            set: new Set([1, 2, 3]),
            func: (name, age) => `${name} is ${age} years old this year`,
            sym: Symbol(123),
            reg: new RegExp(/haha/g),
          };
          a.key = a; // ring reference
    
          const b = deepClone(a);
          console.log(b);
          // {
          //     name: 'fryao',
          //     age: 18,
          //     hobbies: { sports: 'basketball'},
          //     color: ["red", "green"],
          //     map: Map { 'hey' => 111, 'yep' => 222 },
          //     set: Set { 1, 2, 3 },
          //     func: [Function],
          //     sym: [Symbol: Symbol(123)],
          //     reg: /haha/g,
          //     key: [Circular]
          // }
          console.log(b === a); // false
    

    Popular posts from this blog

    大学资料分享——广工计院各个科目实验报告、课设及期末复习资料!!

    Win10 远程计算机或设备将不接受连接

    JAVA Traffic Signal Light Course Design (Source Code + Report + Video Demonstration)

    Implementation of Ajax Interceptor