preface

There are different ways to copy objects in javascript, and if you’re not familiar with the language, it’s easy to fall into the trap of copying objects, so how do you properly copy an object?

After reading this article, I hope you understand:

  • What are deep/shallow copies and how are they different from assignments?
  • How many ways can deep/shallow copies be implemented?

Shallow copy and deep copy

  • A shallow copy is the creation of a new object that has an exact copy of the original object’s property values. If the property is of a primitive type, the value of the primitive type is copied, and if the property is of a reference type, the memory address is copied, so if one object changes the address, the other object is affected.

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

var a1 = {b: {c: {}};

var a2 = shallowClone(a1); // The shallow copy method
a2.b.c === a1.b.c // true The new and old objects still share the same memory

var a3 = deepClone(a3); // The deep copy method
a3.b.c === a1.b.c // false The new object does not share memory with the original object
Copy the code

Here are two images from ConardLi to help us understand what they mean:

In short, a shallow copy copies only Pointers to an object, not the object itself, and the old and new objects still share the same memory. However, a deep copy creates an identical object. The new object does not share memory with the original object. Modifying the new object does not change to the original object.

The difference between assignment and deep/shallow copy

The differences between the three are as follows, but the comparisons are based on reference types:

  • When we assign an object to a new variable, we assign that object’s address on the stack, not the data in the heap. In other words, the two objects point to the same storage space. No matter which object changes, it is the contents of the changed storage space. Therefore, the two objects are linked.

  • Shallow copy: Memory is created in the heap again. The basic data types of objects before and after the copy do not affect each other, but the reference types of objects before and after the copy affect each other because they share the same memory.

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

Let’s look at the following example to compare the effect of the assignment on the original object with that of the deep/shallow copy:

// Object assignment
let obj1 = {
    name : 'Boat in the Waves'.arr : [1[2.3].4]};let obj2 = obj1;
obj2.name = O "wave";
obj2.arr[1] = [5.6.7];console.log('obj1',obj1) / / obj1 {name: 'o waves, arr: [1, [5, 6, 7], 4]}
console.log('obj2',obj2) / / obj2 {name: 'o waves, arr: [1, [5, 6, 7], 4]}
Copy the code
/ / shallow copy
let obj1 = {
    name : 'Boat in the Waves'.arr : [1[2.3].4]};let obj3=shallowClone(obj1)
obj3.name = O "wave";
obj3.arr[1] = [5.6.7];// New and old objects still share the same memory
// This is a shallow copy method
function shallowClone(source) {
    var target = {};
    for(var i in source) {
        if(source.hasOwnProperty(i)) { target[i] = source[i]; }}return target;
}
console.log('obj1',obj1) / / obj1 {name: 'row boat in waves, arr: [1, [5, 6, 7], 4]}
console.log('obj3',obj3) / / obj3 {name: 'o waves, arr: [1, [5, 6, 7], 4]}
Copy the code
/ / copy
let obj1 = {
    name : 'Boat in the Waves'.arr : [1[2.3].4]};let obj4=deepClone(obj1)
obj4.name = O "wave";
obj4.arr[1] = [5.6.7];// The new object does not share memory with the original object
// This is a deep copy method
function deepClone(obj) {
    if (obj === null) return obj; 
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeofobj ! = ="object") return obj;
    let cloneObj = new obj.constructor();
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        // Implement a recursive copycloneObj[key] = deepClone(obj[key]); }}return cloneObj;
}
console.log('obj1',obj1) // obj1 {name: arr: [1, [2, 3], 4]}
console.log('obj4',obj4) / / obj4 {name: 'o waves, arr: [1, [5, 6, 7], 4]}
Copy the code

In the above example, obj1 is the original object, obj2 is the assignment object, obj3 is the shallow copy object, and obj4 is the deep copy object. The table below shows how they affect the original data:

Implementation of shallow copy

1.Object.assign()

The object.assign () method copies any number of enumerable properties of the source Object to the target Object and returns the target Object.

let obj1 = { person: {name: "kobe".age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }
Copy the code

2. The _. Clone method of lodash function library

The library also provides _.clone for Shallow copies, which we’ll cover later for deep copies.

var _ = require('lodash');
var obj1 = {
    a: 1.b: { f: { g: 1}},c: [1.2.3]};var obj2 = _.clone(obj1);
console.log(obj1.b.f === obj2.b.f);// true
Copy the code

3. Expansion operator…

The expansion operator is an ES6 / ES2015 feature that provides a very convenient way to perform shallow copies, as does object.assign ().

let obj1 = { name: 'Kobe'.address: {x:100.y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
Copy the code

4.Array.prototype.concat()

let arr = [1.3, {
    username: 'kobe'
    }];
let arr2 = arr.concat();    
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'wade' } ]
Copy the code

5.Array.prototype.slice()

let arr = [1.3, {
    username: ' kobe'
    }];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr); // [ 1, 3, { username: 'wade' } ]
Copy the code

Deep copy implementation

1.JSON.parse(JSON.stringify())

let arr = [1.3, {
    username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan'; 
console.log(arr, arr4)
Copy the code

While this approach can implement a deep copy of arrays or objects, it can’t handle functions and regex, which are based on json.stringify and json.parse. The resulting regex is no longer a regex (becomes an empty object) and the resulting function is no longer a function (becomes null).

Take the following example:

let arr = [1.3, {
    username: ' kobe'
},function(){}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan'; 
console.log(arr, arr4)
Copy the code

2. The _. CloneDeep method of library lodash

The library also provides _.clonedeep for Deep Copy

var _ = require('lodash');
var obj1 = {
    a: 1.b: { f: { g: 1}},c: [1.2.3]};var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
Copy the code

3. The jQuery. The extend () method

Jquery provides a $.extend for Deep Copy

$.extend(deepCopy, target, object1, [objectN])// The first argument is true, which is a deep copy
Copy the code
var$=require('jquery');
var obj1 = {
    a: 1.b: { f: { g: 1}},c: [1.2.3]};var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
Copy the code

4. Handwriting recursion method

Recursive method to achieve the principle of deep cloning: iterating over objects, arrays until they are all basic data types, and then to copy, is deep copy.

There is a special case needs to pay attention to is the condition of the circular reference object, the object’s properties directly refer to its own situation, solve the problem of circular reference, we can open up a extra storage space, to store the current objects and copy the object correspondence, when need to copy the current object, go to the storage space, find ever copy this object, If so, return directly, if not, continue to copy, so cleverly resolve the circular reference problem. If in doubt, read ConardLi’s deep copy of how to Write an amazing Interviewer. This article.

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // If null or undefined, I do not copy
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // This can be an object or a normal value. If it is a function, it does not need a deep copy
  if (typeofobj ! = ="object") return obj;
  // If it is an object, a deep copy is required
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // Constructor is found for this class stereotype, which refers to the current class itself
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // Implement a recursive copycloneObj[key] = deepClone(obj[key], hash); }}return cloneObj;
}
let obj = { name: 1.address: { x: 100}}; obj.o = obj;// The object has a circular reference
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
Copy the code

Welcome to pay attention to the public number: front-end craftsman, your growth we witness together!

Refer to the article

  • How to write a deep copy of an amazing interviewer?

  • JavaScript shallow copy and deep copy

  • Js Deep copy vs shallow copy

  • The ultimate search for deep copy (unknown to 99% of the population)

  • How to deep clone a JavaScript object