Точно клонировать объект в JavaScript - PullRequest
20 голосов
/ 14 февраля 2010

Я пытался точно клонировать объект в JavaScript.Я знаю следующее решение с использованием jquery:

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

, но проблема в том, что тип объектов теряется:

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

Есть ли какое-либо решение, чтобы получить истину на второмпредупредит

Ответы [ 7 ]

12 голосов
/ 15 февраля 2010

jQuery.extend не ожидает, что вы будете использовать оператор instanceof. Он делает великолепно сложную копию, а не настоящий клон. Цикл по элементам не достаточно. Кроме того, вызов конструктора не лучший, потому что вы потеряете свои аргументы. Попробуйте это:

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

Имейте в виду, что, поскольку ваш прототип теперь указывает на оригинал (myObj), любые изменения в myObj будут отражаться в myObjClone. Наследование прототипов Javascript довольно сложно. Вы должны быть уверены, что ваш новый объект имеет правильный прототип и, следовательно, правильный конструктор.

По общему признанию, Javascript заставляет мою голову болеть. Тем не менее, я думаю, что я правильно читаю из спецификации языка ECMAScript :

13.2.2 [[Construct]]
Когда внутренний метод [[Construct]] для объекта Function F вызывается с возможно пустым списком аргументов, предпринимаются следующие шаги:

  1. Пусть obj будет вновь созданным собственным объектом ECMAScript.
  2. Установите все внутренние методы obj, как указано в 8.12.
  3. Установите для внутреннего свойства [[Class]] объекта obj значение "Object".
  4. Установите для внутреннего свойства [[Extensible]] объекта obj значение true.
  5. Пусть proto будет значением вызова внутреннего свойства [[Get]] для F с аргументом> "prototype".
  6. Если тип (proto) равен Object, установите для внутреннего свойства [[Prototype]] объекта obj значение proto.
  7. Если Type (proto) не является Object, установите для внутреннего свойства [[Prototype]] объекта obj значение> стандартного встроенного объекта-прототипа Object, как описано в 15.2.4.
  8. Пусть result будет результатом вызова внутреннего свойства [[Call]] для F, предоставив> obj в качестве значения this и предоставив список аргументов, переданный в [[Construct]] в качестве аргументов.
  9. Если тип (результат) равен объекту, вернуть результат.
  10. Возвратите объект.

Этот человек , кажется, понимает эту концепцию намного лучше, чем я. К, я возвращаюсь в Яву сейчас, где я плаваю больше, чем тону :).

4 голосов
/ 14 февраля 2010

Рассматривали ли вы использовать предложенную здесь функцию клона ?

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
2 голосов
/ 30 ноября 2012

Вдохновившись некоторыми ответами, которые я нашел в StackOverflow, я придумала довольно гибкую функцию, которая все еще работает, когда у объекта или любого из его подобъектов есть конструктор с необходимыми параметрами to Object.create).

(Благодаря Джастину Маккэндлессу теперь также поддерживаются циклические ссылки.)

//If Object.create isn't already defined, we just do the simple shim, without the second argument,
//since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

Эта функция является частью моей библиотеки simpleOO ; там будут сделаны любые исправления ошибок или улучшения (не стесняйтесь открывать проблему на github, если вы обнаружите какую-либо ошибку).

1 голос
/ 20 июля 2011

Проблема в том, что вы передаете новый объект для копирования в {{} '. Вот почему вы теряете свой тип. Я обнаружил, что если вы оберните реальный объект перед его передачей, а затем развернете скопированный объект, расширение сохранит тип, как ожидалось.

function clone(obj)
{
    var wrappedObj = { inner: obj };
    var newObject = jQuery.extend(true, {}, wrappedObj);
    newObject = newObject.inner;
    return newObject;
}
1 голос
/ 21 марта 2010
function clone( obj ) {  
    var target = new obj.constructor();  
    for ( var key in target ) { delete target[key]; }  
    return $.extend( true, target, obj );  
}  

$. Extends не может скопировать все внутренние свойства, которые не видны (некоторые видны в Firefox), но если obj.constructor верен и не будет ошибаться без аргументов, внутренние свойства могут быть установлены с помощью new obj.constructor(). Если вы делаете наследование с чем-то вроде Derived.prototype = new Base(), вам также нужно следовать за этим с Derived.prototype.constructor = Derived, чтобы получить правильный конструктор.

Вы можете сделать $.extend( true, new obj.constructor(), obj ), но возможно, что конструктор создаст свойства, которые были позже удалены - даже если вы могли бы получить правильные аргументы конструктора - поэтому свойства должны быть удалены перед выполнением расширения. Неважно, что аргументы конструктора неверны, так как эффекты оригинальных аргументов конструктора - плюс все остальное, что произошло с объектом с тех пор - находятся в клонируемом нами объекте.

0 голосов
/ 17 октября 2013

Вот альтернативное решение, которое точно не копирует и не клонирует что-либо, но должно дать желаемый результат.

var myObj = new MyClass({a: 1},{a: 2});
var myObjCreator = MyClass.bind(this, {a: 1},{a: 2});
var myObjClone = new myObjCreator();

При этом используется функция bind Javascript для создания объекта, который автоматически передает данные параметры конструктору MyClass.

У меня были те же требования, что и у ОП, и это сработало для меня, поэтому я решил опубликовать его, хотя я понимаю, что некоторым людям может понадобиться настоящая глубокая копия измененного объекта, и это не будет соответствовать требованиям.

0 голосов
/ 28 февраля 2013

В Firefox вы можете написать:

Object.prototype.clone = function() {
  return eval(uneval(this));
}

Может использоваться как:

object1 = object2.clone();

Ответ найден здесь: источник

Но это просто магия Firefox. Другие браузеры могут аварийно завершить работу здесь.

...