Deep Copy vs JSON Stringify / JSON Parse

JavaScript performance comparison

Revision 80 of this test case created by

Preparation code

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>

      
<script>
Benchmark.prototype.setup = function() {
  function recursiveDeepCopy(o) {
    var newO,
      i;
  
    if (typeof o !== 'object') {
      return o;
    }
    if (!o) {
      return o;
    }
  
    if ('[object Array]' === Object.prototype.toString.apply(o)) {
      newO = [];
      for (i = 0; i < o.length; i += 1) {
        newO[i] = recursiveDeepCopy(o[i]);
      }
      return newO;
    }
  
    newO = {};
    for (i in o) {
      if (o.hasOwnProperty(i)) {
        newO[i] = recursiveDeepCopy(o[i]);
      }
    }
    return newO;
  }
  
  
  function deepCopy(o) {
    var copy = o,
      k;
  
    if (o && typeof o === 'object') {
      copy = Object.prototype.toString.call(o) === '[object Array]' ? [] : {};
      for (k in o) {
        copy[k] = deepCopy(o[k]);
      }
    }
  
    return copy;
  }
  
  function clone(obj) {
    if (obj == null || typeof(obj) != 'object')
      return obj;
  
    var temp = new obj.constructor();
    for (var key in obj)
      temp[key] = clone(obj[key]);
  
    return temp;
  }
  
  var uc = {
    "list": {
      "0oVwOM": {
        "id": "0oVwOM",
        "parent": "pTlmbh",
        "name": "New node",
        "created_at": 1384289621
      },
      "aHxe8k": {
        "id": "aHxe8k",
        "parent": "Fhs2hL",
        "name": "hjkhjkhjk",
        "created_at": 1384354593
      },
      "Fhs2hL": {
        "id": "Fhs2hL",
        "parent": "root",
        "name": "test",
        "created_at": 1383403881
      },
      "HYPSgv": {
        "id": "HYPSgv",
        "parent": "0oVwOM",
        "name": "New node",
        "created_at": 1384342657
      },
      "osFIMf": {
        "id": "osFIMf",
        "parent": "root",
        "name": "New node",
        "created_at": 1384354584
      },
      "PsovXE": {
        "id": "PsovXE",
        "parent": "root",
        "name": "hjkhjkhjk",
        "created_at": 1384354589
      },
      "pTlmbh": {
        "id": "pTlmbh",
        "parent": "Fhs2hL",
        "name": "New node",
        "created_at": 1384289277
      },
      "RbXhdJ": {
        "id": "RbXhdJ",
        "parent": "root",
        "name": "empty",
        "created_at": 1384359806
      }
    },
    "maps": {
      "parent": {
        "pTlmbh": {
          "0oVwOM": {
            "id": "0oVwOM",
            "parent": "pTlmbh",
            "name": "New node",
            "created_at": 1384289621
          }
        },
        "Fhs2hL": {
          "aHxe8k": {
            "id": "aHxe8k",
            "parent": "Fhs2hL",
            "name": "hjkhjkhjk",
            "created_at": 1384354593
          },
          "pTlmbh": {
            "id": "pTlmbh",
            "parent": "Fhs2hL",
            "name": "New node",
            "created_at": 1384289277
          }
        },
        "root": {
          "Fhs2hL": {
            "id": "Fhs2hL",
            "parent": "root",
            "name": "test",
            "created_at": 1383403881
          },
          "osFIMf": {
            "id": "osFIMf",
            "parent": "root",
            "name": "New node",
            "created_at": 1384354584
          },
          "PsovXE": {
            "id": "PsovXE",
            "parent": "root",
            "name": "hjkhjkhjk",
            "created_at": 1384354589
          },
          "RbXhdJ": {
            "id": "RbXhdJ",
            "parent": "root",
            "name": "empty",
            "created_at": 1384359806
          }
        },
        "0oVwOM": {
          "HYPSgv": {
            "id": "HYPSgv",
            "parent": "0oVwOM",
            "name": "New node",
            "created_at": 1384342657
          }
        }
      },
      "path": [
        ["Fhs2hL"],
        ["Fhs2hL", "aHxe8k"],
        ["Fhs2hL", "pTlmbh"],
        ["Fhs2hL", "pTlmbh", "0oVwOM"],
        ["Fhs2hL", "pTlmbh", "0oVwOM", "HYPSgv"],
        ["osFIMf"],
        ["PsovXE"],
        ["RbXhdJ"]
      ]
    }
  };
  
  function recursive(obj) {
    var clone, i;
  
    if (typeof obj !== 'object' || !obj)
      return obj;
  
    if ('[object Array]' === Object.prototype.toString.apply(obj)) {
      clone = [];
      var len = obj.length;
      for (i = 0; i < len; i++)
        clone[i] = recursive(obj[i]);
      return clone;
    }
  
    clone = {};
    for (i in obj)
      if (obj.hasOwnProperty(i))
        clone[i] = recursive(obj[i]);
    return clone;
  }

};
</script>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in CCBot 2.0.0 / Other 0.0.0
Test Ops/sec
angular copy
var uc = {
  "list": {
    "0oVwOM": {
      "id": "0oVwOM",
      "parent": "pTlmbh",
      "name": "New node",
      "created_at": 1384289621
    },
    "aHxe8k": {
      "id": "aHxe8k",
      "parent": "Fhs2hL",
      "name": "hjkhjkhjk",
      "created_at": 1384354593
    },
    "Fhs2hL": {
      "id": "Fhs2hL",
      "parent": "root",
      "name": "test",
      "created_at": 1383403881
    },
    "HYPSgv": {
      "id": "HYPSgv",
      "parent": "0oVwOM",
      "name": "New node",
      "created_at": 1384342657
    },
    "osFIMf": {
      "id": "osFIMf",
      "parent": "root",
      "name": "New node",
      "created_at": 1384354584
    },
    "PsovXE": {
      "id": "PsovXE",
      "parent": "root",
      "name": "hjkhjkhjk",
      "created_at": 1384354589
    },
    "pTlmbh": {
      "id": "pTlmbh",
      "parent": "Fhs2hL",
      "name": "New node",
      "created_at": 1384289277
    },
    "RbXhdJ": {
      "id": "RbXhdJ",
      "parent": "root",
      "name": "empty",
      "created_at": 1384359806
    }
  },
  "maps": {
    "parent": {
      "pTlmbh": {
        "0oVwOM": {
          "id": "0oVwOM",
          "parent": "pTlmbh",
          "name": "New node",
          "created_at": 1384289621
        }
      },
      "Fhs2hL": {
        "aHxe8k": {
          "id": "aHxe8k",
          "parent": "Fhs2hL",
          "name": "hjkhjkhjk",
          "created_at": 1384354593
        },
        "pTlmbh": {
          "id": "pTlmbh",
          "parent": "Fhs2hL",
          "name": "New node",
          "created_at": 1384289277
        }
      },
      "root": {
        "Fhs2hL": {
          "id": "Fhs2hL",
          "parent": "root",
          "name": "test",
          "created_at": 1383403881
        },
        "osFIMf": {
          "id": "osFIMf",
          "parent": "root",
          "name": "New node",
          "created_at": 1384354584
        },
        "PsovXE": {
          "id": "PsovXE",
          "parent": "root",
          "name": "hjkhjkhjk",
          "created_at": 1384354589
        },
        "RbXhdJ": {
          "id": "RbXhdJ",
          "parent": "root",
          "name": "empty",
          "created_at": 1384359806
        }
      },
      "0oVwOM": {
        "HYPSgv": {
          "id": "HYPSgv",
          "parent": "0oVwOM",
          "name": "New node",
          "created_at": 1384342657
        }
      }
    },
    "path": [
      ["Fhs2hL"],
      ["Fhs2hL", "aHxe8k"],
      ["Fhs2hL", "pTlmbh"],
      ["Fhs2hL", "pTlmbh", "0oVwOM"],
      ["Fhs2hL", "pTlmbh", "0oVwOM", "HYPSgv"],
      ["osFIMf"],
      ["PsovXE"],
      ["RbXhdJ"]
    ]
  }
};

