Как да копирате дълбоко затварящи обекти (частни обекти) по време на дълбоко копиране

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

                ....
}

В друг код GameManager.js,

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

И за Deepcopying, който използвам,

Справка: 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;
    }

Моята нужда:
Искам cells (частен член на конструктора) в Board да бъде дълбоко копиран.

Проблем:
Но когато отстраних грешки с firebug, видях, че функцията deepCopy не копира дълбоко частни обекти на конструктора.

Моят случай:
board.moveCell(1,2), тук клетка [1][2] също е преместена в duplicateBoard.
Това означава, че не е извършено дълбоко копиране на cell И дъската, и дублираната дъска имат една и съща препратка към клетка[1][2].

Какво проследих? Функцията за дълбоко копиране третира constructor като функция, следователно игнорира дълбокото копиране на функциите, тъй като ще се провали в typeof o === 'object. Но премахването на това условие не е полезно, защото по този начин duplicateBoard няма функции, а всички функции ще бъдат от тип object{}.


person Muthu Ganapathy Nathan    schedule 26.05.2013    source източник
comment
Можете ли да използвате jQuery? Ако можете, може би бихте опитали copy = $.extend(true, {}, board);, но не знам дали наистина работи (не е тествано)   -  person Niccolò Campolungo    schedule 26.05.2013
comment
Правилното име за частното свойство е затваряне и те не се показват като свойства на екземпляр на обект, защото могат да се използват само в тялото на функцията. Как създавате екземпляр(и) на Board, връщате ли стойност или използвате новата ключова дума?   -  person HMR    schedule 26.05.2013
comment
@LightStyle Не мисля, че jQuery може да копира свойства, дефинирани в затварянето. Стойностите на затваряне не са част от екземпляра на обекта и дори в рамките на обекта не можете да ги наричате this.cells   -  person HMR    schedule 26.05.2013
comment
@HMR Използвах нова ключова дума. Като нова дъска; Освен това направих cells за затваряне, защото не може да бъде достъпен от никоя друга функция   -  person Muthu Ganapathy Nathan    schedule 26.05.2013
comment
@HMR да, нуждата ми е да поддържам cells да бъде в затворен (частен), но все пак да ги копира дълбоко.   -  person Muthu Ganapathy Nathan    schedule 26.05.2013
comment
@EAGER_STUDENT Съвсем сигурен съм, че не можете да копирате стойности за затваряне, може да помислите отново да го направите частен, ако искате да клонирате обекта.   -  person HMR    schedule 26.05.2013
comment
@HMR да, трябва да остане private, за да съм сигурен, че може да не бъде достъпен от други класове (АБСТРАКЦИЯ НА ДАННИ)   -  person Muthu Ganapathy Nathan    schedule 26.05.2013
comment
Много мръсно решение може да бъде създаването на this.clone във функцията Board (функциите трябва да бъдат посочени като прототип, защото не се променят за екземпляри). След това създайте нов обект в клонинг и го върнете.   -  person HMR    schedule 26.05.2013
comment
@HMR Клониране означава, че как да клонирам (дълбоко копирам) съществуващия обект в this.clone, там също ще възникне същия проблем. бихте ли предоставили примерен код.   -  person Muthu Ganapathy Nathan    schedule 26.05.2013
comment
интересната дилема как може външна функция да копира частните променливи на обект, ако не се предполага, че може да има достъп до тях на първо място :-)   -  person go-oleg    schedule 26.05.2013
comment
@user2359560 Имам достъп до личните членове, като имам deep-Copy във всяка функция, което помага за копирането на нейните членове.   -  person Muthu Ganapathy Nathan    schedule 26.05.2013
comment
@EAGER_STUDENT, какво ще кажете за нещо подобно?   -  person Alexander    schedule 27.05.2013
comment
Актуализирах кода си, той ще копира свойствата и техните стойности, включително променливите за затваряне. Разбива прототипа на клонирани екземпляри, но тъй като не можете да използвате прототип (нямате достъп до затварящи променливи в прототипа), няма да има значение.   -  person HMR    schedule 27.05.2013
comment
@Alexander отлично, единственото нещо, което остава да направите, е да копирате други стойности във функцията за клониране, тъй като в момента тя задава всички други стойности на стойностите на конструктора по подразбиране. И не копирайте стойностите на функцията, защото това ще обърка препратката за затваряне. Добавих го в отговора си.   -  person HMR    schedule 27.05.2013


Отговори (3)


Това не е добро решение, тъй като всички функции, които имат достъп до вашата "частна" променлива на клетките, трябва да бъдат декларирани като this.someFunction вместо Board.prototype, така че всеки екземпляр на Board ще има свои собствени функции, вместо да ги споделя.

Ето примерен код, който би нарушил прототипа (c instanceof b не е вярно), но тъй като не можете да използвате прототип, защото трябва да получите достъп до затварящи променливи във вашите функции, това няма да има значение.

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());

[АКТУАЛИЗАЦИЯ]

Защо това е лош дизайн е, защото не можете да декларирате вашите обектни методи като прототип:

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

Това ви принуждава да декларирате всичките си методи в тялото на теста със синтаксис "this.someFunction". Ако създадете няколко тестови екземпляра, тогава всеки екземпляр има свой собствен набор от методи, които правят абсолютно същото. За да спестите ресурси, трябва да използвате прототип, но тогава нямате достъп до променливите за затваряне в тези функции, така че не можете. Моля, прочетете това относно основите на прототипа: Наследяване на прототип - писане

Може би, ако имате само няколко екземпляра, няма да има значение, но технически не можете да клонирате тези обекти. Истински клонинг на b в горния код би бил typeof Test, но в кода по-горе клонираният екземпляр на "b", наречен "c", не е typeof Test и няма начин да видя настройката му, без да наруша новонастроената затваряща променлива, наречена "съобщение".

