2010-02-14 3 views
20

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

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 

Есть ли решение, чтобы получить истинное на второй боевой готовности?

+1

Дэвид, разница во всех остальных вопросах клонирования заключается в том, что я попросил сохранить свойство типа объектов. – Tom

ответ

12

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 является довольно сложным. Вы должны быть уверены, что ваш новый объект имеет правильный прототип и, следовательно, правильный конструктор.

Admitadly, Javascript заставляет мою голову болеть. Тем не менее, я думаю, что я читаю это право, от ECMAScript language spec:

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. Если Type (proto) - Object, задайте внутреннее свойство [[Prototype]] объекта obj для proto.
  7. Если тип (proto) не является объектом, задайте внутреннее свойство [[Prototype]] объекта obj для> стандартного встроенного объекта прототипа объекта, как описано в 15.2.4.
  8. Пусть результат будет вызван внутренним свойством [[Call]] для F, предоставив> obj как это значение и предоставив список аргументов, переданных в [[Construct]] в качестве аргументов.
  9. Если Тип (результат) - Объект, то возвращает результат.
  10. Вернуться объект.

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

+0

Thas - очень приятное решение. Кроме того, вы могли бы скопировать все свойства прототипа клонов в клон, поэтому изменения в myObj не будут отображаться в myObjClone. Но что произойдет, если вы снова назовете клон на другом объекте, изменится ли прототип myObjClone? – Tom

+0

Если вы попробуете «myNewObjClone = clone (myObj)», то нет, вы не измените прототип myObjClone. Объект Clone живет только внутри функции clone, поэтому вы получаете новый при каждом вызове clone (obj). Это пример использования закрытия, чтобы «скрыть» переменную, в данном случае объект Clone. – Stephano

4

Считаете ли вы использование клона function suggested here?

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 
+0

Это хороший старт, но клон терпит неудачу, если конструктор требует параметров: var MyClass = function (param1, param2) {alert (param1.контрольная работа)}; – Tom

+0

Tom, где вы ожидаете, что функция 'clone' получит ожидаемые аргументы? Как это знать? – James

+0

@ J-P - Вот о чем мой вопрос. Есть ли способ получить точный клон, сохраняющий информацию о типе, не зная ничего о клонировании объекта. Теперь я предполагаю, что это невозможно. – Tom

1
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 ошибочен, так как эффекты исходного конструктора args - плюс все остальное, что произошло с объектом с тех пор, - находятся в объекте, который мы клонируем.

1

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

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

Я думаю, что это лучший ответ, а не вы рок МУЖЧИНА :) –

2

После черпают вдохновение из некоторых ответов, которые я нашел на StackOverflow, я придумал с функцией, которая является достаточно гибкой, и до сих пор работает, когда объект или любой из его подобъектов имеет конструктор требуемые параметры (благодаря 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, если обнаружите какую-то ошибку).

0

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

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

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

object1 = object2.clone(); 

Ответ был найден час ere: source

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

+1

Это двойное зло ... um Я имею в виду eval ... – Tom

0

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

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

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

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

Смежные вопросы