2
\$\begingroup\$

I'm quite new to JavaScript and wrote this clone method to be able to clone any kind of object. It works quite well for the cases I've tested until now, but for JSON objects it seemed kind of slow to me. I'm sure there is some additional speed to to squeeze out and a lot of overall improvement to make. But I don't know enough about JavaScript to optimize this.

Object.prototype.clone = function(deep, falseArray/*check*/) {
var type = Object.prototype.toString.call(this).match(/^\[object (.+?)\]$/)[1];
/*var falseArray;
if(check) {
falseArray = (Object.keys(this).length - this.length);
}*/ //takes around 400ms additional at 500k array to check if it has additional properties, just pass it as argument decide if oy
switch (type) {
    case "Array" :
        var clone = [];
        if (!falseArray) {
            if (!deep)
                clone = this.concat();
            else
                this.forEach(function(e) {
                  if(typeof e !== "undefined" && e !== null)
                    clone.push((typeof e !== "object"?e:e.clone((typeof deep == "boolean"?deep:(deep-1)))));
                  else
                    clone.push("");                           
                });
        } else {// Variable is an 'Array' but has an extra propertie e.g: var arr = [1,2,3]; arr.a = "b" //its the slowest possibility but normally Objects would be used

            for (var prop in this ) {
                clone[prop] = this[prop];
            }
        }
        break;
    case "Object":
        var clone = {};
        if (!deep) {
            for (var prop in this) {
                clone[prop] = this[prop];
            }
        } else {
            for (var prop in this) {
                if(typeof this[prop] !== "undefined" && this[prop]!== null)                  
                  clone[prop] = (typeof this [prop] !== "object"?this[prop]:this[prop].clone((typeof deep == "boolean"?deep:(deep-1))));
                else
                  clone[prop] = ""; 
            }
        }
        break;
    default : var clone = this.valueOf();
            break;  
}
return clone;
};
Object.defineProperty(Object.prototype, "clone", {
enumerable : false
});

And here are some links:

JS Bin example
jsPerf

I've changed it a little bit to have it in Array.Prototype and Object.prototype to avoid the switch for the Object type, but I can't tell if it makes a difference.

Object.prototype.clone = function(deep, falseAray) {
    var type = Object.prototype.toString.call(this).match(/^\[object (.+?)\]$/)[1];
    var test = this;
    if (!type == "Object") {
        return this.valueOf;
    }
    /*var falseArray;
    if(check) {
    falseArray = (Object.keys(this).length - this.length);
    }*/ //takes around 400ms additional at 500k array to check if it has additional properties, just pass it as argument
    var clone = {};
    if (!deep) {
        for (var prop in this) {
            clone[prop] = this[prop]
        }
    } else {
        for (var prop in this) {
            if ( typeof this[prop] !== "undefined" && this[prop] !== null)
                clone[prop] = ( typeof this[prop] !== "object" ? this[prop] : this[prop].clone(( typeof deep == "boolean" ? deep : (deep - 1))));
            else
                clone[prop] = "";
        }
    }
    return clone;
};
Object.defineProperty(Object.prototype, "clone", {
    enumerable : false
});

Array.prototype.clone = function(deep, falseArray) {
    var test = this;
    /*var falseArray;
    if(check) {
    falseArray = (Object.keys(this).length - this.length);
    }*/ //takes around 400ms additional at 500k array to check if it has additional properties, just pass it as argument
    var clone = [];
    if (!falseArray) {//For me around 15-20ms at [500k]
        if (!deep)
            clone = this.concat();
        else
            this.forEach(function(e) {
                if ( typeof e !== "undefined" && e !== null)
                    clone.push(( typeof e !== "object" ? e : e.clone(( typeof deep == "boolean" ? deep : (deep - 1)))));
                else
                    clone.push("");
            });
    } else {// Variable is an 'Array' but has an extra propertie e.g: var arr = [1,2,3]; arr.a = "b" //its the slowest possibility but normally Objects would be used

        for (var prop in this ) {//around 630 - 700ms
            clone[prop] = this[prop];
        }
    }
    return clone;
};
Object.defineProperty(Array.prototype, "clone", {
    enumerable : false
});

JS Bin example
jsPerf

I tested it in Titanium where some code creates tables from JSON Objects (1000 rows with 29 properties per row (which contain array objects and primitive and null Values). The clone method copies the JSON object.

The first needed:

1: 400ms , 2: 361ms , 3: 314ms , 4: 317ms , 5: 318ms

The second needed:

1: 294ms , 2: 329ms , 3: 298ms , 4: 298ms , 5: 299ms

(Complete execution time, not only the copying)

I tried to change this:

(typeof deep == "boolean" ? deep : (deep - 1))

to

(deep - 1)

and call the method with x.clone(Infinity)instead of x.clone(true) to save one if clause per level of depth, but it turned out the execution speed varies from 300 to 370ms. Why is subtracting from infinity that slow/unstable compared to a type check through + a if clause? (its type is a number, so I thought it might do simply nothing if subtracting/adding etc sth to infinity).

It seems this is a problem for Titanium, benchmarks on Jsperf are around the same for both. I also tested both Codes with an 2k array where each entry contains arrays to a depth of 500. But the speed of both is the same. Is the cause this the branch prediction?

\$\endgroup\$
  • \$\begingroup\$ Why are you extending Object.prototype? \$\endgroup\$ – Bill Barry Jul 12 '12 at 22:47
  • \$\begingroup\$ to be able to invoke the method to clone directly from any Object like var y = x.clone() \$\endgroup\$ – C5H8NNaO4 Jul 12 '12 at 23:38
1
\$\begingroup\$

One thing I noticed was that you are using advanced (ECMAScript 5) parts of JavaScript - this will not work in old browsers, such as IE8 (I don't know where you are planning on using this).

\$\endgroup\$
  • \$\begingroup\$ I would mainly use it in titanium (if not only) therefore i don't have to worry about browser compatibility but it would be nice to get some more speed out of it, when processing big json Objects (and of course i would be happy to learn javascript a little better ) \$\endgroup\$ – C5H8NNaO4 Jul 17 '12 at 17:53
1
\$\begingroup\$

When you say "big JSON objects", do you mean a string containing JSON?

If I were you, I'd compare your method with serializing to a JSON string and deserializing from that string (that's cloning, too). As it is probably implemented in native code and directly supported by the VM, it should be really fast. I expect it to beat anything you can write - and it's way more compact, too. But I didn't test it, YMMV. Just consider and benchmark it.

\$\endgroup\$
  • \$\begingroup\$ i tried it right at the beginning when i wrote this ,and serializing/deserializing was way slower for me ( chrome ) \$\endgroup\$ – C5H8NNaO4 Aug 20 '12 at 14:34
  • \$\begingroup\$ Did you use the native JSON object or did you import a JS-version? \$\endgroup\$ – Arne Oct 2 '12 at 7:16
  • \$\begingroup\$ jsperf.com/deep-clone-test/3 for me its 0.58 (clone method) vs 0.26 Ops/sec (serializing/deserializing) with JSON.parse/stringify \$\endgroup\$ – C5H8NNaO4 Oct 9 '12 at 9:53
  • \$\begingroup\$ Ok, it was worth a shot. Sorry :-) \$\endgroup\$ – Arne Oct 10 '12 at 7:43
  • \$\begingroup\$ that sure is right, Thx anyway for the suggestion:) \$\endgroup\$ – C5H8NNaO4 Oct 10 '12 at 15:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.