cloning an object (v34)

Revision 34 of this benchmark created on


Description

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 HTML

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

Setup

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;
    }

Test runner

Ready to run.

Testing in
TestOps/sec
jQuery.extend() deep
var newObject = jQuery.extend(true, {}, complexobj);
ready
JSON stringify/parse
var newObject = JSON.parse(JSON.stringify(complexobj));
ready
clone function
var newObject = clone(complexobj);
ready
stringify eval
var newObject = eval("(" + JSON.stringify(complexobj) + ")");
ready
angular.copy deep
var newObject = angular.copy(complexobj);
ready

Revisions

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