← Technology Archive

Historical archive

Deep Copy vs. Shallow Copy in JavaScript

The difference between copying references and cloning values, the limits of JSON serialization, and recursive ES5 and ES6 deep-clone implementations.

The essential difference between a deep copy and a shallow copy is whether nested data is independently duplicated or still shared through references.

JavaScript primitive values are copied by value. Objects and arrays are reference values, so a simple assignment copies the reference to the same object.

JSON.parse(JSON.stringify(obj))

Serializing and parsing an object can provide a simple deep copy for JSON-compatible data:

const copy = JSON.parse(JSON.stringify(obj));

It does not work correctly for every JavaScript value:

  1. Date objects become strings.
  2. RegExp and Error objects lose their useful data.
  3. Functions and undefined properties are omitted.
  4. NaN, Infinity, and -Infinity become null.
  5. Circular references cause serialization to fail.

ES5 recursive clone

function deepClone(origin, target) {
  var result = target || {};
  var toString = Object.prototype.toString;
  var arrayType = '[object Array]';

  for (var key in origin) {
    if (Object.prototype.hasOwnProperty.call(origin, key)) {
      if (typeof origin[key] === 'object' && origin[key] !== null) {
        result[key] = toString.call(origin[key]) === arrayType ? [] : {};
        deepClone(origin[key], result[key]);
      } else {
        result[key] = origin[key];
      }
    }
  }

  return result;
}

ES6 clone with WeakMap

A WeakMap remembers previously cloned objects and prevents infinite recursion when the input contains cycles.

function deepClone(origin, seen = new WeakMap()) {
  if (origin === undefined || origin === null || typeof origin !== 'object') {
    return origin;
  }

  if (origin instanceof Date) {
    return new Date(origin);
  }

  if (origin instanceof RegExp) {
    return new RegExp(origin);
  }

  const existing = seen.get(origin);
  if (existing) {
    return existing;
  }

  const target = new origin.constructor();
  seen.set(origin, target);

  for (const key in origin) {
    if (Object.prototype.hasOwnProperty.call(origin, key)) {
      target[key] = deepClone(origin[key], seen);
    }
  }

  return target;
}

Modern runtimes also provide structuredClone() for many common structured data types, although it cannot clone every possible JavaScript value.