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

3081

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

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

soundly_typed
источник
31
Смотрите этот вопрос: stackoverflow.com/questions/122102/…
Нияз
257
Для JSON я используюmObj=JSON.parse(JSON.stringify(jsonObject));
Лорд Лох.
68
Я действительно не понимаю, почему никто не предлагает Object.create(o), он делает все, что просит автор?
froginvasion
45
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; После этого y.deep.keyтакже будет 2, следовательно, Object.create НЕ МОЖЕТ ИСПОЛЬЗОВАТЬСЯ для клонирования ...
Рубен Столк
18
@ r3wt, который не будет работать ... Пожалуйста, отправляйте сообщения только после выполнения основного теста решения ..
akshay

Ответы:

1562

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

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

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

А. Леви
источник
5
почти нормально работал в nodejs - просто пришлось изменить строку для (var i = 0, var len = obj.length; i <len; ++ i) {на for (var i = 0; i <obj.length; ++ i) {
Trindaz
5
Для будущих гуглов: та же самая глубокая копия, рекурсивно передавая ссылки вместо использования операторов return
Trindaz
8
Будет ли в настоящее JSON.parse(JSON.stringify([some object]),[some revirer function])время решением проблемы?
KooiInc
5
В первом фрагменте вы уверены, что не должно быть var cpy = new obj.constructor()?
Cyon
8
Присвоение объекта, по-видимому, не делает истинную копию в Chrome, поддерживает ссылку на исходный объект - в конечном итоге использовались JSON.stringify и JSON.parse для клонирования - отлично работало
1owk3y
975

Если вы не используете Dates, functions, 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()

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

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

heinob
источник
43
Обратите внимание, что это может быть использовано только для тестирования. Во-первых, это далеко не оптимально с точки зрения времени и потребления памяти. Во-вторых, не все браузеры имеют такие методы.
Nux
2
Вы всегда можете включить JSON2.js или JSON3.js. В любом случае они понадобятся для вашего приложения. Но я согласен, что это может быть не лучшим решением, поскольку JSON.stringify не включает унаследованные свойства.
Тим Хонг,
79
@Nux, почему не оптимально с точки зрения времени и памяти? MiJyn говорит: «Причина, по которой этот метод медленнее, чем поверхностное копирование (для глубокого объекта), заключается в том, что этот метод по определению является глубоким копированием. Но поскольку JSON реализован в собственном коде (в большинстве браузеров), это будет значительно быстрее. чем использование любого другого решения для глубокого копирования на основе JavaScript, и иногда может быть быстрее, чем метод поверхностного копирования на основе JavaScript (см .: jsperf.com/cloning-an-object/79). " stackoverflow.com/questions/122102/…
BeauCielBleu
17
Я просто хочу добавить обновление к октябрю 2014 года. Chrome 37+ быстрее с JSON.parse (JSON.stringify (oldObject)); Преимущество использования этого заключается в том, что движку javascript очень легко видеть и оптимизировать что-то лучшее, если он этого хочет.
mirhagk
17
Обновление 2016: теперь это должно работать практически во всех широко используемых браузерах. (см. Могу ли я использовать ... ) Главный вопрос сейчас будет достаточно ли он эффективен.
Джеймс Фостер
784

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

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

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

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

var copiedObject = jQuery.extend(true, {}, originalObject)
паскаль
источник
164
или даже:var copiedObject = jQuery.extend({},originalObject);
Грант Маклин
82
Также полезно указывать true в качестве первого параметра для глубокого копирования:jQuery.extend(true, {}, originalObject);
Will Shaver
6
Да, я нашел эту ссылку полезной (то же решение, что и для Pascal) stackoverflow.com/questions/122102/…
Garry English
3
Просто обратите внимание, это не копирует протоконтроль оригинального объекта
Сэм Джонс
1
Согласно stackoverflow.com/questions/184710/…. , Кажется, что "мелкая копия" просто копирует ссылку на originalObject, так почему здесь это сказано ...subsequent changes to the copiedObject will not affect the originalObject, and vice versa.... Извините, что я был действительно смущен.
Карр
687

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

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

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

Виталий Федоренко
источник
Да, я считаю, что Object.assignэто путь. Это также легко заполнить: gist.github.com/rafaelrinaldi/43813e707970bd2d77fa
22
Также имейте в виду , что это будет копировать «методы» , определенные с помощью литералов объектов (поскольку они являются перечисли) , но не методы бросили вызов через механизм «класса» (так как они не перечислимы).
Маркус Юний Брут
16
Я думаю, следует упомянуть, что это не поддерживается IE, кроме Edge. Некоторые люди все еще используют это.
Саулюс
1
Это так же, как @EugeneTiurin его ответ.
Уилт
5
Говоря из опыта здесь ... НЕ используйте это. Объекты имеют иерархическую структуру, и вы будете копировать только первый уровень, что приведет к назначению скопированного объекта целиком. Поверь мне, это может спасти тебе дни от царапин на голове.
ow3n
234

в MDN :

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

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

Тарек
источник
8
JSON.parse (JSON.stringify (a)) выглядит красиво, но перед его использованием я рекомендую сравнить время, необходимое для вашей желаемой коллекции. В зависимости от размера объекта, это может быть не самый быстрый вариант.
Эдза
3
Метод JSON, который я заметил, преобразует объекты даты в строки, но не обратно в даты. Приходится иметь дело с забавными часовыми поясами в Javascript и вручную фиксировать любые даты. Может быть аналогичные случаи для других типов, кроме дат
Стив Сигер
для даты я бы использовал moment.js, так как он имеет cloneфункциональность. узнать больше здесь momentjs.com/docs/#/parsing/moment-clone
Tareq
Это хорошо, но
будьте
Другая проблема с анализом json - это циклические ссылки. Если внутри объекта есть какие-либо циклические ссылки, это сломается
philoj
134

Есть много ответов, но ни один из них не упоминает Object.create из ECMAScript 5, который, по общему признанию, не дает точной копии, но устанавливает источник в качестве прототипа нового объекта.

Таким образом, это не точный ответ на вопрос, но это однострочное решение и, следовательно, элегантный. И это лучше всего работает в 2 случаях:

  1. Где такое наследство полезно (дух!)
  2. Где исходный объект не будет изменен, таким образом, отношения между двумя объектами не проблема.

Пример:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

Почему я считаю это решение лучшим? Он родной, поэтому нет циклов, нет рекурсии. Тем не менее, старые браузеры будут нуждаться в polyfill.

itpastorn
источник
Примечание: Object.create не является глубокой копией (itpastorn не упомянул рекурсию). Доказательство:var a = {b:'hello',c:{d:'world'}}, b = Object.create(a); a == b /* false */; a.c == b.c /* true */;
zamnuts
103
Это наследование прототипа, а не клонирование. Это совершенно разные вещи. Новый объект не имеет своих собственных свойств, он просто указывает на свойства прототипа. Смысл клонирования заключается в создании нового нового объекта, который не ссылается ни на какие свойства другого объекта.
d13
7
Я с вами полностью согласен. Я также согласен, что это не клонирование, как это может быть «задумано». Но давай люди, прими природу JavaScript вместо того, чтобы пытаться найти неясные решения, которые не стандартизированы. Конечно, вам не нравятся прототипы, и все они «бла» для вас, но на самом деле они очень полезны, если вы знаете, что делаете.
froginvasion
4
@RobG: эта статья объясняет разницу между ссылками и клонированием: en.wikipedia.org/wiki/Cloning_(programming) . Object.createуказывает на свойства родителя через ссылки. Это означает, что если значения свойств родителя изменятся, то и потомок также изменится. Это имеет некоторые неожиданные побочные эффекты с вложенными массивами и объектами, которые могут привести к трудным для обнаружения ошибкам в вашем коде, если вы о них не знаете: jsbin.com/EKivInO/2 . Клонированный объект - это совершенно новый, независимый объект, который имеет те же свойства и значения, что и родительский объект, но не связан с родительским объектом.
d13
1
Это вводит людей в заблуждение ... Object.create () может использоваться как средство наследования, но клонирование далеко не близко к нему.
Праджняванта
129

Элегантный способ клонировать объект Javascript в одну строку кода

Object.assignМетод является частью 2015 (ES6) стандарта ECMAScript и делает именно то , что вам нужно.

var clone = Object.assign({}, obj);

Метод Object.assign () используется для копирования значений всех перечисляемых собственных свойств из одного или нескольких исходных объектов в целевой объект.

Читать далее...

Polyfill для поддержки старых браузеров:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}
Евгений Тюрин
источник
Извините за глупый вопрос, но почему Object.assignиспользуются два параметра, когда valueфункция в полифилле принимает только один параметр?
Qwertie
@Qwertie вчера Все аргументы повторяются и объединяются в один объект, отдавая приоритет свойствам из последнего переданного аргумента
Евгений Тюрин
О, я понимаю, спасибо (я не был знаком с argumentsобъектом раньше.) У меня проблемы с поиском Object()через Google ... это типизированная передача, не так ли?
Qwertie
45
это будет выполнять только поверхностное «клонирование»
Маркус Юний Брут
1
Этот ответ точно такой же, как и этот: stackoverflow.com/questions/122102/… Я знаю, что это тот же человек, но вы должны ссылаться, а не просто копировать ответ.
lesolorzanov
88

Есть несколько проблем с большинством решений в Интернете. Поэтому я решил сделать продолжение, которое включает, почему принятый ответ не должен быть принят.

стартовая ситуация

я бы хотел глубоко скопировать Javascript Objectсо всеми его детьми и их детьми и так далее. Но так как я не нормальный разработчик, у меня Objectесть нормальный properties , circular structuresи даже nested objects.

Итак, давайте создадим circular structureи nested objectпервый.

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

Давайте соберем все вместе по Objectимени a.

var a = {
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
};

Далее мы хотим скопировать aв переменную с именем bи изменить ее.

var b = a;

b.x = 'b';
b.nested.y = 'b';

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

console.log(a, b);

a --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

Теперь давайте найдем решение.

JSON

Первая попытка, которую я попробовал, использовала JSON.

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

Не тратьте слишком много времени на это, вы получите TypeError: Converting circular structure to JSON .

Рекурсивная копия (принятый «ответ»)

Давайте посмотрим на принятый ответ.

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

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

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

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

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

Выглядит хорошо, а? Это рекурсивная копия объекта, которая также обрабатывает другие типы Date, но это не было обязательным требованием.

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

Рекурсия и circular structuresне очень хорошо работают вместе ...RangeError: Maximum call stack size exceeded

нативное решение

После спора с моим коллегой, мой начальник спросил нас, что случилось, и он нашел простое решение после некоторого поиска в Google. Это называется Object.create.

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

Это решение было добавлено в Javascript некоторое время назад и даже обрабатывает circular structure.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

... и вы видите, это не сработало с вложенной структурой внутри.

полифилл для нативного раствора

В Object.createстаром браузере, как и в IE 8 , есть полифилл. Это что-то вроде рекомендованного Mozilla, и, конечно, оно не идеально и приводит к той же проблеме, что и нативное решение .

function F() {};
function clonePF(o) {
    F.prototype = o;
    return new F();
}

var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

Я Fвышел за рамки, чтобы мы могли взглянуть на то, что instanceofговорит нам.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> F {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> true

Та же проблема, что и у нативного решения , но немного худший результат.

лучшее (но не идеальное) решение

Копаясь, я нашел похожий вопрос ( в Javascript, когда я выполняю глубокое копирование, как мне избежать цикла из-за свойства «this»? ) На этот, но с более лучшим решением.

function cloneDR(o) {
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o)) {
        return o; // primitive value
    }

    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function") {
        return cache();
    }
    // else
    o[gdcc] = function() { return result; }; // overwrite
    if (o instanceof Array) {
        result = [];
        for (var i=0; i<o.length; i++) {
            result[i] = cloneDR(o[i]);
        }
    } else {
        result = {};
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = cloneDR(o[prop]);
            else if (set)
                result[prop] = cloneDR(cache);
    }
    if (set) {
        o[gdcc] = cache; // reset
    } else {
        delete o[gdcc]; // unset again
    }
    return result;
}

