KBD

Keith Devens .com

Friday, July 4, 2008 Flag waving
You can have premature generalization as well as premature optimization. – Bjarne Stroustrup
← FIREBUG IS SO AWESOMESmurfs - GNAP! →

Daily link icon Thursday, June 7, 2007

Javascript clone function

Because I'm sure it'll come in handy later, here's a javascript object clone function:

function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;

    var temp = {};
    for(var key in obj)
        temp[key] = clone(obj[key]);
    return temp;
}

Update: From feedback in the comments, a better version is:

function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;

    var temp = new obj.constructor(); // changed (twice)
    for(var key in obj)
        temp[key] = clone(obj[key]);

    return temp;
}
← FIREBUG IS SO AWESOMESmurfs - GNAP! →

Comments XML gif

Nathan wrote:

Very useful function, Thanks. I does not clone arrays so I tweaked it to do so...

function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;
    if(obj.constructor == Array)
        return [].concat(obj);
    var temp = {};
    for(var key in obj)
        temp[key] = clone(obj[key]);
    return temp;
}
∴ Nathan | 6-Sep-2007 1:24pm est | #10286

Brian McAllister (http://www.frequency-decoder.com/) wrote:

Actually, if the array items are themselves objects then you need to re-tweak the function yet again as the .concat will shallow clone the array items:


function clone(obj){
        if(obj == null || typeof(obj) != 'object') return obj;

        if(obj.constructor == Array) {
                var temp = [];
                for(var i = 0; i < obj.length; i++) {
                        if(typeof(obj[i]) == 'object')   temp.push(clone(obj[i]));
                        else temp.push(obj[i]);
                }
                return temp;
        }

        var temp = {};
        for(var key in obj) temp[key] = clone(obj[key]);
        return temp;
}

∴ Brian McAllister | 13-Sep-2007 3:06am est | http://www.frequency-decoder.com/ | #10299

Shawn Regan wrote:

Nice but the function breaks (or recurses indefintely) for any self referential object:

function animal(legs, color) {
this.legs=legs;
this.color=color;
this.me=this;
}

var a = new animal("four","brown");

var d = clone(a);

∴ Shawn Regan | 21-Nov-2007 10:46am est | #10393

ernie ofori (http://www.aiti-kace.com.gh) wrote:

Thank you guys, was very handy for me.

∴ ernie ofori | 26-Nov-2007 1:19pm est | http://www.aiti-kace.com.gh | #10404

Glenn wrote:

Actually it should be pretty easy to handle self references

just adjust the following

...
var temp = {};
for(var key in obj) temp[key] = clone(obj[key]);
return temp;

to

...
var temp = {};
for (var key in obj) {
if (obj !== obj[key]) {
temp[key] = clone(obj[key]);
} else {
temp[key] = temp;
}
}
return temp;

By checking to make sure obj does not equal obj[key] you can identify a self reference and then instead create temp[key] to reference the new object temp.

∴ Glenn | 27-Nov-2007 2:07pm est | #10408

Keith (http://keithdevens.com/) wrote:

By checking to make sure obj does not equal obj[key] you can identify a self reference and then instead create temp[key] to reference the new object temp.

Sure but that would only handle one level of self-references.

Keith | 27-Nov-2007 3:26pm est | http://keithdevens.com/ | #10409

Keith (http://keithdevens.com/) wrote:

Also, Nathan and Brian, thanks for your suggestions... I finally took a few minutes to go over them.

As Brian points out:

    return [].concat(obj);

doesn't share the behavior of:

for(var key in obj)
    temp[key] = clone(obj[key]);

because the former doesn't copy clone the elements.

Your changes are overcomplicated though. Only took a one-line change. Since the .constructor property is a method (didn't even know about .constructor until now, so thanks) you can just call it to get the right kind of object, and the loop works the same for both arrays and objects.

Here's the updated function:

function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;

    var temp = obj.constructor(); // changed
    for(var key in obj)
        temp[key] = clone(obj[key]);
    return temp;
}

Edit: fixed a couple errors in my post.

Keith | 27-Nov-2007 4:21pm est | http://keithdevens.com/ | #10410

Marc wrote:

I think the following line is erroneous:

var temp = obj.constructor();
and should be changed to:var temp = new obj.constructor();
for two reasons:
  • calling the constructor modifies the original object;
  • the constructor doesn't return an object.
∴ Marc | 17-Dec-2007 9:29am est | #10444

Keith (http://keithdevens.com/) wrote:

* calling the constructor modifies the original object;

Not sure where you're getting this idea.

* the constructor doesn't return an object.

Yes it does. Did you even try it before posting?

Keith | 17-Dec-2007 10:46am est | http://keithdevens.com/ | #10445

Marc wrote:

Try the following example:

function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;
    var temp = obj.constructor(); // breaks
    for(var key in obj)
        temp[key] = clone(obj[key]);
    return temp;
}

function car(make, model, year) {
   this.make = make;
   this.model = model;
   this.year = year;
}

mycar = new car("Eagle", "Talon TSi", 1993);

hiscar = clone(mycar);

print(hiscar.make);
print(hiscar.model);
print(hiscar.year);
which fails in Spidermonkey.

The car example is taken from the Mozilla developer website.

∴ Marc | 18-Dec-2007 10:58am est | #10451

Keith (http://keithdevens.com/) wrote:

Sweet thanks, I'll try that code out when I get a minute.

Keith | 18-Dec-2007 11:02am est | http://keithdevens.com/ | #10452

Pat Kelley wrote:

Marc is correct about the constructor comment - the only versions that I could get to return an object were

new this.constructor() 
// or 
this.constructor(this)

Granted this may be more a comment on my coding ability, but...

∴ Pat Kelley | 26-Dec-2007 4:27pm est | #10461

Pat Kelley wrote:

Heh - found out something: the constructor with argument of this returned as expected in all cases - except on Boolean where apparently the constructor returns an evaluation of any argument determining whether it is a Boolean or not as a Boolean primitive or object, so it returned true for an initial Boolean primitive of false. Interesting what you find out when you mess around with objects.

So it looks like new this.constructor() is the way to go...

∴ Pat Kelley | 26-Dec-2007 4:45pm est | #10462

Pat Kelley wrote:

Okay, maybe I'm going insane. This method works for just about anything - Number primitives, string objects and primitives, whatever...but then it gets to Boolean - and depending on whether or not this is passed as an argument, it returns true or false regardless of the value of the Boolean. Any ideas?

Object.prototype.clone = function(){
if( this === null || typeof(this) !== "object" ){
    return this;
}
var returnObj = new this.constructor(this);
  for(var prop in this){
    if(this !== this[prop]){
    returnObj[prop] = this[prop].clone();
        }else{
    returnObj[prop] = this[prop];    
    }
  }
  return returnObj;
}

var myString = false;
var myNewString = myString.clone();

alert(myString + ":" + myNewString);
∴ Pat Kelley | 26-Dec-2007 5:05pm est | #10463

Marc wrote:

It seems that object cloning in JavaScript is less trivial than might appear at first glance.

Concerning Arrays and the for...in statement, consider the following warning issued by Mozilla Developer Center.

Has anybody considered cloning functions?

Regards,
Marc

∴ Marc | 27-Dec-2007 5:32am est | #10467

Brendon (http://aphexcreations.net) wrote:

This is the cleanest way to handle every situation:


function clone_array(arr) {
 var i, _i, temp;
    if( typeof arr !== 'object' ) {
     return arr;
    }
    else {
        if(arr.concat) {
         temp = [];
            for(i = 0, _i = arr.length; i < _i; i++) {
             temp[i] = arguments.callee(arr[i]);
            }
        }
        else {
         temp = {};
            for(i in arr) {
             temp[i] = arguments.callee(arr[i]);
            }        
        }
     return temp;
    }
}

∴ Brendon | 30-Dec-2007 11:30pm est | http://aphexcreations.net | #10472

Diego (http://www.fyneworks.com) wrote:

I was looking for an object cloning function and this post was very useful at first, but as I read through the comments I realized the function was evolving closer and closer to the jQuery extend function. (http://www.jquery.com)

I think it's a very comprehensive solution that handles anything (evens the issue with Booleans mentioned above).

The jQuery.extend doesn't actually clone, but it updates and returns the first argument. However cloning can be achieved like this:
var oldObj = { stuff:1, bool:true, arr:['a','b','c']};
var newObj = jQuery.extend({}/* clone */, oldObj);

∴ Diego | 13-Feb-2008 7:31am est | http://www.fyneworks.com | #10524

Jake wrote:


var parent = { name : "jake", age : "26"};

var joinedToParent = parent;

//changing joinedToParent, will alter parent as
//they both point to same object in memory

joinedToParent.name = "Mark";

print(parent.name);

//Now to clone an object, so it does not point
//to the same object we can do the following
var independant = clone(parent);

independant.name = "Fred";

print(independant.name);
print(parent.name);

function clone(object){
  function emptyConstructor() {}
  emptyConstructor.prototype = object;
  return new emptyConstructor();
}

[Edited code comments so as not to brake layout - Keith]

∴ Jake | 8-May-2008 10:05am est | #10669

Antonio wrote:

This is a clever way to clone an object if you are using YAHOO UI. Same technique can be used with other libraries:

function clone(object) {
  return YAHOO.lang.JSON.parse( YAHOO.lang.JSON.stringify( obj ) );
}
∴ Antonio | 1-Jul-2008 12:47pm est | #10723

Feel free to post a comment below. Please see my comment policy.

Formatting Rules (No HTML):

  • **bold**, *italic*, _underlined_, --strikeout--
  • "text"="url" creates a link, and URLs are auto-highlighted
  • Blockquote: Like e-mail, begin paragraph with > (greater-than sign)
  • Lists: begin paragraph with *,-, or + (unordered), or # (ordered)
  • Code block: ?!code:language=perl|php|sql|javascript|etc.{\n}...{\n}?!/code

:
(will be your IP address if blank)
: (optional)
(Will not be shown on site)

: (optional)
:

July 2008
SunMonTueWedThuFriSat
 12345
6789101112
13141516171819
20212223242526
2728293031 



RSS feed RSS feed for Keith's Weblog
Atom feed Atom feed for Keith's Weblog
Weblog archive
Recent comments
  on 5 posts

Recent comments XML

Girls, please don't get breast implants

> And no, you will not be receiving​a picture.

:-(...

Keith: Jul 2, 6:05am

Javascript clone function

This is a clever way to clone an​object if you are using YAHOO UI.​Same tec...

Antonio: Jul 1, 12:47pm

I hate Norton Antivirus

Oh just one other thing norton is​great at keeping people out of your​compu...

kevin.sands: Jul 1, 12:50am

Terminator 3 was awful

I think the biggest reason why T3​totally blew was because Edward​Furlong g...

76.167.172.64: Jun 29, 3:06am

Generated in about 0.129s.

(Used 8 db queries)

mobile phone