How to clone an object in JavaScript

Cloning an object in JavaScript means creating a new object that has the same properties and values as the original one, but is not the same reference. It’s harder than you think. Writing your own cloning function is not recommended.

There are different ways to clone an object, depending on how deep you want to copy, and how you want to handle reference types, circular references, functions, special objects, hidden properties.

Each method has its pros and cons. Choose it wisely!

  1. Using spread operator (...) to shallow clone an object. This means that only the top-level properties are copied, and any nested objects or arrays will still refer to the original ones.
const obj = { a: 1, b: { c: 2 } };

const clone = { ...obj }; // shallow clone
console.log(clone); // { a: 1, b: { c: 2 } }

clone.a = 3; // change clone
console.log(obj.a); // 1, original is not affected

clone.b.c = 4; // change clone
console.log(obj.b.c); // 4, original is affected
  1. Using Object.assign() method to shallow clone an object. This works similarly to the spread operator, but allows you to assign multiple sources to a target object.
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };

const clone = Object.assign({}, obj1, obj2); // shallow clone
console.log(clone); // { a: 1, b: 2, c: 3, d: 4 }

clone.a = 5; // change clone
console.log(obj1.a); // 1, original is not affected

clone.c = 6; // change clone
console.log(obj2.c); // 3, original is not affected
  1. Using JSON.parse() and JSON.stringify() methods to deep clone an object. This means that all the properties and values are copied, including nested objects and arrays. However, this method only works for objects that can be serialized to JSON, which excludes functions, dates, undefined, infinity, regexps, maps, sets, and other complex types.
const obj = { a: 1, b: { c: 2 }, d: new Date(), e: function () {} };

const clone = JSON.parse(JSON.stringify(obj)); // deep clone
console.log(clone); // { a: 1, b: { c: 2 }, d: '2023-11-23T01:03:44.148Z' }

clone.a = 7; // change clone
console.log(obj.a); // 1, original is not affected

clone.b.c = 8; // change clone
console.log(obj.b.c); // 2, original is not affected
console.log(clone.d); // "2023-11-22T12:10:30.000Z", date is stringified
console.log(clone.e); // undefined, function is lost
  1. Using Lodash _.cloneDeep() to deep clone an object. This allows you to handle more complex types and cases, such as circular references, prototype inheritance, and custom cloning logic.
const _ = require('lodash');

const obj = { a: 1, b: { c: 2 }, d: new Date(), e: function () {} };
const clone = _.cloneDeep(obj); // deep clone

clone.a = 9; // change clone
console.log(obj.a); // 1, original is not affected

clone.b.c = 10; // change clone
console.log(obj.b.c); // 2, original is not affected
console.log(clone.d); // 2023-11-22T12:10:30.000Z, date is preserved
console.log(clone.e); // [Function: e], function is preserved
  1. Using global structuredClone() method to deep clone a serializable object. This method isn’t a feature of the JavaScript language itself — instead it’s a feature of browsers and other JavaScript hosts that implement web APIs.
// Define an object with circular references
const obj = { name: "Basit", age: 26 };
obj.self = obj;

// Serialize the object using structuredClone
const clone = structuredClone(obj);

// Check the clone
console.log(clone.name); // "Basit"
console.log(clone.age); // 26
console.log(clone.self === clone); // true

// Modify the clone
clone.name = "John";
clone.age = 30;

// Check the original object
console.log(obj.name); // "Basit", not affected
console.log(obj.age); // 26, not affected
console.log(obj.self === obj); // true, not affected

Why cloning objects is not a trivial task?

Cloning objects in programming, including JavaScript, is not always a trivial task due to the complexities inherent in the nature of objects and the way programming languages handle them. Here are some reasons why cloning objects can be challenging:

  • Primitive types can be cloned by simple assignment, but complex types need to be iterated over and copied recursively.
  • Circular references, which are objects that refer to themselves or other objects in a loop, can cause infinite loops or stack overflow errors when cloning. To avoid this, we need to keep track of the visited objects and use references instead of copying them again.
  • Functions, which are objects that can have properties and behaviors, can be difficult to clone, as they may not preserve their scope, context, or closure. Also, they may use the this keyword or the arguments object, which can affect the cloning process.
  • Special JavaScript objects, such as Date, RegExp, Map, Set, or custom class instances, may not be cloned correctly by some methods, as they may lose some of their properties or methods.
  • Hidden or non-enumerable properties, such as prototype, proto, or symbols, may not be copied by some methods, as they are not accessible or iterable.

How deep you want to copy?

Shallow and deep cloning are two ways of copying objects in JavaScript. Shallow cloning means that only the first level of the object is copied, while deeper levels are referenced. Deep cloning means that all levels of the object are copied, creating a true copy of the original object. Here are some differences between shallow and deep cloning:

  • Shallow cloning is faster and uses less memory than deep cloning, but it can cause problems if the original object is modified or deleted.
  • Deep cloning is slower and uses more memory than shallow cloning, but it ensures that the copied object is independent and consistent.
  • Shallow cloning can be done using the assignment operator, the Object.assign() method, or the spread operator. Deep cloning can be done using the JSON.parse() and JSON.stringify() methods, or using some libraries like lodash.cloneDeep() or jQuery.extend().
  • Shallow cloning works well for primitive types (such as numbers, strings, booleans, etc.), but not for complex types (such as arrays, objects, functions, etc.). Deep cloning works for both primitive and complex types, but it may not preserve some special properties or behaviors of the original object.

Difference between reference and value types

Cloning reference vs. value type in JavaScript is a topic that involves understanding how different types of data are stored, copied, and compared in the language.

A value type is a primitive data type, such as a number, string, boolean, null, or undefined. A value type is stored directly in a variable, and when it is copied to another variable, the value itself is copied. This means that changing one variable does not affect the other, and comparing two variables with the same value type will return true.

var x = 10; // x is a value type
var y = x; // y copies the value of x

x = 20; // changing x does not affect y
console.log (x); // 20
console.log (y); // 10
console.log (x == y); // false

A reference type is an object, such as an array, function, date, or user-defined object. A reference type is stored as a reference (or a pointer) to the location in memory where the object is stored. When it is copied to another variable, the reference is copied, not the object itself. This means that changing one variable will affect the other, and comparing two variables with the same reference type will return false, unless they point to the same object.

var x = [1, 2, 3]; // x is a reference type
var y = x; // y copies the reference of x

x[0] = 20; // changing x affects y
console.log (x); // [20, 2, 3]
console.log (y); // [20, 2, 3]
console.log (x == y); // true

var z = [20, 2, 3]; // z is a new reference type
console.log (x == z); // false

Cloning a value type is easy, as it can be done with a simple assignment. Cloning a reference type is more complicated, as it requires creating a new object and copying all the properties and values of the original object.