Muthu Ganapathy Nathan
Muthu Ganapathy Nathan

Reputation: 3307

How to deep-copy closure objects (private objects) while Deep Copying

    Board = function()
    {
        var  cells = [8];


        /**
         * Initializing every cell using numeric format.
         * */
        for (var i=0 ; i<8; i++){
            cells[i] = [8];
            for (var j=0 ; j<8; j++)
                cells[i][j] = new Cell(new Position(i,j));
        }

                ....
}

In Another code GameManager.js,

var duplicateBoard = Copy.deepCopy(board);
board.moveCell(1,2)

And for Deepcopying I am using,

Ref : http://jsperf.com/deep-copy-vs-json-stringify-json-parse

 function deepCopy(o) {
        var copy = o,k;

        if (o && typeof o === 'object') {
            copy = Object.prototype.toString.call(o) === '[object Array]' ? [] : {};
            for (k in o) {
                copy[k] = deepCopy(o[k]);
            }
        }

        return copy;
    }

My need :
I want cells (private member of constructor ) in Board to be deep-copied.

Problem :
But, When I debugged with firebug, I saw, deepCopy function does not deep copying private objects of constructor.

My Case :
board.moveCell(1,2), Here cell[1][2] is moved in duplicateBoard too.
That is, No deep-copying of cell has taken place Both the board and duplicateBoard has same reference to cell[1][2].

What I have traced ? The deep-copy function, treats the constructor to be a function, hence it ignores deep-copying the functions, since it will fail in typeof o === 'object. But removing this condition is not useful, because by doing so, duplicateBoard has no functions rather all the functions to be object{} type.

Upvotes: 2

Views: 3740

Answers (3)

HMR
HMR

Reputation: 39270

It's not a good solution as all functions accessing your "private" cells variable have to declared as this.someFunction instead of Board.prototype so each Board instance will have their own funcions instead of sharing them.

Here is some sample code that would break prototype (c instanceof b is not true) but since you can't use prototype because you need to access closure variables in your functions that would not matter.

function Test(privates) { 
    var msg = [];
    if(privates!==undefined){
      msg=deepCopy(privates.msg,[]);
    }
    this.Message = function(newMsg) {
        if (newMsg) {
            msg.push(newMsg);
        } else {
            return msg;
        }
    }
    this.clone=function(){
      var orgMsg=msg
      var ret = function(){
        Test.call(this,{msg:orgMsg});
      }
      return deepCopy(this,new ret());
    }
}
// this does not set prototype correctly
function deepCopy(from,to) {
    if(from == null || typeof(from) != 'object') {
        return from;
    }
    for(var key in from) {
      // this.Message has closure ref to msg
      // you can't copy it because we've set a new
      // closure ref
      if(typeof from[key]!=="function"){
        to[key] = deepCopy(from[key]);
      }
    }
    return to;  
}

var b = new Test();
b.Message("Before cloning");
console.log("b message before cloning:",b.Message());
var c = b.clone();
console.log("c message after cloning:",c.Message());
b.Message("From BB after Clone");
console.log("c message after pushing new item in b:",c.Message());
c.Message("From CC after Clone");
console.log("b message after pushing new item in c:",b.Message());
console.log("c message after pushing new item in b:",c.Message());

[UPDATE]

Why this is a bad desing is because you can't declare your object methods as prototype:

Test.prototype.Message(){
 //here the msg variable doesn't exist
}

This forces you to declare all your methods in the Test body with "this.someFunction" syntax. If you create multiple Test instances than each instance has it's own set of methods doing the exact same thing. To save resources you should use prototype but then you can't access closure varibales in these funcitons so you can't. Please read this on prototype basics: Prototypical inheritance - writing up

Maybe if you only have a couple of instances it wouldn't matter but technically you can't clone these objects. A real clone of b in the above code would be typeof Test but in the code above cloned instance of "b" called "c" is not typeof Test and there is no way I can see setting it without breaking the newly set closure variable called "msg".

Upvotes: 1

Niccol&#242; Campolungo
Niccol&#242; Campolungo

Reputation: 12042

Use $.extend():

var testObj = function() {
    var rand = Math.random(0, 1);
    this.r = function() { 
        return rand; 
    };
    this.changeRand = function() {
        rand = Math.random(0, 1);
    };
};
var obj1 = new testObj();
alert(obj1.r());
obj1.changeRand();
alert(obj1.r());
var obj2 = $.extend(true, {}, obj1);
alert(obj2.r());
alert(obj1.r() === obj2.r()); // true

JSFiddle

In the same way you should use it for your board:

var Board = function() {
        var cells = [8];
        /**
         * Initializing every cell using numeric format.
         * */
        for (var i=0 ; i<8; i++){
            cells[i] = [8];
            for (var j=0 ; j<8; j++)
                cells[i][j] = new Cell(new Position(i,j));
        }
}
var board = new Board(),
    copy = $.extend(true, {}, board);

Normally I try to avoid using jQuery, but in this case it seems perfect...

Upvotes: 0

Michael Coxon
Michael Coxon

Reputation: 5510

This cannot be done as the "private" variable is local to the function(constructor). With the way that JS works, even by cloning the functions you will still get a pointer from the original object (http://jsfiddle.net/kBzQP/),

function deepCopy(o) {
    if(o == null || typeof(o) != 'object') {
        return o;
    }

    var newObj = new o.constructor();

    for(var key in o) {
        newObj[key] = deepCopy(o[key]);
    }

    return newObj;  
}

if you do not clone functions then you get a brand new set of private variables with all the public variables cloned (http://jsfiddle.net/kBzQP/3/).

function deepCopy(o) {

    if(o == null || typeof(o) != 'object') {
        return o;
    }

    var newObj = new o.constructor();

    for(var key in o) {
        if(typeof(o) != 'function') continue;
        newObj[key] = deepCopy(o[key]);
    }

    return newObj;  
}

The best way to handle this is to make your private variables publicly accessible but give them a different naming convention such as "_myPrivateVariable". This way the variables will be cloned and anyone else using your class will know that this is a private variable.

So in your case it would be:

Board = function()
    {
        this._cells = [8];


        /**
         * Initializing every cell using numeric format.
         * */
        for (var i=0 ; i<8; i++){
            this._cells[i] = [8];
            for (var j=0 ; j<8; j++)
                this._cells[i][j] = new Cell(new Position(i,j));
        }

                ....
}

For reference sake check here: Copy javascript object with private member

Upvotes: 2

Related Questions