cloning an object

JavaScript performance comparison

Revision 36 of this test case created

Info

There is no quick and easy facility for cloning an object, Some people recommend using JQuery.extend others JSON.parse/stringify

http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-a-javascript-object

If you want the fastest possible clone function. I would personally anticipate the data structure of your object and write a custom clone to handle it.

Added a test for the history.replaceState method demonstrated here: http://stackoverflow.com/a/10916838 and found that it throws an error for this complex object.

Made an attempt in this clone function to properly handle functions, classes, and some natural javascript object types like Date and RegExp.

This clone function also supports the calling of a clone method on a class or function if one exists. Here is an example of a class defined to be cloneable.

function Classy(init) {
  var internal = init || 0;
  return { "increment": function () { internal++; },
           "getInternal": function () { return internal },
           "clone": function () { return new Classy(internal); }
  }
};

Note: to define a class cloneable you will need to design it in such a way that any class-level variable that may be manipulated over the life of the object's instance is 1) settable in the constructor, and 2) get-able from the instance.

Here's an example of using the class above:

var classyA = new Classy();
classyA.getInternal();  //<-- returns 0
var classyB = classyA;
classyA.increment();
classyB.getInternal();  //<-- returns 1, classyB is a reference to classA
var classyC = classyA.clone();
classyC.getInternal();  //<-- returns 1, it is a clone of classA at that moment
classyA.increment();
classyB.getInternal();  //<-- returns 2
classyC.getInternal();  //<-- returns 1

Preparation code

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
 
<script>
Benchmark.prototype.setup = function() {
    function Classy(init) {
      var internal = init || 0;
      return { "increment": function () { internal++; },
               "getInternal": function () { return internal },
               "clone": function () { return new Classy(internal); }
      }
    };
    var complexobj = {
        "a": "a string",
        "b": 123456789,
        "c": true,
        "d": null,
        "e": ["a",2,false,null,{}],
        "f": function () { return true; },
        "g": new Date(),
        "h": new RegExp(/aaa/i),
        "i": new Classy(),
        "j": {
            "1":  "a string",
            "2":  123456789,
            "3":  true,
            "4":  null,
            "5":  ["a",2,false,null,{}],
            "6":  function () { return true; },
            "7":  new Date(),
            "8":  new RegExp(/aaa/i),
            "9":  new Classy(),
            "10": {
                "i":    "a string",
                "ii":   123456789,
                "iii":  true,
                "iv":   null,
                "v":    ["a",2,false,null,{}],
                "vi":   function () { return true; },
                "vii":  new Date(),
                "viii": new RegExp(/aaa/i),
                "ix":   new Classy()
            }
        }
    };
   
   
    function clone(obj) {
        var copy;
       
        //types where returning the object will naturally result in a clone
        var objtype = typeof obj;
        if (objtype === "undefined" || obj === null
          || (objtype !== "object" && objtype !== "function")) {
            return obj;
        }
   
        if (objtype === "function") {
            if (obj.hasOwnProperty("clone")) {
                return obj.clone();
            } else {
                return eval("(" + obj.toString() + ")");
            }
        }
   
        if (obj instanceof Date) {
            copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }
   
        if (obj instanceof RegExp) {
            copy = new RegExp(obj.toString());
            return copy;
        }
   
        if (obj instanceof Array) {
            copy = [];
            for (var i=0; i<obj.length; i++) {
                copy[i] = clone(obj[i]);
            }
            return copy;
        }
   
        if (obj instanceof Object) {
            copy = {};
            var keys = Object.keys(obj);
            if (obj.hasOwnProperty("clone")) {
                return obj.clone();
            } else if (keys.length > 0) {
                for (var i=0; i<keys.length; i++) {
                    var key = keys[i];
                    copy[key] = clone(obj[key]);
                }
                return copy;
            } else {
                //not sure all types of objects that would get you into this scenario, so this may take some tweaking
                return JSON.parse(JSON.stringify(obj));
            }
        }
    }
   
    function replaceStateClone(obj) {
        var oldState = history.state;
        history.replaceState(obj, null);
        var clonedObj = history.state;
        history.replaceState(oldState, null);
        return clonedObj;
    }
};
</script>

Preparation code output

Test runner

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

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
jQuery.extend() deep
var newObject = jQuery.extend(true, {}, complexobj);
pending…
JSON stringify/parse
var newObject = JSON.parse(JSON.stringify(complexobj));
pending…
clone function
var newObject = clone(complexobj);
pending…
stringify eval
var newObject = eval("(" + JSON.stringify(complexobj) + ")");
pending…
_.extend
var newObject = _.cloneDeep(complexobj);
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. Here’s a list of current revisions for this page:

0 comments

Comment form temporarily disabled.

Add a comment