Keith Devens .com |
Friday, July 4, 2008 | ![]() |
| You can have premature generalization as well as premature optimization. – Bjarne Stroustrup | ||
|
| ← FIREBUG IS SO AWESOME | Smurfs - GNAP! → |

Nathan wrote:
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;
}
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);
ernie ofori (http://www.aiti-kace.com.gh) wrote:
Thank you guys, was very handy for me.
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.
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 (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.
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: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?
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.
Keith (http://keithdevens.com/) wrote:
Sweet thanks, I'll try that code out when I get a minute.
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 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 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);
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
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;
}
}
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);
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]
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 ) );
}
Feel free to post a comment below. Please see my comment policy.
Formatting Rules (No HTML):
Generated in about 0.129s.
(Used 8 db queries)

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