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