KBD

Keith Devens .com

Friday, September 3, 2010 Flag waving
... be conservative in what you do, be liberal in what you accept from others. – Jon Postel (RFC 793)
← 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

Kosta wrote:

I believe the following code should take care of the infinite recursion problem with self-referencing.

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

  var copied = copied || {};
  var temp = new obj.constructor(); // changed (twice)
  copied[obj] = temp;
  for(var key in obj) {
    if (copied[obj[key]])
      temp[key] = copied[obj[key]];
    else {
      temp[key] = clone(obj[key], copied);
      copied[obj[key]] = temp[key];
    }
  }

  return temp;
}

I tested it with the following piece of code, but I believe it should also work for arbitrary nesting

function infinite_loop() {
  var loopy = {};
  loopy.loopy = loopy;
  var copy = clone(loopy);
  document.write("done");
}
∴ Kosta | 23-Jan-2009 2:21pm est | #11048

Kosta wrote:

please disregard my previous comment, javascript objects cannot have different objects as objects, os above code is total bogus

∴ Kosta | 23-Jan-2009 4:28pm est | #11049

Josh wrote:

@Antonio

THANK YOU SO MUCH! I have been searching for almost an hour for a a catch-all solution for YUI. Plenty of info out there for jQuery/Mootools/ExtJS, but this is the first thing I've come across for YUI.

To top it all off, I was already using the YUI connection manager and JSON plugin.

Thanks again, this is working great for my purposes, however my cloned objects do not contain functions.

∴ Josh | 12-Feb-2009 1:38pm est | #11064

Richard Collette wrote:

Thank you! This was extremely useful for me. I was decorating functions passed in a configuration object. Of course the next time the same configuration object was used by that method, the function in the configuration object was already decorated. I now clone the configuration object and can modify it as needed.

∴ Richard Collette | 1-May-2009 3:53pm est | #11137

59.93.86.40 wrote:

hi,

does this clone work to clone a select object of a document. i.e.. clone(document.getElementById('select_obj_id')); and clone(selectEle.options);?

∴ 59.93.86.40 | 8-Jun-2009 10:00pm est | #11184

anonymous wrote:

http://forum.dklab.ru/viewtopic.php?t=12678

here is discussion (in Russian) with different versions of the subject.

∴ anonymous | 8-Jul-2009 10:22am est | #11207

bandoswuu qiye, Jr. (http://www.jj54.com/) wrote:

Thanks.
the code runs well under IE7 and my firefox 3.0 .

∴ bandoswuu qiye, Jr. | 9-Jul-2009 8:39pm est | http://www.jj54.com/ | #11213

Mark wrote:

Very interesting, but if the object constructor takes parameters you can't get a clone since you don't know what the parameters are.

∴ Mark | 20-Aug-2009 10:31pm est | #11356

Lau S. wrote:

Hi Keith,

Thanks for the good basis. Here's some changes that should works with any object, with or without parameters.

Object.prototype.clone = function clone (obj) {
    if(obj === null) return null;

    if(!obj) obj = this;

    if(typeof(obj) != 'object' && typeof(obj) != 'array') return obj;

    var temp = (typeof(obj) != 'array' && obj.constructor != Array) ? new obj.constructor(obj) : new obj.constructor(obj.length);

    for(var key in obj) {
        if (!obj.hasOwnProperty(key)) continue;
        temp[key] = (obj[key] === undefined)  ? undefined : clone(obj[key]);
    }

    return temp;
}

My two cents, two years later Smiley winking

∴ Lau S. | 15-Dec-2009 9:37am est | #11459

Lau S. wrote:

Oops...

Change two lines with this :

    if(typeof(obj) != 'object') return obj;

    var temp = (obj.constructor != Array) ? new obj.constructor(obj) : new obj.constructor(obj.length);
∴ Lau S. | 15-Dec-2009 3:25pm est | #11460

hcyu wrote:

>> function clone(obj){ if(obj == null || type...emp[key] = clone(obj[key]); return temp; }

undefined

>> a = new Object()

Object {}

>> a.a = a

Object { a=Object}

>> clone(a)

InternalError: too much recursion { message="too much recursion", more...}

Could fix with keep track array of callers, but that wont be very efficient...

∴ hcyu | 30-Mar-2010 8:34am est | #11504

cloner wrote:

If my reading of the code given in the post (1) is good, you could optimise it for copying arrays.

Say you have objects which have a lot of arrays:

var lot_of_arrays = {
    arr1: [1,2,3,4,5,6,7,8 ... 999],
    arr2: [1,2,3,4,5,6,7,8 ... 999],
    ...
    arr999: [1,2,3,4,5,6,7,8 .. 999]
}

The code (1) will loop in every array and copy every value. You could add this to the clone function:

if( typeof obj == 'Array' )
    return obj.slice(0); // see https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/Array/Slice
∴ cloner | 28-Apr-2010 8:22am est | #11529

Dan Beam (http://danbeam.org) wrote:

@cloner:

typeof [];            // "object", there is no "Array" type
[] instanceof Array;  // true
[] instanceof Object; // also true, be careful!

Here's my version:

var clone = function (orig, copy) {
    // terminal state
    if (/number|string|boolean|undefined/.test(typeof orig) || null === orig)
        return orig;

    if ("undefined" === typeof copy)
        copy = orig instanceof Array ? [] : {}; 

    for (var key in orig)
        if (orig !== orig[key])
            copy[key] = clone(orig[key]);

    return copy;
}

And here's a test you can run to check it:

var a = function(){},
    b = function(){},
    c = function(){};

// set up the prototype of the first class
a.prototype = { 
    "true"  : true,
    "false" : false,
    "int"   : 1,
    "float" : 3.14,
    "null"  : null,
    "undef" : {}["not there"],
    "obj"   : {"hello":"world"},
    "array" : ["hello","there","keith"]
};

// shallow inheritance
b.prototype = a.prototype;

// deep inheritance
c.prototype = clone(a.prototype);

// modify original & reference
b.prototype["true"] = "I WAS MODIFIED?!??!";

// modify clone
c.prototype["true"] = "I have truthiness!";

// verify results
console.log(a);
console.log(b);
console.log(c);

If you happen to be using jQuery, I'd recommend:

var copy = jQuery.extend(true, {}, someObject);

does about the same thing as mine does, Smiley (big smile)

∴ Dan Beam | 4-May-2010 5:11pm est | http://danbeam.org | #11551

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)
:

September 2010
SunMonTueWedThuFriSat
 1234
567891011
12131415161718
19202122232425
2627282930 



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

Recent comments XML

new⇒Call a function from a string in Python

or​use:
?!code:language=python
def​fce(arg1=None,arg2=None):
#some​usefu...

Richard: Sep 2, 1:08pm

new⇒Spider solitaire

Been playing 4S Spider for a couple​of years. Only recently did I start​to ...

jimibd: Sep 2, 3:16am

Generated in about 0.188s.

(Used 8 db queries)