person HMR    schedule 26.05.2013
comment
Как var cells=cells помага при дълбокото копиране? Освен това къде се извършва дълбокото копиране? Тъй като Cells на свой ред ще съдържа Position, така че в моя случай е необходима рекурсия. извинете, че ви безпокоя отново. - person Muthu Ganapathy Nathan; 26.05.2013
comment
Задаване на препратка към затваряне на клетки от текущото копие на Board this, така че екземплярът на Board ret, който ще бъде върнат, има препратка към него. Не съм сигурен дали ще работи и не бих запазил клетките частни. Доколкото знам, използването на затваряне ви пречи да използвате прототип за разширяване на вашия обект, тъй като затварянето/частните не са достъпни в декларирани свойства на прототип. Ето основно обяснение за прототипа: stackoverflow.com/ въпроси/16063394/ - person HMR; 26.05.2013
comment
@EAGER_STUDENT съжалявам, но мисля, че най-доброто решение би било да не се използват частни стойности за затваряне на обекти като този. google.closure е набор от инструменти, които изграждат приложения като gmail, можете да имате частни свойства в обекти, като ги декларирате с коментари, така че компилаторът да се оплаква, но те всъщност не се изпълняват като частни в JS, защото това би ви принудило да правите лоши програмни проекти (не можете да използвате prototype, за да декларирате функционални свойства, които използват променливата private/closure клетки). - person HMR; 26.05.2013
comment
Така че cell да бъде частна променлива е лош дизайн?. Или Deep-copying затварянето е невъзможно? - person Muthu Ganapathy Nathan; 26.05.2013
comment
Само бележка, че клетката е частна за конструктора на Board и дори не може да бъде препратена от функции, дефинирани в прототипа на Board. - person go-oleg; 26.05.2013
comment
@EAGER_STUDENT Актуализиран код, можете да опитате нещо подобно, но ако планирате да използвате много екземпляри, моля, обмислете отново използването на частни променливи, тъй като те са скъпи като ресурси. - person HMR; 27.05.2013
comment
Това е доста добра идея, добавяйки функцията за клониране като вътрешен метод. Той ще има достъп до личните стойности. +1 - person Michael Coxon; 27.05.2013
comment
Благодаря ти, Майкъл, Александър излезе със същата функция за клониране във цигулка. Той се грижи за копирането на променливите за затваряне. След това всички останали публични членове трябва да бъдат копирани, с изключение на функциите, защото това го прави свободен, неговата новокопирана променлива за затваряне. - person HMR; 27.05.2013

Това не може да се направи, тъй като променливата "private" е локална за функцията (конструктора). С начина, по който работи JS, дори чрез клониране на функциите пак ще получите указател от оригиналния обект (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;  
}

ако не клонирате функции, тогава получавате чисто нов набор от частни променливи с всички клонирани публични променливи (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;  
}

Най-добрият начин да се справите с това е да направите вашите частни променливи публично достъпни, но да им дадете различна конвенция за именуване, като например „_myPrivateVariable“. По този начин променливите ще бъдат клонирани и всеки друг, който използва вашия клас, ще знае, че това е частна променлива.

Така че във вашия случай би било:

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

                ....
}

За справка проверете тук: Копиране на javascript обект с частен член

person Michael Coxon    schedule 26.05.2013
comment
Това няма да копира затварящи стойности, ако клетките в o са били променени между създаването на o и извикването на deepCopy, промяната не се отразява в върнатия нов обект. - person HMR; 26.05.2013
comment
Опитахте ли да промените стойността на msg между създаването на o и клонирането му? нека добавим функция this.changeMsg(newMsg){msg=newMsg); обадете се на това и след това го клонирайте. Добавете функция this.sayMsg и проверете o и клонинга, сигурен съм, че няма да са същите. - person HMR; 26.05.2013
comment
@EAGER_STUDENT аххх виждам какво се случва там.. указателят на функцията се копира. Моля, вижте jsfiddle.net/kBzQP/2 за мястото, където съм променил функцията, за да не копира върху ново дефинирани функционални указатели. - person Michael Coxon; 26.05.2013
comment
@EAGER_STUDENT, моля, не обръщайте внимание на горното ми съобщение, това не работи във всички случаи - person Michael Coxon; 26.05.2013
comment
@MichaelCoxon Съгласен съм да не използвам private/closures, особено ако планирате да създадете много екземпляри. Но ако счупите прототипа на клонинга, можете да копирате и променливите private/closure, вижте моя актуализиран отговор. Все пак не съм сигурен дали има основателна причина да направите това. Клонингът технически не е клонинг, защото прототипът не се копира, а само свойства и техните стойности. - person HMR; 27.05.2013

Използвайте $.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

По същия начин трябва да го използвате за вашата дъска:

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

Обикновено се опитвам да избягвам използването на jQuery, но в този случай изглежда перфектно...

person Niccolò Campolungo    schedule 26.05.2013
comment
Мисля, че това няма да копира затварянето на клетките в елемента на дъската и ако го направи, ще копира промените в клетките, ако клетките са били променени между създаването на дъска и нейното клониране. - person HMR; 26.05.2013
comment
Вижте новия Fiddle. Той променя променливата за затваряне и я записва дори в клонирания обект. Работи перфектно. @EAGER_STUDENT вижте! - person Niccolò Campolungo; 26.05.2013
comment
Клонираният обект и върнатият обект споделят един и същ msg, опитайте се да промените msg на масив и функция Message, за да избута подадената променлива в msg, след това я клонирайте и извикайте Message на един от обектите, за да накарате нова стойност в масива msg . Ще видите, че другият има същия елемент, въведен в съобщение. @LightStyle - person HMR; 27.05.2013