var b = cloneDR(a);

b.x = 'b';
b.nested.y = 'b';

И давайте посмотрим на вывод ...

console.log(a, b);

a --> Object {
    x: "a",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "a"
    }
}

b --> Object {
    x: "b",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> false

Требования соответствуют, но все еще есть некоторые меньшие проблемы, включая изменение instanceof nestedи circto Object.

Структура деревьев, имеющих общий лист, не будет скопирована, они станут двумя независимыми листьями:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

вывод

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

jsfiddle

Фабио Полони
источник
12
поэтому заключение состоит в том, чтобы избежать этой проблемы :)
mikus
@mikus, пока не появится реальная спецификация, которая охватывает не только основные варианты использования, да.
Фабио Полони
2
Хороший анализ решений, представленных выше, но вывод, сделанный автором, указывает на то, что нет решения этого вопроса.
Амир Мог
2
Обидно, что JS не включает в себя нативную функцию клонирования.
l00k
1
Среди всех топовых ответов я чувствую, что это близко к правильному.
КТУ
78

Если у вас все в порядке с мелкой копией, в библиотеке underscore.js есть метод clone .

y = _.clone(x);

или вы можете расширить его как

copiedObject = _.extend({},originalObject);
Dule
источник
2
Спасибо. Используя эту технику на сервере Метеор.
Turbo
Чтобы быстро начать работу с lodash, я бы порекомендовал изучить npm, Browserify, а также lodash. Я получил клон для работы с 'npm i --save lodash.clone', а затем 'var clone = require (' lodash.clone ');' Чтобы получить требуют работать, вам нужно что-то вроде browserify. После того, как вы установите его и узнаете, как он работает, вы будете использовать «browserify yourfile.js> bundle.js; запускать chrome index.html» каждый раз, когда запускаете код (вместо того, чтобы заходить в Chrome напрямую). Это соберет ваш файл и все необходимые файлы из модуля npm в bundle.js. Вы, вероятно, можете сэкономить время и автоматизировать этот шаг с Gulp, хотя.
Аарон Белл
66

