JS to realize deep copy

Time:2021-7-17

catalogue

  • JS implementation
    • Simple deep copy (one shallow copy)
    • Rough deep copy (discard object‘s constructor)
    • Complex deep copy (relatively perfect)
  • Es implementation
    • Clonedeep method in lodash (perfect)

1、 JS implementation

1. Simple deep copy (one layer of shallow copy)

① For circular copy

//Copy only the shallow copy of the first layer
function simpleCopy(obj1) {
   var obj2 = Array.isArray(obj1) ? [] : {};
   for (let i in obj1) {
   obj2[i] = obj1[i];
  }
   return obj2;
}
var obj1 = {
   a: 1,
   b: 2,
   c: {
   d: 3
  }
}
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // 1
alert(obj2.a); // 3
alert(obj1.c.d); // 4
alert(obj2.c.d); // 4

② Object. Assign() implements one layer deep copy

var obj1 = {
    a: 1,
    b: 2,
    c: 3
}
var obj2 = Object.assign({}, obj1);
obj2.b = 5;
console.log(obj1.b); // 2
console.log(obj2.b); // 5
var obj1 = {
    a: 1,
    b: 2,
    c: ['a','b','c']
}
var obj2 = Object.assign({}, obj1);
obj2.c[1] = 5;
console.log(obj1.c); // ["a", 5, "c"]
console.log(obj2.c); // ["a", 5, "c"]

③ Slice implementation

//Use slice for array objects with only one level of property values
var a = [1,2,3,4];
var b = a.slice();
b[0] = 2;
alert(a); // 1,2,3,4
alert(b); // 2,2,3,4
//Use slice for array objects with multiple attributes
var a = [1,[1,2],3,4];
var b = a.slice();
b[1][0] = 2;
alert(a); // 1,2,2,3,4
alert(b); // 1,2,2,3,4

④ Using the concat () method

var a=[1,2,[3,4]]
 var c=[];
 var b=c.concat(a);
 b[0]=5;
 b[2][0]=6;
 console.log(b[0]);//5
 console.log(a[0])//1
 console.log(b[2][0]);//6
 console.log(a[2][0])//6

⑤ Extension operator “…” for ES6

var a=[1,2,[3,4]]
 var b=[...a];
 b[0]=5;
 b[2][0]=6
 console.log(b[0]);//5
 console.log(a[0])//1
 console.log(b[2][0]);//6
 console.log(a[2][0])//6

Through object. Create()

function deepCopy(obj) {
  var copy = Object.create(Object.getPrototypeOf(obj));
  var propNames = Object.getOwnPropertyNames(obj);
  
  propNames.forEach(function(name) {
    var desc = Object.getOwnPropertyDescriptor(obj, name);
    Object.defineProperty(copy, name, desc);
  });
  
  return copy;
}

var obj1 = { a: 1, b: {bc: 50, dc: 100, be: {bea: 1}} };
var obj2 = deepCopy(obj1);
obj2.a = 20;
obj2.b.bc = 60;
console.log(obj1.a)//1
console.log(obj2.a)//20
console.log(obj1.b.bc)//60
console.log(obj2.b.bc)//60

2. Rough deep copy (discarding the constructor of the object)

Using json.stringify and json.parse to realize deep copy: json.stringify converts objects into strings, and then json.parse converts strings into new objects;

function deepCopy(obj1){
    let _obj = JSON.stringify(obj1);
    let obj2 = JSON.parse(_obj);
    return obj2;
}
var a = [1, [1, 2], 3, 4];
var b = deepCopy(a);
b[1][0] = 2;
alert(a); // 1,1,2,3,4
alert(b); // 2,2,2,3,4

Defect: it discards the constructor of the object. After deep copy, no matter what the original constructor of the object is, it will become object after deep copy; Only number, string, Boolean, array and flat objects can be handled correctly by this method. In other words, only objects that can be converted to JSON format can be used in this way, such as function can’t be converted to JSON;

let obj1 = {
   fun:function(){
      alert(123);
   }
}
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun); // function
console.log(typeof obj2.fun); // undefined

3. Complex deep copy (relatively perfect)

Recursive copy realizes deep copy and solves the problem of circular reference

/**
 *Determine whether it is a basic data type
 * @param value 
 */
function isPrimitive(value){
  return (typeof value === 'string' || 
  typeof value === 'number' || 
  typeof value === 'symbol' ||
  typeof value === 'boolean')
}

/**
 *Judge whether it is a JS object
 * @param value 
 */
function isObject(value){
  return Object.prototype.toString.call(value) === "[object Object]"
}

/**
 *Deep copy a value
 * @param value 
 */
function cloneDeep(value){

  //Record the copied value to avoid circular reference
  let memo = {};

  function baseClone(value){
    let res;
    //If it is a basic data type, it is returned directly
    if(isPrimitive(value)){
      return value;
    //If it is a reference data type, we simply copy a new value instead of the original value
    }else if(Array.isArray(value)){
      res = [...value];
    }else if(isObject(value)){
      res = {...value};
    }

    //Check whether the attribute value of the object we copy is a reference data type. If so, copy recursively
    Reflect.ownKeys(res).forEach(key=>{
      if(typeof res[key] === "object" && res[key]!== null){
        //Here we use memo to record the reference address that has been copied. In order to solve the problem of circular reference
        if(memo[res[key]]){
          res[key] = memo[res[key]];
        }else{
          memo[res[key]] = res[key];
          res[key] = baseClone(res[key])
        }
      }
    })
    return res;  
  }

  return baseClone(value)
}

2、 Es plug in lodash

import lodash from 'lodash'

var objects = [1,{ 'a': 1 }, { 'b': 2 }]; 
var deep = lodash.cloneDeep(objects);
deep[0] = 2;
deep[1].a = 2;
console.log(objects[0]);//1
console.log(deep[0]);//2
console.log(objects[1].a);//1
console.log(objects[1].a);//2

Standing on the shoulders of giants picking apples

https://www.jianshu.com/p/cf1e9d7e94fb

https://www.jianshu.com/p/5f6cd3dabc1c

https://segmentfault.com/a/1190000015455662

https://segmentfault.com/a/1190000018371840

https://www.jianshu.com/p/f4329eb1bace

https://www.lodashjs.com/docs/lodash