Deep Copy vs JSON Stringify / JSON Parse

JavaScript performance comparison

Revision 39 of this test case created by

Preparation code


      
      <script>
Benchmark.prototype.setup = function() {
  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;
  }
  
  var uc = {bir:{iki:{uc:3}}, t: new Date(2011, 1, 20, 10,20), reg: /test/ig, b: false, n: new String('aaaa')}
  
  
  
  
  
  /*
      a function for deep cloning objects that contains other nested objects and circular structures.
      objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                      index (z)
                                           |
                                           |
                                           |
                                           |
                                           |
                                           |                      depth (x)
                                           |_ _ _ _ _ _ _ _ _ _ _ _
                                          /_/_/_/_/_/_/_/_/_/
                                         /_/_/_/_/_/_/_/_/_/
                                        /_/_/_/_/_/_/...../
                                       /................./
                                      /.....            /
                                     /                 /
                                    /------------------
              object length (y)    /
  */
  
  
  function deepClone(obj) {
      var i = -1, //depth of the current object relative to the passed 'obj'
          j = 0;  //number of the object's properties
      var arr = new Array(); //3D array to store the references to objects
      return _clone(obj, arr, i, j);
  }
  
  function _clone(obj, arr, i ,j){
      if (typeof obj !== "object") {
          return obj;
      }
  
      var result = Object.create(Object.getPrototypeOf(obj)); //inherit the prototype of the original object
      if(result instanceof Array){
          result.length = Object.keys(obj).length;
      }
  
      i++; //depth is increased because we entered an object here
      j = Object.keys(obj).length; //native method to get the number of properties in 'obj'
      arr[i] = new Array(); //this is the x-axis, each index here is the depth
      arr[i][j] = new Array(); //this is the y-axis, each index is the length of the object (aka number of props)
      //start the depth at current and go down, cyclic structures won't form on depths more than the current one
      for(var depth = i; depth >= 0; depth--){
          //loop only if the array at this depth and length already have elements
          if(arr[depth][j]){
              for(var index = 0; index < arr[depth][j].length; index++){
                  if(obj === arr[depth][j][index]){
                      return obj;
                  }
              } 
          }
      }
  
      arr[i][j].push(obj); //store the object in the array at the current depth and length
      for (var prop in obj) {
          result[prop] = _clone(obj[prop], arr, i, j);
      }
  
      return result;
  }
  
  
  
  
  
  
      // Clone an object or native type in javascript
      // Exceptions (doesn't handle these types):
      //  (1) DOM nodes - use native cloneNode()
      //  (3) non-plain objects (ie. custom objects with special constructors)
      //  (4) does not copy functions/methods
      //  (5) does not support all HTML5 types
      function cloneB(src, srcStack, destStack) {
  		var type = Object.prototype.toString.call(src).toLowerCase().slice(8, -1);
  		if(!src || typeof src != "object" || type === 'function'){
  			return src;
  		}
  		switch(type){
  			case 'string': return new String(src);
  			case 'number': return new Number(src);
  			case 'boolean': return new Boolean(+src);
  			case 'date': return new Date(+src);
  			case 'regexp': return new RegExp(src.source, (src.multiline ? 'm' : '') + (src.ignoreCase ? 'i' : '') + (src.global ? 'g' : ''));
  		}
  
          // Keep track of copies of nested objects to avoid circular references
          var initStacks = !srcStack;
          srcStack || (srcStack = [], destStack = []);
  
          var length = srcStack.length,
              isArr = Array.isArray(src),
              dest = isArr ? new Array(src.length) : {};
  
          while (length--) {
             if (srcStack[length] == src) {
               // return the previous copy if we've seen this object before
               return destStack[length];
             }
          }
          srcStack.push(src);
          destStack.push(dest);
  
          var keys = isArr ? undefined : Object.keys(src),
              size = isArr ? src.length : keys.length,
              index = -1,
              i;
          while (++index < size) {
              i = keys ? keys[index] : index;
              dest[i] = cloneB(src[i], srcStack, destStack);
          }
  
        if (initStacks) {
          srcStack.length = destStack.length = 0;
        }
        return dest;
      };
  
  
  
  
  
  
  
  
      function cloneC(src, srcStack, destStack) {
      
        if(!src || typeof src != "object" || Object.prototype.toString.call(src) === "[object Function]"){
                  return src;     
          }
        // var type = toString.call(src);
        if (src instanceof String) return new String(src);
        if (src instanceof Number) return new Number(src);
        if (src instanceof Boolean) return new Boolean(+src);
        if (src instanceof Date)
            return new Date(+src); // src.getTime());
        if (src instanceof RegExp) {
            var flags = (src.multiline ? 'm' : '')
                      + (src.ignoreCase ? 'i' : '')
                      + (src.global ? 'g' : ''),
                _rx = new RegExp(src.source, flags);
            return _rx;
        }
      
          // Keep track of copies of nested objects to avoid circular references
          var initStacks = !srcStack;
          srcStack || (srcStack = [], destStack = []);
          
          var length = srcStack.length,
              isArr = Array.isArray(src),
              dest = isArr ? new Array(src.length) : {};
      
          while (length--) {
             if (srcStack[length] == src) {
               // return the previous copy if we've seen this object before
               return destStack[length];
             }
          }
          srcStack.push(src);
          destStack.push(dest);
      
          var keys = isArr ? undefined : Object.keys(src),
              size = isArr ? src.length : keys.length,
              index = -1,
              i;
          while (++index < size) {
              i = keys ? keys[index] : index;
              dest[i] = cloneC(src[i], srcStack, destStack);
          }
      
        if (initStacks) {
          srcStack.length = destStack.length = 0;
        }
        return dest;
      };
  

};
</script>

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
Deep Copy
var bes = deepCopy(uc)
pending…
JSON Stringify / JSON Parse
var bes = JSON.parse(JSON.stringify(uc))
pending…
deepClone
var bes2 = deepClone(uc)
pending…
cloneB
var bes3 = cloneB(uc)
pending…
cloneC
var besC = cloneC(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