Хорошо, представьте, что у вас есть этот объект ниже, и вы хотите его клонировать:

let obj = {a:1, b:2, c:3}; //ES6

или

var obj = {a:1, b:2, c:3}; //ES5

ответ в основном depeneds , на котором ECMAScript вы используете, в ES6+, вы можете просто использовать , Object.assignчтобы сделать клон:

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

или используя оператор распространения, как это:

let cloned = {...obj}; //new {a:1, b:2, c:3};

Но если вы используете ES5, вы можете использовать несколько методов, но JSON.stringify, просто убедитесь, что вы не используете для копирования большой кусок данных, но во многих случаях это может быть удобной однострочной строкой, что-то вроде этого:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over
Алиреза
источник
Можете ли вы привести пример того, что big chunk of dataбы приравнять? 100kb? 100MB? Спасибо!
user1063287
Да, @ user1063287, что, в основном, чем больше данные, тем хуже производительность ... так что это действительно зависит, а не от КБ, МБ или ГБ, это больше о том, сколько раз вы хотите сделать это также ... Также это не будет работать для функций и других вещей ...
Алиреза
3
Object.assignделает мелкую копию (так же, как распространение, @Alizera)
Богдан Д.
Вы не можете использовать let in es5: ^) @Alireza
SensationSama
41

