views:

57

answers:

4

Hi,

I've looked all around and found nothing to help me. Why on earth can't I clone a javascript object with private members without making them quantum entangled?

Just look at this code... It's a plain private property with getter and setter. Somehow if I call the public setter on one instance, the cloned one gets changed too. Why? Can it be worked around?

obj = function(){
    var changed = 0;
    this.getChanged = function(){
        return changed;
    }
    this.setChanged = function(){
        changed = 1;
    }
    this.setUnchanged = function(){
        changed = 0;
    }
};

myObj = new obj();
copiedObj = $.extend(true, {}, myObj); // Or any other deep copy function you'd have around

myObj.setChanged();
myObj.getChanged(); // returns 1
copiedObj.getChanged(); // returns 1!
copiedObj.setUnchanged();
copiedObj.getChanged(); // returns 0
myObj.getChanged(); // returns 0

Thanks for any ideas.

Edit: So far, nothing new. I know javascript doesn't really have OO like Java or C++, but hey we're talking about programming languages, there is always one way out. Sometimes it's ugly but there is one.

I get it. Solution A: just make it this.changed instead of var changed
Solution B: make my own clone function that rebuilds the whole object anew

I was just hoping there would be some Solution C that would trick javascript into standard Object Oriented patterns.

Somebody, am I really stuck with A or B?

A: 

Local variables in functions that are used as constructors are called "private members" so Java masses can understand the intention of the usage. It doesn't work like a property. It is a local variable in a lexical scope. It is "private" to the instance, not the class. There are no classes in javascript.

Your best bet is adding a clone method that will deep copy the instance.

artificialidiot
A: 

Even a deep copy can't copy a function as anything but a reference. And since your functions are closures, they all share the same private members. Why not write a clone() method?

gilly3
+2  A: 

The problem is that changed isn't a private variable-- JavaScript doesn't have private variables. It's a local variable to the function you assign to the obj variable. When you create the functions that get assigned to the getChanged / setChanged / setUnchanged properties, you're creating closures that are closed over the variable changed.

When you clone myObj, you're just creating additional aliases to those functions. So, you're still calling the same functions regardless of whether you access them through myObj or copiedObj, and because they're closures, you're accessing exactly the same changed variable in both cases. Since you can't copy functions, you're better off not trying to make changed private and just doing this.changed = 0; on the second line.

Paul Baumgart
A: 

Got the answer.

gilly3's hint helped.

The clone/deep-copy function I was using in fact was not jQuery's but the one A. Levy posted on a similar question.

Had to tweak it in two ways to work as I expected. In fact the original author had pointed out the way in his explanation but I didn't catch it at the time I first read it.
First I instantiated the copy with the original object's constructor and
Filtered out functions from the for in loop.

Here's the result. Hope that helps someone.

function clone(obj) {
    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; ++i) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object (functions are skipped)
    if (obj instanceof Object) {
        var copy = new obj.constructor();
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr) && !(obj[attr] instanceof Function)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}
Tacio Medeiros
Just keep in mind that you're not actually cloning the value of the **changed** variable using this method. If that's a requirement, you'll need a clone method specific to your obj "class" that uses **getChanged** / **setChanged** to transfer the value.
Paul Baumgart
Yes. In my case the changed variable is specific to the instance and has a default in the constructor. I don't want that to be copied, so that's ok. Thanks.
Tacio Medeiros