function isArrayBuffer(obj) {
  return toString.call(obj) === '[object ArrayBuffer]';
}

function isFunction(value) {return typeof value === 'function';}
function isNumber(value) {return typeof value === 'number';}

var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/;
function isTypedArray(value) {
  return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
}

function isBlankObject(value) {
  return value !== null && typeof value === 'object' && !Object.getPrototypeOf(value);
}

function isScope(obj) {
  return obj && obj.$evalAsync && obj.$watch;
}

function setHashKey(obj, h) {
  if (h) {
    obj.$$hashKey = h;
  } else {
    delete obj.$$hashKey;
  }
}

function isWindow(obj) {
  return obj && obj.window === obj;
}


function isObject(value) {
  // http://jsperf.com/isobject4
  return value !== null && typeof value === 'object';
}

export default function copy(source, destination) {
  var stackSource = [];
  var stackDest = [];

  if (destination) {
    if (isTypedArray(destination) || isArrayBuffer(destination)) {
      throw Error('Can\'t copy! TypedArray destination cannot be mutated.');
    }
    if (source === destination) {
      throw Error('Can\'t copy! Source and destination are identical.');
    }

    // Empty the destination object
    if (Array.isArray(destination)) {
      destination.length = 0;
    } else {
      for(let key in destination) {
        if (key !== '$$hashKey') {
          delete destination[key];
        }
      }
    }

    // @ts-ignore // TODO
    stackSource.push(source);
    // @ts-ignore // TODO
    stackDest.push(destination);
    return copyRecurse(source, destination);
  }

  return copyElement(source);

  function copyRecurse(source, destination) {
    var h = destination.$$hashKey;
    var key;
    if (Array.isArray(source)) {
      for (var i = 0, ii = source.length; i < ii; i++) {
        destination.push(copyElement(source[i]));
      }
    } else if (isBlankObject(source)) {
      // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
      for (key in source) {
        destination[key] = copyElement(source[key]);
      }
    } else if (source && typeof source.hasOwnProperty === 'function') {
      // Slow path, which must rely on hasOwnProperty
      for (key in source) {
        if (source.hasOwnProperty(key)) {
          destination[key] = copyElement(source[key]);
        }
      }
    } else {
      // Slowest path --- hasOwnProperty can't be called as a method
      for (key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          destination[key] = copyElement(source[key]);
        }
      }
    }
    setHashKey(destination, h);
    return destination;
  }

  function copyElement(source) {
    // Simple values
    if (!isObject(source)) {
      return source;
    }

    // Already copied values

    // @ts-ignore // TODO
    var index = stackSource.indexOf(source);
    if (index !== -1) {
      return stackDest[index];
    }

    if (isWindow(source) || isScope(source)) {
      throw Error('Can\'t copy! Making copies of Window or Scope instances is not supported.');
    }

    var needsRecurse = false;
    var destination = copyType(source);

    if (destination === undefined) {
      destination = Array.isArray(source) ? [] : Object.create(Object.getPrototypeOf(source));
      needsRecurse = true;
    }


    // @ts-ignore
    stackSource.push(source);
    // @ts-ignore
    stackDest.push(destination);

    return needsRecurse
      ? copyRecurse(source, destination)
      : destination;
  }

  function copyType(source) {
    switch (toString.call(source)) {
      case '[object Int8Array]':
      case '[object Int16Array]':
      case '[object Int32Array]':
      case '[object Float32Array]':
      case '[object Float64Array]':
      case '[object Uint8Array]':
      case '[object Uint8ClampedArray]':
      case '[object Uint16Array]':
      case '[object Uint32Array]':
        return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length);

      case '[object ArrayBuffer]':
        // Support: IE10
        if (!source.slice) {
          // If we're in this case we know the environment supports ArrayBuffer
          /* eslint-disable no-undef */
          var copied = new ArrayBuffer(source.byteLength);
          new Uint8Array(copied).set(new Uint8Array(source));
          /* eslint-enable */
          return copied;
        }
        return source.slice(0);

      case '[object Boolean]':
      case '[object Number]':
      case '[object String]':
      case '[object Date]':
        return new source.constructor(source.valueOf());

      case '[object RegExp]':
        var re = new RegExp(source.source, source.toString().match(/[^/]*$/)[0]);
        re.lastIndex = source.lastIndex;
        return re;

      case '[object Blob]':
        return new source.constructor([source], {type: source.type});
    }

    if (isFunction(source.cloneNode)) {
      return source.cloneNode(true);
    }
  }
}

var bes = copy(uc)
pending…
Deep Copy
var bes = deepCopy(uc)
pending…
JSON Stringify / JSON Parse
var bes = JSON.parse(JSON.stringify(uc))
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL.

0 Comments