Одним из особенно не элегантных решений является использование кодировки JSON для создания глубоких копий объектов, которые не имеют методов-членов. Методология состоит в том, чтобы JSON кодировать ваш целевой объект, а затем, расшифровав, вы получите искомую копию. Вы можете декодировать столько раз, сколько хотите, чтобы сделать столько копий, сколько вам нужно.

Конечно, функции не принадлежат JSON, поэтому это работает только для объектов без методов-членов.

Эта методология идеально подошла для моего случая использования, поскольку я храню большие двоичные объекты JSON в хранилище значений ключей, и когда они представляются как объекты в API JavaScript, каждый объект фактически содержит копию исходного состояния объекта, поэтому мы может вычислить дельту после того, как вызывающая сторона видоизменила экспонированный объект.

var object1 = {key:"value"};
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value
Крис Уокер
источник
Почему функции не принадлежат JSON? Я видел, как они передавались как JSON не раз ...
the_drow
5
Функции не являются частью спецификации JSON, потому что они не являются безопасным (или интеллектуальным) способом передачи данных, для чего и был создан JSON. Я знаю, что собственный кодер JSON в Firefox просто игнорирует переданные ему функции, но я не уверен в поведении других.
Крис Уокер
1
@mark: { 'foo': function() { return 1; } }объект, созданный в буквальном смысле .
abarnert
Функции @abarnert не являются данными. «Функциональные литералы» являются неправильным обозначением - поскольку функции могут содержать произвольный код, включая присваивания и все виды «не сериализуемых» вещей.
vemv
36

