How to copy object in JavaScript

1. Clone objects

When you try to use equal operator '=' to copy an object A to a variable B in JavaScript, actually you only got a reference of A to B, that means when any value changes on B will effect A, like:

const A = { 'name': 'kelvin', 'age': 10}
cons B = A;

console.log(A)
// { 'name': 'kelvin', 'age': 10}
console.log(B)
// { 'name': 'kelvin', 'age': 10}

Now let's make some value change for B

B.location = 'Beijing'

console.log(A)
// { 'name': 'kelvin', 'age': 10, 'location': 'Beijing'}
console.log(B)
// { 'name': 'kelvin', 'age': 10, 'location': 'Beijing'}

that's not what we expected. operator = only copied the pointer to the memeory to that object but not the real value.

2 Clone approaches: Shallow Clone vs Deep Clone

Shallow clone only copies primitive type(string, number, boolean, etc) in the object. Nested object or array will not be recursively copied.
Only that object reference will be copied to new object.

Deep clone recursively copied everyting in the object, including primitive type, nested, arrays,functions etc.

Object.assign()

The simplest way to create a shallow copy of an object.

const monster = { life: 100, defense: 100 }
const bossMonster = Object.assing({}, monster);

console.log(bossMonster);
// { life: 100, defense: 100 }

Spread Operator

spread operator (...) is ES6 feature that provides a easy way of shallow clone an object.

const monster = { life: 100, defense: 100 }
const bossMonster = { ...monster }

console.log(bossMonster);
// { life: 100, defense: 100 }

JSON methods

You can use following JSON methods to create a deep clone of the object when it contains primitive types, but it DOES NOT include nested, arrays, Date, function, etc.
JSON.stringify()
JSON.parse()

const monster = { life: 100, defense: 100 }
const bossMonster = JSON.parse(JSON.stringify(monster));

console.log(bossMonster);
// { life: 100, defense: 100 }

2 downsides:

  1. The nested object must be JSON serializable and deserializable
  2. It's slow when the object contains non-compatible values
// undefined is omitted
// Infinity is turned to null
JSON.parse(JSON.stringify({ a: undefined, b: Infinity })); 

// { b: null }

// Date object is turned to string
JSON.parse(JSON.stringify({ a: new Date() })); 

// { a: "2020-06-16T19:44:57.492Z" }

// function is omitted too
JSON.parse(JSON.stringify({ a: () => { return 'Hi'; } })); 

// {}

cloneDeep() in Lodash
cloneDeep() method from Lodash works for all data types, it can correctly recursively copies everything from original object.

const _ = require('lodash');

const profile = {
    name: 'Kelvin',
    age: 10,
    location: {
        city: 'Beijing',
        country: 'China'
    },
    id: undefined,
    workload: Infinity
};

const clonedProfile = _.cloneDeep(obj);

console.log(clonedProfile);

// {
//     name: 'Kelvin',
//     age: 10,
//     location: { city: 'Beijing', country: 'China' },
//     id: undefined
//     workload: Infinity
// }

2. Copy an array

Array.slice()

It can creates a shallow copy of the original array.

const num = [1, 2, 3, 4, 5]
const copiedNum = num.slice();

console.log(copiedNum);
//[1, 2, 3, 4, 5]

Array.from()
it can create a new shallow copied array.

const num = [1, 2, 3, 4, 5]
const copiedNum = Array.from(num);

console.log(copiedNum);
//[1, 2, 3, 4, 5]

Spread operator

const num = [1, 2, 3, 4, 5]
const copiedNum = [...num];

console.log(copiedNum);
//[1, 2, 3, 4, 5]

Array.concat()

const num = [1, 2, 3, 4, 5]
const copiedNum = [].concat(num);

console.log(copiedNum);
//[1, 2, 3, 4, 5]