Как правильно клонировать объект JavaScript.

javascript clone javascript-objects


У меня есть объект x . Я хотел бы скопировать его как объект y , чтобы изменения y не изменяли x . Я понял, что копирование объектов, полученных из встроенных объектов JavaScript, приведет к дополнительным нежелательным свойствам. Это не проблема, так как я копирую один из своих собственных объектов, созданных в буквальном смысле.

Как правильно клонировать объект JavaScript?




Answer 1 A. Levy


Сделать это для любого объекта в JavaScript не будет просто или просто. Вы столкнетесь с проблемой ошибочного выбора атрибутов из прототипа объекта, которые следует оставить в прототипе и не копировать в новый экземпляр. Если, например, вы добавляете метод clone в Object.prototype , как показывают некоторые ответы, вам необходимо явно пропустить этот атрибут. Но что, если есть другие дополнительные методы, добавленные в Object.prototype или другие промежуточные прототипы, о которых вы не знаете? В этом случае вы скопируете атрибуты, которые не должны делать, поэтому вам нужно обнаружить непредвиденные нелокальные атрибуты с hasOwnProperty метода hasOwnProperty .

Помимо неперечислимых атрибутов, вы столкнетесь с более сложной проблемой при попытке скопировать объекты, которые имеют скрытые свойства. Например, prototype является скрытым свойством функции. Кроме того, на прототип объекта ссылается атрибут __proto__ , который также скрыт и не будет скопирован циклом for / in, повторяющимся по атрибутам исходного объекта. Я думаю, что __proto__ может быть специфичным для интерпретатора JavaScript Firefox, и это может быть что-то другое в других браузерах, но вы получите представление. Не все перечисляемо. Вы можете скопировать скрытый атрибут, если знаете его имя, но я не знаю, как его обнаружить автоматически.

Еще одним препятствием в поиске элегантного решения является проблема правильной настройки наследования прототипа. Если прототипом вашего исходного объекта является Object , тогда будет работать просто создание нового общего объекта с помощью {} , но если прототип исходного Object является неким потомком Object , то вы пропустите дополнительные члены из этого прототипа, который вы пропустили, используя Фильтр hasOwnProperty , или который был в прототипе, но не был перечисляемым в первую очередь. Одним из решений может быть вызов свойства constructor исходного объекта, чтобы получить начальный объект копирования, а затем скопировать атрибуты, но тогда вы все равно не получите неперечислимые атрибуты. Например, Date Объект хранит свои данные как скрытый член:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

Строка даты для d1 будет на 5 секунд меньше, чем для d2 . Один из способов сделать одну и ту же Date другим - вызвать метод setTime , но это характерно для класса Date . Я не думаю, что есть пуленепробиваемое общее решение этой проблемы, хотя я был бы рад ошибаться!

Когда мне пришлось реализовать общее глубокое копирование, я в итоге пошел на компромисс, предполагая, что мне нужно будет только скопировать простой Object , Array , Date , String , Number или Boolean . Последние 3 типа являются неизменяемыми, поэтому я мог выполнить поверхностное копирование и не беспокоиться о его изменении. Я также предположил, что любые элементы, содержащиеся в Object или Array , также будут одним из 6 простых типов в этом списке. Это может быть выполнено с помощью кода, подобного следующему:

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

Вышеуказанная функция будет адекватно работать для 6 простых типов,о которых я говорил,до тех пор,пока данные в объектах и массивах образуют древовидную структуру.То есть в объекте не более одной ссылки на одни и те же данные.Например:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

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




Answer 2 heinob


Если вы не используете Date s, функции, undefined, regExp или Infinity в вашем объекте, очень простой однострочник - это JSON.parse(JSON.stringify(object)) :

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

Это работает для всех видов объектов,содержащих объекты,массивы,строки,булеры и числа.

См. Также эту статью о алгоритме структурированного клонирования браузеров, который используется при отправке сообщений сотруднику и от него. Он также содержит функцию для глубокого клонирования.




Answer 3 Pascal


С jQuery вы можете копировать с расширением :

var copiedObject = jQuery.extend({}, originalObject)

последующие изменения в copiedObject не будут влиять на originalObject , и наоборот.

Или сделать глубокую копию :

var copiedObject = jQuery.extend(true, {}, originalObject)



Answer 4 Vitalii Fedorenko


В ECMAScript 6 есть метод Object.assign , который копирует значения всех перечисляемых собственных свойств из одного объекта в другой. Например:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

Но имейте в виду,что вложенные объекты все равно копируются как ссылка.




Answer 5 Tareq


За MDN :

  • Если вам нужна мелкая копия, используйте Object.assign({}, a)
  • Для «глубокой» копии используйте JSON.parse(JSON.stringify(a))

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