Вы можете просто использовать свойство распространения, чтобы скопировать объект без ссылок. Но будьте осторожны (см. Комментарии), «копия» находится на самом низком уровне объекта / массива. Вложенные свойства все еще являются ссылками!


Полный клон:

let x = {a: 'value1'}
let x2 = {...x}

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

Клон со ссылками на второй уровень:

const y = {a: {b: 'value3'}}
const y2 = {...y}

// => nested object is still a references:

y2.a.b = 'value4'
console.log(y.a.b)    // => 'value4'

JavaScript на самом деле не поддерживает глубокие клоны изначально. Используйте служебную функцию. Например Рамда:

http://ramdajs.com/docs/#clone

musemind
источник
1
Это не работает ... это будет работать, вероятно, когда x будет массивом, например, x = ['ab', 'cd', ...]
Kamil Kiełczewski
3
Это работает, но имейте в виду, что это мелкая копия, поэтому любые глубокие ссылки на другие объекты остаются ссылками!
Bugs Bunny
Частичное клонирование также может произойти следующим образом:const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}
Кристиан Транина
26

Для тех, кто использует AngularJS, существует также прямой метод клонирования или расширения объектов в этой библиотеке.

var destination = angular.copy(source);

или

angular.copy(source, destination);

Больше в документации angular.copy ...

Лукас Елинек
источник
2
Это глубокая копия FYI.
Замнюц
23

Вот функция, которую вы можете использовать.

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;
}
Picardo
источник
10
Этот ответ довольно близок, но не совсем корректен. Если вы попытаетесь клонировать объект Date, вы не получите ту же дату, потому что вызов функции конструктора Date инициализирует новую Date с текущей датой / временем. Это значение не перечисляется и не будет скопировано циклом for / in.
А. Леви
Не идеально, но хорошо для тех основных случаев. Например, допускается простое клонирование аргумента, который может быть базовым объектом, массивом или строкой.
james_womack
Upvoted для правильного вызова конструктора с помощью new. Принятый ответ не дает.
GetFree
работает на узле все остальное! еще остались ссылочные ссылки
user956584
Рекурсивная мысль - это здорово. Но если значение равно массиву, оно будет работать?
Q10Viking
23

Ответ А. Леви почти полный, вот мой маленький вклад: есть способ, как обрабатывать рекурсивные ссылки , см. Эту строку

if(this[attr]==this) copy[attr] = copy;

Если объект является XML-элементом DOM, мы должны использовать вместо него cloneNode

if(this.cloneNode) return this.cloneNode(true);

Вдохновленный исчерпывающим исследованием А. Леви и подходом Келвина к созданию прототипов, я предлагаю следующее решение:

Object.prototype.clone = function() {
  if(this.cloneNode) return this.cloneNode(true);
  var copy = this instanceof Array ? [] : {};
  for(var attr in this) {
    if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
      copy[attr] = this[attr];
    else if(this[attr]==this) copy[attr] = copy;
    else copy[attr] = this[attr].clone();
  }
  return copy;
}

Date.prototype.clone = function() {
  var copy = new Date();
  copy.setTime(this.getTime());
  return copy;
}

Number.prototype.clone = 
Boolean.prototype.clone =
String.prototype.clone = function() {
  return this;
}

Смотрите также заметку Энди Бёрка в ответах.

Ян Турош
источник
3
Date.prototype.clone = function() {return new Date(+this)};
RobG
23

