Как глубоко копировать замыкающие объекты (приватные объекты) во время глубокого копирования

    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)

И для глубокого копирования я использую,

Ссылка: 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 не было. И доска, и дубликатBoard имеют одинаковую ссылку на ячейку[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
Правильное имя частного свойства — closure, и они не отображаются как свойства экземпляра объекта, поскольку их можно использовать только в теле функции. Как вы создаете экземпляр(ы) 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 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». Если вы создаете несколько экземпляров Test, каждый из них имеет собственный набор методов, выполняющих одно и то же. Чтобы сэкономить ресурсы, вы должны использовать прототип, но тогда вы не можете получить доступ к переменным закрытия в этих функциях, поэтому вы не можете. Пожалуйста, прочитайте это об основах прототипа: Прототипное наследование - запись

Может быть, если у вас есть только пара экземпляров, это не имеет значения, но технически вы не можете клонировать эти объекты. Настоящим клоном 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, потому что это заставит вас делать плохой программный дизайн. (вы не можете использовать прототип для объявления свойств функции, которые используют переменную частных/закрывающих ячеек). - 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/close, см. мой обновленный ответ. Не уверен, что есть веская причина для этого. Клон технически не является клоном, потому что копируется не прототип, а только свойства и их значения. - 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
См. новую скрипку. Он изменяет переменную замыкания и сохраняет ее даже в клонированном объекте. Это работает отлично. @EAGER_STUDENT посмотри! - person Niccolò Campolungo; 26.05.2013
comment
Клонированный объект и возвращенный объект используют одно и то же сообщение, попробуйте изменить сообщение на массив и функцию Message, чтобы поместить переданную переменную в сообщение, затем клонируйте его и вызовите Message для любого из объектов, чтобы поместить новое значение в массив сообщений. . Вы увидите, что у другого есть тот же элемент, вставленный в сообщение. @LightStyle - person HMR; 27.05.2013