Подтвердить что ты не робот

Точно клонировать объект в javascript

Я попытался точно клонировать объект в 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

Есть ли какое-либо решение для получения второго предупреждения?

4b9b3361

Ответ 1

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]] для объекта функции F вызывается с возможным пустым списком аргументов, выполняются следующие шаги:

  • Пусть obj - только что созданный родной объект ECMAScript.
  • Задайте все внутренние методы obj, как указано в 8.12.
  • Установить внутреннее свойство [[Class]] объекта obj в "Object".
  • Установите внутреннее свойство [[Расширяемое]] объекта obj в значение true.
  • Пусть proto является значением вызова внутреннего свойства [[Get]] F с аргументом > "prototype".
  • Если Type (proto) - Object, установите внутреннее свойство [[Prototype]] для obj для proto.
  • Если Type (proto) не Object, установите внутреннее свойство [[Prototype]] для объекта obj для стандартного встроенного объекта прототипа объекта, как описано в 15.2.4.
  • Пусть результат будет вызван внутренним свойством [[Call]] для F, предоставив > obj как это значение и предоставив список аргументов, переданных в [[Construct]] в качестве аргументов.
  • Если Type (result) - Object, то возвращает результат.
  • Возврат obj.

Этот человек, кажется, понимает концепцию намного лучше, чем я. K, я вернусь в java, теперь, когда я плаваю больше, чем погружаюсь:).

Ответ 2

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

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

Ответ 3

Получив вдохновение из некоторых ответов, которые я нашел в StackOverflow, я придумал функцию, которая довольно гибкая и по-прежнему работает, когда объект или любой из его под-объектов имеет конструктор с требуемыми параметрами (спасибо к Object.create).

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

//If Object.create isn't already defined, we just do the simple shim, without the second argument,
//since that 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, если обнаружите какую-либо ошибку).

Ответ 4

function clone( obj ) {  
    var target = new obj.constructor();  
    for ( var key in target ) { delete target[key]; }  
    return $.extend( true, target, obj );  
}  

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

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

Ответ 5

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

function clone(obj)
{
    var wrappedObj = { inner: obj };
    var newObject = jQuery.extend(true, {}, wrappedObj);
    newObject = newObject.inner;
    return newObject;
}

Ответ 6

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

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

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

object1 = object2.clone();

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

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

Ответ 7

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

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

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

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