Из этой статьи: Как скопировать массивы и объекты в Javascript Брайана Хуисмана:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};
Кальвин
источник
4
Это близко, но не работает для любого объекта. Попробуйте клонировать объект Date с этим. Не все свойства являются перечисляемыми, поэтому не все они будут отображаться в цикле for / in.
А. Леви
Добавление к объекту прототипа, как это сломало jQuery для меня. Даже когда я переименовал в clone2.
iPadDeveloper2011
3
@ iPadDeveloper2011 Приведенный выше код содержал ошибку, в результате которой была создана глобальная переменная с именем «i» (для i в этом), а не «(для var i в этом)». У меня достаточно кармы, чтобы отредактировать ее и исправить, так что я сделал.
mikemaccana
1
@Calvin: это должно быть создано не перечисляемым свойством, иначе «клон» появится в циклах for.
mikemaccana
2
почему не var copiedObj = Object.create(obj);отличный способ, а?
Дэн П.
20

В ES-6 вы можете просто использовать Object.assign (...). Пример:

let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);

Хорошая ссылка здесь: https://googlechrome.github.io/samples/object-assign-es6/

Жоао Оливейра
источник
12
Это не глубоко клонировать объект.
августа
Это задание, а не копия. clone.Title = "просто клон" означает, что obj.Title = "просто клон".
HoldOffHunger
@HoldOffHunger Вы ошибаетесь. Проверьте это в JS консоли вашего браузера ( let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); clone.title = "Whazzup";)
Коллапсар
@collapsar: Это именно то, что я проверил, тогда console.log (человек) будет "Whazzup", а не "Thor Odinson". Смотрите комментарий Августа.
HoldOffHunger
1
@HoldOffHunger Не происходит ни в Chrome 60.0.3112.113, ни в Edge 14.14393; Комментарий Августа не применяется, так как значения примитивных типов objсвойств действительно клонируются. Значения свойств, которые сами являются объектами, не будут клонированы.
Коллапсар
19

В ECMAScript 2018

let objClone = { ...obj };

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

Паван Гарре
источник
1
Спасибо за подсказку, что вложенные объекты все еще копируются в качестве ссылки! Я чуть не сошел с ума при отладке своего кода, потому что я изменил вложенные свойства для «клона», но оригинал был изменен.
Бенни Нойгебауэр
Это ES2016, а не 2018, и этот ответ был дан двумя годами ранее .
Дан Даскалеску
ну и что, если я тоже хочу получить копию вложенного свойства
Sunil Garg
1
@SunilGarg Чтобы скопировать вложенное свойство, вы также можете использовать const objDeepClone = JSON.parse(JSON.stringify(obj));
Pavan
17

Используя Lodash:

var y = _.clone(x, true);
VaZaA
источник
5
OMG, было бы безумно изобретать клонирование. Это единственный вменяемый ответ.
Дэн Росс,
5
Я предпочитаю, _.cloneDeep(x)поскольку это по сути то же самое, что и выше, но читается лучше.
Гарбанцио
15

Интересует клонирование простых объектов:

JSON.parse(JSON.stringify(json_original));

Источник: Как скопировать объект JavaScript в новую переменную НЕ по ссылке?

Мохаммед Акдим
источник
Очень мило - просто.
Мэтт Х
@MattH: этот ответ был дан уже в 2012 году . ты видел это? Мохаммед, ты проверил существующие ответы, прежде чем дублировать один из них?
Дан Даскалеску
хорошо это один из способов. Ты никогда не думал об этом
1-14x0r
14

Вы можете клонировать объект и удалить любую ссылку из предыдущего, используя одну строку кода. Просто сделайте:

var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references

obj2.text = 'moo2'; // Only updates obj2's text property

console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}

Для браузеров / движков, которые в настоящее время не поддерживают Object.create, вы можете использовать этот polyfill:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}
Роб Эванс
источник
1
+1 Object.create(...)кажется определенно путь.
Рене Ниффенеггер
Идеальный ответ. Может быть, вы могли бы добавить объяснение Object.hasOwnProperty? Таким образом, люди знают, как предотвратить поиск ссылки на прототип.
froginvasion
Хорошо работает, но в каких браузерах работает полифилл?
Ян Ланн
11
Это создает obj2 с obj1 в качестве прототипа. Это работает только потому, что вы следите за textучастником в obj2. Вы не делаете копию, просто откладываете цепочку прототипов, когда член не найден в obj2.
Ник Desaulniers
2
Это НЕ создает его «без ссылок», оно просто перемещает ссылку на прототип. Это все еще ссылка. Если свойство изменяется в оригинале, то и свойство прототипа будет в «клоне». Это не клон вообще.
Джимбо Джонни
13

