views:

556

answers:

3

Hi, I tried to exactly clone an object in javascript. I know the following solution using jquery:

var newObject = jQuery.extend({}, oldObject);
// Or
var newObject = jQuery.extend(true, {}, oldObject);

but the problem with that is, that the objects type gets lost:

var MyClass = function(param1, param2) {
    alert(param1.a + param2.a);
};
var myObj = new MyClass({a: 1},{a: 2});
var myObjClone = jQuery.extend(true, {}, myObj);
alert(myObj instanceof MyClass);      // => true
alert(myObjClone instanceof MyClass); // => false

Is there any solution to get true on the second alert?

+3  A: 

Have you considered using the clone function suggested here?

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

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

var MyClass = function(param1, param2) {};
var myObj = new MyClass(1,2);
var myObjClone = clone(myObj);
alert(myObj instanceof MyClass);      // => true
alert(myObjClone instanceof MyClass); // => true
Yacoby
That's a good start, but the clone fails if the constructor requires parameters: var MyClass = function(param1, param2) {alert(param1.test)};
Tom
Tom, where do you expect this `clone` function to get the expected arguments from? How is it to know?
J-P
@J-P - That's what my question is about. Is there a way to get an exact clone preserving the type information without knowing anything about the object to clone. I now assume it's impossible.
Tom
+4  A: 

jQuery.extend is not expecting you to use the instanceof operator. It is doing a gloriously complicated copy, and not a true clone. Looping through the elements is not enough. Also, calling the constructor isn't best cause you'll loose your arguments. Try this:

var MyClass = function(param1, param2) {
    alert(param1.a + param2.a);
    this.p1 = param1;
    this.p2 = param2;
};

function Clone() { }
function clone(obj) {
    Clone.prototype = obj;
    return new Clone();
}

var myObj = new MyClass({a: 1},{a: 2});
var myObjClone = clone(myObj);
alert(myObj instanceof MyClass);      // => true
alert(myObjClone instanceof MyClass); // => true
console.log(myObj);       //note they are
console.log(myObjClone)   //exactly the same

Be aware that since your prototype now points back to the origional (myObj), any changes to myObj will reflect in myObjClone. Javascript's prototypal inheritance is kinda tricky. You need to be sure that your new object has the correct prototype, and hence the correct constructor.

Admitadly, Javascript makes my head hurt. Still, I think I'm reading this right, from the ECMAScript language spec:

13.2.2 [[Construct]]
When the [[Construct]] internal method for a Function object F is called with a possibly empty list of arguments, the following steps are taken:

  1. Let obj be a newly created native ECMAScript object.
  2. Set all the internal methods of obj as specified in 8.12.
  3. Set the [[Class]] internal property of obj to "Object".
  4. Set the [[Extensible]] internal property of obj to true.
  5. Let proto be the value of calling the [[Get]] internal property of F with argument >"prototype".
  6. If Type(proto) is Object, set the [[Prototype]] internal property of obj to proto.
  7. If Type(proto) is not Object, set the [[Prototype]] internal property of obj to the >standard built-in Object prototype object as described in 15.2.4.
  8. Let result be the result of calling the [[Call]] internal property of F, providing >obj as the this value and providing the argument list passed into [[Construct]] as args.
  9. If Type(result) is Object then return result.
  10. Return obj.

This person seems to understand the concept much better than I do. K, I'm going back to java now where I swim more than I sink :) .

Stephano
Thas's a very nice solution. Additionaly you could copy all properties of the clones prototype to the clone so changes to myObj will not reflect in myObjClone. But what happens if you call clone again on a different object, will the prototype of myObjClone be changed?
Tom
If you try "myNewObjClone = clone(myObj)", then no, you don't change the prototype of myObjClone. The Clone object only lives inside the clone function, so you get a new one each time you call clone(obj). That is an example of using closure to "hide" a variable, in this case, the object Clone.
Stephano
+1  A: 
function clone( obj ) {  
    var target = new obj.constructor();  
    for ( var key in target ) { delete target[key]; }  
    return $.extend( true, target, obj );  
}  

$.extend can't copy all the internal properties that are not visible (some are visible in firefox), but if obj.constructor is correct, and won't fault without args, the internal properties can be set with new obj.constructor(). If you do inheritance with something like Derived.prototype = new Base(), you also would need to follow that with Derived.prototype.constructor = Derived to get the constructor right.

You could do $.extend( true, new obj.constructor(), obj ), but it's possible the constructor creates properties that were later deleted -- even if you could get the constructor args right -- that's why the properties have to be deleted before doing the extend. It doesn't matter that the constructor args are wrong since the effects of the original constructor args -- plus everything else that has happened to the object since then -- are in the object we're cloning.

randrc