banner
SlhwSR

SlhwSR

热爱技术的一名全栈开发者
github
bilibili

Implementation and principles of deep and shallow copy

1. Data Type Storage#

In the previous article, we mentioned that there are two major data types in JavaScript:

  • Primitive types
  • Reference types

Primitive type data is stored in the stack memory.

Reference type data is stored in the heap memory. Variables of reference data types are references to actual objects in the heap and are stored in the stack.

2. Shallow Copy#

Shallow copy refers to creating a new data that has an exact copy of the original data's property values.

If the property is a primitive type, the copy is the value of the primitive type. If the property is a reference type, the copy is the memory address.

In other words, shallow copy only copies one level, while deeply nested reference types share the same memory address.

Here is a simple implementation of shallow copy:

function shallowClone(obj) {
    const newObj = {};
    for(let prop in obj) {
        if(obj.hasOwnProperty(prop)){
            newObj[prop] = obj[prop];
        }
    }
    return newObj;
}

In JavaScript, there are several ways to achieve shallow copy:

  • Object.assign
  • Array.prototype.slice(), Array.prototype.concat()
  • Copying using the spread operator

Object.assign#

var obj = {
    age: 18,
    nature: ['smart', 'good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    },
    love: function () {
        console.log('fx is a great girl')
    }
}
var newObj = Object.assign({}, obj);

slice()#

const arr = ["One", "Two", "Three"]
const newArr = arr.slice(0)
newArr[1] = "love";
console.log(arr) // ["One", "Two", "Three"]
console.log(newArr) // ["One", "love", "Three"]

concat()#

const arr = ["One", "Two", "Three"]
const newArr = arr.concat()
newArr[1] = "love";
console.log(arr) // ["One", "Two", "Three"]
console.log(newArr) // ["One", "love", "Three"]

Spread Operator#

const arr = ["One", "Two", "Three"]
const newArr = [...arr]
newArr[1] = "love";
console.log(arr) // ["One", "Two", "Three"]
console.log(newArr) // ["One", "love", "Three"]

3. Deep Copy#

Deep copy creates a new stack, and the two objects are completely the same but have different addresses. Modifying the properties of one object will not affect the properties of the other object.

Common methods for deep copy are:

  • _.cloneDeep()
  • jQuery.extend()
  • JSON.stringify()
  • Custom loop recursion

_.cloneDeep()#

const _ = require('lodash');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

jQuery.extend()#

const $ = require('jquery');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false

JSON.stringify()#

const obj2=JSON.parse(JSON.stringify(obj1));

However, this method has drawbacks as it ignores undefined, symbol, and function.

const obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

Loop Recursion#

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (typeof obj !== "object") return obj;
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

4. Differences#

First, let's take a look at two diagrams to see the difference between shallow copy and deep copy more clearly.

image

From the above diagram, we can see that both shallow copy and deep copy create a new object, but the behavior of copying object properties is different.

Shallow copy only copies the pointers to the properties that point to objects, rather than copying the objects themselves. The new and old objects still share the same memory, so modifying the object properties will affect the original object.

// Shallow copy
const obj1 = {
    name : 'init',
    arr : [1,[2,3],4],
};
const obj3 = shallowClone(obj1); // A shallow copy method
obj3.name = "update";
obj3.arr[1] = [5,6,7]; // The new and old objects still share the same memory

console.log('obj1', obj1); // obj1 { name: 'init',  arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3', obj3); // obj3 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }

On the other hand, deep copy creates a completely identical object with a different memory address. Modifying the properties of one object will not affect the properties of the other object.

// Deep copy
const obj1 = {
    name : 'init',
    arr : [1,[2,3],4],
};
const obj4 = deepClone(obj1); // A deep copy method
obj4.name = "update";
obj4.arr[1] = [5,6,7]; // The new object has a different memory address from the original object

console.log('obj1', obj1); // obj1 { name: 'init', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4', obj4); // obj4 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }

Summary#

In the case of copying reference types:

  • Shallow copy only copies one level. When the property is an object, the shallow copy is a reference, and the two objects point to the same address.
  • Deep copy recursively copies the nested levels. When the property is an object, the deep copy creates a new stack, and the two objects point to different addresses.
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.