Новый ответ на старый вопрос! Если вы имеете удовольствие от использования ECMAScript 2016 (ES6) с синтаксисом распространения , это легко.

keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}

Это обеспечивает чистый метод для мелкой копии объекта. Создание глубокой копии, означающей создание новой копии каждого значения в каждом рекурсивно-вложенном объекте, требует более тяжелых решений, указанных выше.

JavaScript продолжает развиваться.

Чарльз Мерриам
источник
2
это не работает, когда у вас есть функции, определенные на объектах
Петр Марек
насколько я вижу, оператор распространения работает только с итерациями - developer.mozilla.org говорит: var obj = {'key1': 'value1'}; var array = [...obj]; // TypeError: obj is not iterable
Олег
@Oleh, так что используйте `{... obj} вместо [... obj];`
manikant gautam
@manikantgautam Я раньше использовал Object.assign (), но теперь действительно синтаксис распространения объектов поддерживается в последних версиях Chrome, Firefox (до сих пор нет в Edge и Safari). Это предложение ECMAScript ... но, насколько я понимаю, Babel поддерживает его, поэтому, вероятно, его безопасно использовать.
Олег
12
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

Решение ES6, если вы хотите (поверхностно) клонировать экземпляр класса, а не просто объект свойства.

Флори
источник
Чем это отличается от let cloned = Object.assign({}, obj)?
ceztko
10

Я думаю, что есть простой и рабочий ответ. При глубоком копировании есть две проблемы:

  1. Сохраняйте свойства независимыми друг от друга.
  2. И сохранить методы на клонированном объекте.

Поэтому я думаю, что одним из простых решений будет сначала сериализовать и десериализовать, а затем назначить для него функции копирования.

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

Хотя на этот вопрос есть много ответов, я надеюсь, что этот тоже поможет.

ConductedClever
источник
Хотя если мне разрешено импортировать lodash, я предпочитаю использовать lodash cloneDeep.
ConductedClever
2
Я использую JSON.parse (JSON.stringify (source)). Всегда работает.
Миша
2
@ Миша, так ты упустишь функции. Термин «произведения» имеет много значений.
ConductedClever
И имейте в виду, что, как я уже говорил, будут скопированы только функции первого слоя. Так что если у нас есть несколько объектов друг внутри друга, то единственный способ - это рекурсивно копировать поле за полем.
ConductedClever
9

Для глубокого копирования и клонирования JSON.stringify затем JSON.parse объект:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}
Нишант Двиведи
источник
довольно умно ... есть ли недостатки этого подхода?
Алекс
8

Это адаптация кода А. Леви для обработки клонирования функций и множественных / циклических ссылок - это означает, что если два свойства в клонированном дереве являются ссылками на один и тот же объект, то клонированное дерево объектов будет иметь эти свойства указывают на один и тот же клон ссылочного объекта. Это также решает случай циклических зависимостей, которые, если оставить их необработанными, приводят к бесконечному циклу. Сложность алгоритма составляет O (n)

function clone(obj){
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) {
        if (obj == null) return null;
        if (obj.__obj_id == undefined){
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        }
        return obj.__obj_id;
    }

    function cloneRecursive(obj) {
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;

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

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

        // Handle Object
        if (obj instanceof Object) {
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function(){return obj.apply(this, arguments);};
            else
                copy = {};

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
        }       


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



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    {
        delete originalObjectsArray[i].__obj_id;
    };

    return cloneObj;
}

Несколько быстрых тестов

var auxobj = {
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    };

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function (){
    this.prop1 = "prop1 val changed by f1";
};

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));
Раду Симионеску
источник
1
По состоянию на сентябрь 2016 года это единственное правильное решение вопроса.
DomQ
6

Я просто хотел добавить ко всем Object.create решениям в этом посте, что это не работает желаемым образом с nodejs.

В Firefox результат

var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´

является

{test:"test"},

В nodejs это

{}
heinob
источник
Это наследование прототипа, а не клонирование.
d13
1
@ d13, пока ваш аргумент действителен, обратите внимание, что в JavaScript нет стандартного способа клонирования объекта. Это прототип наследования, но, тем не менее, его можно использовать как клоны, если вы понимаете концепции.
froginvasion
@froginvasion. Единственная проблема с использованием Object.create заключается в том, что вложенные объекты и массивы являются просто указателями на вложенные объекты и массивы прототипа. jsbin.com/EKivInO/2/edit?js,console . Технически «клонированный» объект должен иметь свои собственные уникальные свойства, которые не являются общими ссылками на свойства других объектов.
д13
@ d13 хорошо, теперь я понимаю твою точку зрения. Но я имел в виду, что слишком многие люди отчуждаются от концепции прототипического наследования, и мне не удается понять, как это работает. Если я не ошибаюсь, ваш пример можно исправить, просто позвонив, Object.hasOwnPropertyчтобы проверить, владеете ли вы массивом или нет. Да, это добавляет дополнительную сложность, чтобы иметь дело с прототипным наследованием.
froginvasion
6
function clone(src, deep) {

    var toString = Object.prototype.toString;
    if(!src && typeof src != "object"){
        //any non-object ( Boolean, String, Number ), null, undefined, NaN
        return src;
    }

    //Honor native/custom clone methods
    if(src.clone && toString.call(src.clone) == "[object Function]"){
        return src.clone(deep);
    }

    //DOM Elements
    if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){
        return src.cloneNode(deep);
    }

    //Date
    if(toString.call(src) == "[object Date]"){
        return new Date(src.getTime());
    }

    //RegExp
    if(toString.call(src) == "[object RegExp]"){
        return new RegExp(src);
    }

    //Function
    if(toString.call(src) == "[object Function]"){
        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });

    }

    var ret, index;
    //Array
    if(toString.call(src) == "[object Array]"){
        //[].slice(0) would soft clone
        ret = src.slice();
        if(deep){
            index = ret.length;
            while(index--){
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }

    return ret;
};
user1547016
источник
2
if(!src && typeof src != "object"){, Я думаю, что это ||не должно быть &&.
MikeM
6

Поскольку mindeavor заявил, что клонируемый объект является «буквально сконструированным» объектом, решение может состоять в том, чтобы просто сгенерировать объект несколько раз, а не клонировать экземпляр объекта:

function createMyObject()
{
    var myObject =
    {
        ...
    };
    return myObject;
}

var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
Берт Регелинк
источник
6

Я написал свою собственную реализацию. Не уверен, что это считается лучшим решением:

/*
    a function for deep cloning objects that contains other nested objects and circular structures.
    objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                    index (z)
                                         |
                                         |
                                         |
                                         |
                                         |
                                         |                      depth (x)
                                         |_ _ _ _ _ _ _ _ _ _ _ _
                                        /_/_/_/_/_/_/_/_/_/
                                       /_/_/_/_/_/_/_/_/_/
                                      /_/_/_/_/_/_/...../
                                     /................./
                                    /.....            /
                                   /                 /
                                  /------------------
            object length (y)    /
*/

Ниже приводится реализация:

function deepClone(obj) {
    var depth = -1;
    var arr = [];
    return clone(obj, arr, depth);
}

/**
 *
 * @param obj source object
 * @param arr 3D array to store the references to objects
 * @param depth depth of the current object relative to the passed 'obj'
 * @returns {*}
 */
function clone(obj, arr, depth){
    if (typeof obj !== "object") {
        return obj;
    }

    var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'

    var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
    if(result instanceof Array){
        result.length = length;
    }

    depth++; // depth is increased because we entered an object here

    arr[depth] = []; // this is the x-axis, each index here is the depth
    arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
    // start the depth at current and go down, cyclic structures won't form on depths more than the current one
    for(var x = depth; x >= 0; x--){
        // loop only if the array at this depth and length already have elements
        if(arr[x][length]){
            for(var index = 0; index < arr[x][length].length; index++){
                if(obj === arr[x][length][index]){
                    return obj;
                }
            }
        }
    }

    arr[depth][length].push(obj); // store the object in the array at the current depth and length
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
    }

    return result;
}
yazjisuhail
источник
не работает для моего объекта, хотя мой случай немного сложен.
Sajuuk