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

2619

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

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

  • 28
    Смотрите этот вопрос: stackoverflow.com/questions/122102/…
  • 0
    Определенно поддержите @Niyaz! Короткая ссылка: tinyurl.com/JSCopyObject
Показать ещё 21 комментарий
Теги:
javascript-objects
clone

60 ответов

1453
Лучший ответ

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

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

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

  • 0
    Достигнут ли в вашей функции клонирования новый оператор Error?
  • 2
    @javierfp: я думаю, что это достижимо. Оператор instanceof работает путем проверки цепочки прототипов объектов (в соответствии со ссылкой на JavaScript в Mozilla: developer.mozilla.org/en/JavaScript/Reference/Operators/Special/… ). Я предполагаю, что кто-то может изменить цепочку прототипов, чтобы больше не включать Object. Это было бы необычно, но вызвало бы ошибку.
Показать ещё 27 комментариев
780

С помощью jQuery вы можете мелкой копии с extend:

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

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

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

var copiedObject = jQuery.extend(true, {}, originalObject)
  • 163
    или даже: var copiedObject = jQuery.extend({},originalObject);
  • 81
    Также полезно указывать значение true в качестве первого параметра для глубокого копирования: jQuery.extend(true, {}, originalObject);
Показать ещё 11 комментариев
742

Если вы не используете функции внутри вашего объекта, очень простой один лайнер может быть следующим:

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

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

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

  • 39
    Обратите внимание, что это может быть использовано только для тестирования. Во-первых, это далеко не оптимально с точки зрения времени и потребления памяти. Во-вторых, не все браузеры имеют такие методы.
  • 2
    Вы всегда можете включить JSON2.js или JSON3.js. В любом случае они понадобятся для вашего приложения. Но я согласен, что это может быть не лучшим решением, поскольку JSON.stringify не включает унаследованные свойства.
Показать ещё 27 комментариев
569

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

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

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

  • 0
    Да, я считаю, что Object.assign - это путь. Это тоже легко заполнить: gist.github.com/rafaelrinaldi/43813e707970bd2d77fa
  • 211
    Но имейте в виду, что это делает только поверхностную копию. Вложенные объекты все еще копируются как ссылки!
Показать ещё 8 комментариев
154

За MDN:

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

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

  • 6
    JSON.parse (JSON.stringify (a)) выглядит красиво, но перед его использованием я рекомендую сравнить время, необходимое для вашей желаемой коллекции. В зависимости от размера объекта, это может быть не самый быстрый вариант.
  • 3
    Метод JSON, который я заметил, преобразует объекты даты в строки, но не обратно в даты. Приходится иметь дело с забавными часовыми поясами в Javascript и вручную фиксировать любые даты. Могут быть аналогичные случаи для других типов, кроме дат
Показать ещё 3 комментария
120

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

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

  • Если такое наследование полезно (duh!)
  • Если исходный объект не будет изменен, что делает связь между двумя объектами без проблем.

Пример:

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.

  • 0
    Примечание: Object.create не является глубокой копией (itpastorn не упомянул рекурсию). Доказательство: var a = {b:'hello',c:{d:'world'}}, b = Object.create(a); a == b /* false */; ac == bc /* true */;
  • 0
    В дополнение к основным выявленным проблемам, это может быть опасным в более изощренной форме. Один простой способ увидеть это: Object.keys(foo) = ['a'] тогда как Object.keys(bar) = [] (однако getOwnPropertyNames работает в этой ситуации, как и ожидалось, но hasOwnProperty также неверен). Даже используя объекты в качестве карт, я буду осторожен, используя этот подход.
Показать ещё 13 комментариев
111

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

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

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;
    }
  });
}
  • 0
    Извините за глупый вопрос, но почему Object.assign принимает два параметра, когда функция- value в polyfill принимает только один параметр?
  • 0
    @Qwertie вчера Все аргументы повторяются и объединяются в один объект, приоритезируя свойства из последнего переданного аргумента
Показать ещё 3 комментария
73

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

y = _.clone(x);

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

copiedObject = _.extend({},originalObject);
  • 36
    И у Лодаша есть клон глубже
  • 2
    Благодарю. Используя эту технику на сервере Метеор.
Показать ещё 1 комментарий
72

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

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

Я хочу глубоко скопировать 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

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

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

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

заключение

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

jsfiddle

  • 7
    поэтому заключение состоит в том, чтобы избежать этой проблемы :)
  • 0
    @mikus, пока не появится настоящая спецификация, которая охватывает не только основные варианты использования, да.
Показать ещё 5 комментариев
38

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

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

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

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

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

object2.key = "a change";
console.log(object1);// returns value
  • 0
    Почему функции не принадлежат JSON? Я видел их как JSON не раз ...
  • 4
    Функции не являются частью спецификации JSON, потому что они не являются безопасным (или интеллектуальным) способом передачи данных, для чего и был создан JSON. Я знаю, что собственный кодер JSON в Firefox просто игнорирует переданные ему функции, но я не уверен в поведении других.
Показать ещё 4 комментария
31

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


Полный клон:

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

  • 1
    Это не работает ... это будет работать, вероятно, когда x будет массивом, например, x = ['ab', 'cd', ...]
  • 3
    Это работает, но имейте в виду, что это мелкая копия, поэтому любые глубокие ссылки на другие объекты остаются ссылками!
Показать ещё 1 комментарий
27

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

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

или

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

Ответ в основном зависит от того, какой 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
  • 0
    Можете ли вы привести пример того, к чему будет относиться big chunk of data ? 100kb? 100MB? Спасибо!
  • 0
    Я просто использую оператор распространения (ES6)
Показать ещё 2 комментария
22

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

var destination = angular.copy(source);

или

angular.copy(source, destination);

Подробнее в angular.copy документация...

  • 1
    Это глубокая копия FYI.
20

Ответ A.Levy почти завершен, вот мой небольшой вклад: существует способ обработки рекурсивных ссылок, см. эту строку

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

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

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

Вдохновленный исчерпывающим исследованием A.Levy и прототипом Calvin, я предлагаю это решение:

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;
}

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

  • 0
    Не работает с наследованием.
  • 3
    Date.prototype.clone = function() {return new Date(+this)};
19

Из этой статьи: Как скопировать массивы и объекты в 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.
  • 0
    Добавление к объекту прототипа, как это сломало jQuery для меня. Даже когда я переименовал в clone2.
Показать ещё 4 комментария
18

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

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

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

  • 9
    Это не глубоко клонировать объект.
  • 0
    Это задание, а не копия. clone.Title = "просто клон" означает, что obj.Title = "просто клон".
Показать ещё 3 комментария
17

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

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;
}
  • 8
    Этот ответ довольно близок, но не совсем корректен. Если вы попытаетесь клонировать объект Date, вы не получите ту же дату, потому что вызов функции конструктора Date инициализирует новую Date с текущей датой / временем. Это значение не перечисляется и не будет скопировано циклом for / in.
  • 0
    Не идеально, но хорошо для тех основных случаев. Например, допускается простое клонирование аргумента, который может быть базовым объектом, массивом или строкой.
Показать ещё 2 комментария
14

В ECMAScript 2018

let objClone = { ...obj };

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

  • 0
    Спасибо за подсказку, что вложенные объекты все еще копируются в качестве ссылки! Я чуть не сошел с ума при отладке своего кода, потому что я изменил вложенные свойства для «клона», но оригинал был изменен.
14

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

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

obj2.text = 'moo2'; // Only updates obj2 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(...) кажется, определенно подходит.
  • 0
    Идеальный ответ. Может быть, вы могли бы добавить объяснение для Object.hasOwnProperty ? Таким образом, люди знают, как предотвратить поиск ссылки на прототип.
Показать ещё 5 комментариев
13

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

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

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

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

  • 1
    это не работает, когда у вас есть функции, определенные на объектах
  • 0
    насколько я вижу, оператор распространения работает только с итерациями - developer.mozilla.org говорит: var obj = {'key1': 'value1'}; var array = [...obj]; // TypeError: obj is not iterable
Показать ещё 2 комментария
10
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

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

10

Использование Lodash:

var y = _.clone(x, true);
  • 3
    OMG, было бы безумно изобретать клонирование. Это единственный вменяемый ответ.
  • 4
    Я предпочитаю _.cloneDeep(x) поскольку по сути это то же самое, что и выше, но читается лучше.
Показать ещё 1 комментарий
9

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

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

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

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;
}
  • 0
    не работает для моего объекта, хотя мой случай немного сложен.
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;
};
  • 0
    Помогло ли вам это решение? Дайте мне знать, если вы видите какие-либо проблемы.
  • 2
    if(!src && typeof src != "object"){ . Я думаю, что это должно быть || не && .
Показать ещё 1 комментарий
6

Это адаптация кода А. Леви для обработки клонирования функций и множественных/циклических ссылок. Это означает, что если два свойства в клонированном дереве являются ссылками одного и того же объекта, клонированное дерево объектов будут иметь эти свойства, указывающие на один и тот же клон ссылочного объекта. Это также решает случай циклических зависимостей, которые, если их оставить необработанными, приводят к бесконечной петле. Сложность алгоритма 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 года это единственное правильное решение вопроса.
  • 0
    Ну, что ж, спасибо. не стесняйтесь голосовать тогда
6

Ответ Jan Turoň выше очень близок и может быть лучшим в браузере из-за проблем с совместимостью, но это может вызвать некоторые странные проблемы перечисления. Например, выполнение:

for ( var i in someArray ) { ... }

Назначит метод clone() я после итерации через элементы массива. Здесь адаптация, которая позволяет избежать перечисления и работает с node.js:

Object.defineProperty( Object.prototype, "clone", {
    value: 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;
    }
});

Object.defineProperty( Date.prototype, "clone", {
    value: function() {
        var copy = new Date();
        copy.setTime( this.getTime() );
        return copy;
    }
});

Object.defineProperty( Number.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( Boolean.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( String.prototype, "clone", { value: function() { return this; } } );

Это позволяет избежать использования метода clone(), потому что defineProperty() по умолчанию перечислит значение false.

5

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

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

var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
5

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

В Firefox результат

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

есть

{test:"test"}.

В nodejs это

{}
  • 0
    Нет, это не так. b.test возвращает.
  • 0
    Это наследование прототипа, а не клонирование.
Показать ещё 3 комментария
4

Используйте deepcopy от npm. Работает как в браузере, так и в node как модуль npm...

https://www.npmjs.com/package/deepcopy

пусть a = deepcopy (b)

4

Проконсультируйтесь с http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data для алгоритма W3C "Безопасная передача структурированных данных", который должен быть реализован браузерами для передачи данных на веб-сайт работников. Однако он имеет некоторые ограничения, поскольку он не выполняет функции. См. https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm для получения дополнительной информации, включая альтернативный алгоритм в JS, который поможет вам в этом.

  • 0
    +1 за MDN ссылку, намного легче переварить, чем стандарт.
  • 0
    Хотя здесь есть несколько замечательных ссылок, на самом деле это не ответ. Если бы он был расширен, чтобы включить реализацию упомянутых алгоритмов, это могло бы быть ответом.
4

Bellow - это моя версия глубокого клонирования, функций покрытия и обработки круговых ссылок.

https://github.com/radsimu/UaicNlpToolkit/blob/master/Modules/GGS/GGSEngine/src/main/resources/ro/uaic/info/nlptools/ggs/engine/core/jsInitCode.js#L17

  • 0
    Уверен, что ссылка мертва.
  • 0
    @PatrickRoberts - спасибо за внимание. Я обновил ответ своей версией клона, которую я в конечном итоге создал
Показать ещё 1 комментарий
3

Клонировать объект на основе "шаблона". Что вы делаете, если вам не нужна точная копия, но вам нужна надежность какой-то надежной операции клонирования, но вы хотите только клонировать клоны или хотите, чтобы вы могли контролировать существование или формат каждого значения атрибута клонировали?

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

   function isFunction(functionToCheck) {
       var getType = {};
       return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
   }

   function cloneObjectByTemplate(obj, tpl, cloneConstructor) {
       if (typeof cloneConstructor === "undefined") {
           cloneConstructor = false;
       }
       if (obj == null || typeof (obj) != 'object') return obj;

       //if we have an array, work through it contents and apply the template to each item...
       if (Array.isArray(obj)) {
           var ret = [];
           for (var i = 0; i < obj.length; i++) {
               ret.push(cloneObjectByTemplate(obj[i], tpl, cloneConstructor));
           }
           return ret;
       }

       //otherwise we have an object...
       //var temp:any = {}; // obj.constructor(); // we can't call obj.constructor because typescript defines this, so if we are dealing with a typescript object it might reset values.
       var temp = cloneConstructor ? new obj.constructor() : {};

       for (var key in tpl) {
           //if we are provided with a function to determine the value of this property, call it...
           if (isFunction(tpl[key])) {
               temp[key] = tpl[key](obj); //assign the result of the function call, passing in the value
           } else {
               //if our object has this property...
               if (obj[key] != undefined) {
                   if (Array.isArray(obj[key])) {
                       temp[key] = [];
                       for (var i = 0; i < obj[key].length; i++) {
                           temp[key].push(cloneObjectByTemplate(obj[key][i], tpl[key], cloneConstructor));
                       }
                   } else {
                       temp[key] = cloneObjectByTemplate(obj[key], tpl[key], cloneConstructor);
                   }
               }
           }
       }

       return temp;
   }

Простой способ вызвать это будет следующим образом:

var source = {
       a: "whatever",
       b: {
           x: "yeah",
           y: "haha"
       }
   };
   var template = {
       a: true, //we want to clone "a"
       b: {
           x: true //we want to clone "b.x" too
       }
   }; 
   var destination = cloneObjectByTemplate(source, template);

Если вы хотите использовать функцию, чтобы убедиться, что атрибут возвращен или чтобы убедиться, что он является определенным типом, используйте шаблон, подобный этому. Вместо использования {ID: true} мы предоставляем функцию, которая по-прежнему просто копирует атрибут ID исходного объекта, но гарантирует, что это число, даже если оно не существует на исходном объекте.

 var template = {
    ID: function (srcObj) {
        if(srcObj.ID == undefined){ return -1; }
        return parseInt(srcObj.ID.toString());
    }
}

Массивы будут клонировать, но если вы хотите, чтобы ваша собственная функция также обрабатывала эти отдельные атрибуты, и делайте что-то особенное:

 var template = {
    tags: function (srcObj) {
        var tags = [];
        if (process.tags != undefined) {
            for (var i = 0; i < process.tags.length; i++) {

                tags.push(cloneObjectByTemplate(
                  srcObj.tags[i],
                  { a : true, b : true } //another template for each item in the array
                );
            }
        }
        return tags;
    }
 }

Итак, в приведенном выше примере наш шаблон просто копирует атрибут "теги" исходного объекта, если он существует (он считается массивом), и для каждого элемента в этом массиве функция клонирования вызывается для индивидуального клонирования на основе второго шаблона, который просто копирует атрибуты "a" и "b" каждого из этих элементов тега.

Если вы берете объекты из node и вы хотите контролировать, какие атрибуты этих объектов клонированы, это отличный способ контролировать это в node.js, а код работает и в браузере.

Вот пример использования: http://jsfiddle.net/hjchyLt1/

3

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

Сначала создайте функцию, которая возвращает объект

function template() {
  return {
    values: [1, 2, 3],
    nest: {x: {a: "a", b: "b"}, y: 100}
  };
}

Затем создайте простую функцию мелкой копии

function copy(a, b) {
  Object.keys(b).forEach(function(key) {
    a[key] = b[key];
  });
}

Создайте новый объект и скопируйте его на него.

var newObject = {}; 
copy(newObject, template());

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

var newObject = template();

Теперь, когда у вас есть новый объект, проверьте его свойства:

console.log(Object.keys(newObject));

Отображается:

["values", "nest"]

Да, это собственные свойства newObject, а не ссылки на свойства на другом объекте. Позвольте просто проверить:

console.log(newObject.nest.x.b);

Отображается:

"b"

newObject приобрел все свойства объекта шаблона, но не имеет никакой цепи зависимостей.

http://jsbin.com/ISUTIpoC/1/edit?js,console

Я добавил этот пример, чтобы обсудить некоторые дебаты, поэтому, пожалуйста, добавьте несколько комментариев:)

  • 1
    Object.keys не был реализован до JavaScript 1.8.5, то есть он недоступен в IE 8 и других устаревших браузерах. Поэтому, хотя этот ответ будет отлично работать в современных браузерах, он не будет работать в IE 8. Поэтому, если вы используете этот метод, вы должны использовать правильно эмулирующий полифилл Object.keys.
2

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

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

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

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

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

  • 0
    Хотя если мне разрешено импортировать lodash, я предпочитаю использовать lodash cloneDeep .
  • 1
    Я использую JSON.parse (JSON.stringify (source)). Всегда работает.
Показать ещё 2 комментария
2

Я пробовал это в случае скалярного объекта, и он работает для меня:

function binder(i) {
  return function () {
    return i;
  };
}

a=1;
b=binder(a)(); // copy value of a into b

alert(++a); // 2
alert(b); // still 1

С уважением.

  • 0
    a=1;b=a;alert(++a);alert(b); // still 1
2

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

Если существуют круговые зависимости (т.е. два под-объекта связаны друг с другом), вы отвратителен, поскольку (с теоретической точки зрения) нет способа решить эту проблему издавать элегантно.

  • 2
    На самом деле, сериализация объектов Python обрабатывает циклические ссылки, отслеживая узлы в графе объектов, который он уже обработал. Вы можете использовать этот подход для реализации надежной процедуры копирования. Это было бы немного больше работы, хотя!
1

Согласно Руководству по стилю JavaScript Airbnb с 404 участниками:

Предпочитаете оператор распространения объекта по объекту Object.assign для объектов с мелкой копией. Используйте оператор останова объекта, чтобы получить новый объект с пропущенными некоторыми свойствами.

// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates 'original' ಠ_ಠ
delete copy.a; // so does this

// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }

// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }

const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

Также я хотел бы предупредить вас, что, хотя Airbnb вряд ли рекомендует использовать оператор с расширенным использованием объекта. Имейте в виду, что Microsoft Edge до сих пор не поддерживает эту функцию 2018.

ES2016+ Таблица совместимости >>

1

Здесь современное решение, которое не имеет ловушек Object.assign() (не копирует по ссылке):

const cloneObj = (obj) => {
    return Object.keys(obj).reduce((dolly, key) => {
        dolly[key] = (obj[key].constructor === Object) ?
            cloneObj(obj[key]) :
            obj[key];
        return dolly;
    }, {});
};
1

Я не знаю, в каких случаях это не работает, но он достал мне копию массива. Я думаю, что это мило:) Надеюсь, он поможет

copiedArr = origArr.filter(function(x){return true})
1

Хорошо, поэтому это может быть самый лучший вариант для мелкого копирования. Если следует множество примеров с использованием assign, но также сохраняет наследование и прототип. Это так просто и работает для большинства объектов массива и объектов, кроме тех, которые содержат требования к конструктору или свойства только для чтения. Но это означает, что он терпит неудачу в TypedArrays, RegExp, Date, Maps, Sets и объектных версиях примитивов (Boolean, String и т.д.).

function copy ( a ) { return Object.assign( new a.constructor, a ) }

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

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

получается из базового встроенного объекта и массива...

> a = { a: 'A', b: 'B', c: 'C', d: 'D' }
{ a: 'A', b: 'B', c: 'C', d: 'D' }
> b = copy( a )
{ a: 'A', b: 'B', c: 'C', d: 'D' }
> a = [1,2,3,4]
[ 1, 2, 3, 4 ]
> b = copy( a )
[ 1, 2, 3, 4 ]

И терпит неудачу из-за средних get/setters, конструктора требуются аргументы или свойства только для чтения, а также грехи против отца.

> a = /\w+/g
/\w+/g
> b = copy( a )  // fails because source and flags are read-only
/(?:)/
> a = new Date ( '1/1/2001' )
2000-12-31T16:00:00.000Z
> b = copy( a )  // fails because Date using methods to get and set things
2017-02-04T14:44:13.990Z
> a = new Boolean( true )
[Boolean: true]
> b = copy( a )  // fails because of of sins against the father
[Boolean: false]
> a = new Number( 37 )
[Number: 37]
> b = copy( a )  // fails because of of sins against the father
[Number: 0]
> a = new String( 'four score and seven years ago our four fathers' )
[String: 'four score and seven years ago our four fathers']
> b = copy( a )  // fails because of of sins against the father
{ [String: ''] '0': 'f', '1': 'o', '2': 'u', '3': 'r', '4': ' ', '5': 's', '6': 'c', '7': 'o', '8': 'r', '9': 'e', '10': ' ', '11': 'a', '12': 'n', '13': 'd', '14': ' ', '15': 's', '16': 'e', '17': 'v', '18': 'e', '19': 'n', '20': ' ', '21': 'y', '22': 'e', '23': 'a', '24': 'r', '25': 's', '26': ' ', '27': 'a', '28': 'g', '29': 'o', '30': ' ', '31': 'o', '32': 'u', '33': 'r', '34': ' ', '35': 'f', '36': 'o', '37': 'u', '38': 'r', '39': ' ', '40': 'f', '41': 'a', '42': 't', '43': 'h', '44': 'e', '45': 'r', '46': 's' } 
1

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

И недооцененная WeakMap приходит к проблеме циклов, в которой сохранение пар ссылок на старый и новый объекты может помочь нам легко воссоздать цельное дерево.

Я предотвратил глубокое клонирование элементов DOM, возможно, вы не хотите клонировать всю страницу:)

function deepCopy(object) {
    const cache = new WeakMap(); // Map of old - new references

    function copy(obj) {
        if (typeof obj !== 'object' ||
            obj === null ||
            obj instanceof HTMLElement
        )
            return obj; // primitive value or HTMLElement

        if (obj instanceof Date) 
            return new Date().setTime(obj.getTime());

        if (obj instanceof RegExp) 
            return new RegExp(obj.source, obj.flags);

        if (cache.has(obj)) 
            return cache.get(obj);

        const result = obj instanceof Array ? [] : {};

        cache.set(obj, result); // store reference to object before the recursive starts

        if (obj instanceof Array) {
            for(const o of obj) {
                 result.push(copy(o));
            }
            return result;
        }

        const keys = Object.keys(obj); 

        for (const key of keys)
            result[key] = copy(obj[key]);

        return result;
    }

    return copy(object);
}

Некоторые тесты:

// #1
const obj1 = { };
const obj2 = { };
obj1.obj2 = obj2;
obj2.obj1 = obj1; // Trivial circular reference

var copy = deepCopy(obj1);
copy == obj1 // false
copy.obj2 === obj1.obj2 // false
copy.obj2.obj1.obj2 // and so on - no error (correctly cloned).

// #2
const obj = { x: 0 }
const clone = deepCopy({ a: obj, b: obj });
clone.a == clone.b // true

// #3
const arr = [];
arr[0] = arr; // A little bit weird but who cares
clone = deepCopy(arr)
clone == arr // false;
clone[0][0][0][0] == clone // true;

ПРИМЕЧАНИЕ. Я использую константы для оператора loop, = > и WeakMaps для создания более важного кода. Этот синтаксис (ES6) поддерживается сегодня браузерами

0

Использование значений по умолчанию (исторически специфично для nodejs, но теперь доступно из браузера благодаря современному JS):

import defaults from 'object.defaults';

const myCopy = defaults({}, myObject);
0

(Ниже приводится в основном интеграция ответов @A. Levy и @Jan Turoň с комментариями @RobG, большое им спасибо!)

функция

function clone(obj) {

  if ([null, undefined].includes(obj)) return obj

  if (obj instanceof Node) return obj.cloneNode(true)

  let _obj // The clone of obj

  switch (obj.constructor) {
    case Object:
      _obj = Object.create(
        Object.getPrototypeOf(obj),
        Object.getOwnPropertyDescriptors(obj.constructor).value
      ) // Set the [[Prototype]] for the prototypal inheritance
        Object.assign(_obj, obj) // Utilize this built-in method to copy properties that are primitive types, especially for non-enumerable (e.g. Symbol)

      for (const [key, val] of Object.entries(obj)) {
        if (obj.hasOwnProperty(key)) { // Prevent duplicate the properties of the prototype
          val === obj // Handle infinite recursive references (or circular structures)
          ? _obj[key] = _obj
          : _obj[key] = clone(val)
        }
      }
      break

    case Array:
      _obj = [...obj] // Non-reference copying to prevent subsequent interference with the original object
      break

    case Date:
      _obj = new Date(+obj)
      break

    default:
      _obj = obj
      break
  }

  return _obj
}

тесты

obj0 = {

  u: undefined,

  nul: null,

  t: true,

  n: 9,

  str1: "string",

  str2: "",

  sym: Symbol("symbol"),

  [Symbol("e")]: Math.E,

  function(params) {},

  o: {
    n: 0,
    o: {
      f: function(args) {}
    }
  },

  arr: [0, 1],

  d: new Date()
}


obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o = {
  g: function(g) {}
}
obj1.arr[0] = 1
obj1.d.setTime(+obj0.d + 60*1000)

console.log("-".repeat(2**6))



console.log(">:>: Test - routinely")

console.log("obj0:\n ",obj0)
console.log("obj1:\n ",obj1)
console.log("obj0 has not been interfered:\n ",JSON.stringify(obj0))
console.log("obj1:\n ",JSON.stringify(obj1))

console.log("-".repeat(2**6))



console.log(">:>: Test - infinite recursion")

obj0.o.recursion = obj0.o
obj1.o.recursion = null
console.log("obj0:\n ",obj0)
console.log("obj1:\n ",obj1)

console.log("-".repeat(2**6))



console.log(">:>: Test - function objects")

Person = function(name) {
  this.name = name
}

Boy = function() {}

Boy.prototype = Object.create(Person.prototype)
Boy.prototype.sex = "M"

boy = Object.create(Boy.prototype)

console.log("boy:",boy)
console.log("clone(boy):",clone(boy))
  • 0
    Не могли бы вы объяснить, как этот код: Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj.constructor).value) сравнивается с Object.create(toClone); Object.assign(clonedObj, toClone) ?
  • 1
    (2) Object.assign() - быстрая, но поверхностная копия, первый слой объекта копируется по значению, второй и более глубокий копируются . Первоначально я использовал его для целесообразного копирования примитивных типов, но, поскольку он почти полностью дублирован, теперь он удален, спасибо за указание ~ @LeviRoberts
Показать ещё 2 комментария
0

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

Проблема в том, что <element> имеет parent и child атрибуты, которые ссылаются на другие элементы с parent и child значениями, которые указывают на исходный <element>, вызывая либо бесконечную рекурсивную, либо циклическую избыточность.

Если ваш объект является чем-то безопасным и простым,

{
    '123':456
}

... тогда любой другой ответ здесь, вероятно, сработает.

Но если у вас есть...

{
    '123':<reactJSComponent>,
    '456':document.createElement('div'),
}

... тогда вам нужно что-то вроде этого:

    // cloneVariable() : Clone variable, return null for elements or components.
var cloneVariable = function (args) {
    const variable = args.variable;

    if(variable === null) {
            return null;
    }

    if(typeof(variable) === 'object') {
            if(variable instanceof HTMLElement || variable.nodeType > 0) {
                    return null;
            }

            if(Array.isArray(variable)) {
                    var arrayclone = [];

                    variable.forEach((element) => {
                            arrayclone.push(cloneVariable({'variable':element}));
                    });

                    return arrayclone;
            }

            var objectclone = {};

            Object.keys(variable).forEach((field) => {
                    objectclone[field] = cloneVariable({'variable':variable[field]});
            });

            return objectclone;
    }

    return variable;
}
0

Вы можете клонировать свой объект без изменения родительского объекта Object -

    /** [Object Extend]*/
    ( typeof Object.extend === 'function' ? undefined : ( Object.extend = function ( destination, source ) {
        for ( var property in source )
            destination[property] = source[property];
        return destination;
    } ) );
    /** [/Object Extend]*/
    /** [Object clone]*/
    ( typeof Object.clone === 'function' ? undefined : ( Object.clone = function ( object ) {
        return this.extend( {}, object );
    } ) );
    /** [/Object clone]*/

    let myObj = {
        a:1, b:2, c:3, d:{
            a:1, b:2, c:3
        }
    };

    let clone = Object.clone( myObj );

    clone.a = 10;

    console.log('clone.a==>', clone.a); //==> 10

    console.log('myObj.a==>', myObj.a); //==> 1 // object not modified here

    let clone2 = Object.clone( clone );

    clone2.a = 20;

    console.log('clone2.a==>', clone2.a); //==> 20

    console.log('clone.a==>', clone.a); //==> 10 // object not modified here
0

Если вы используете TypeScript, вам необходимо поддерживать более старые веб-браузеры (и поэтому не можете использовать Object.assign) и не используете библиотеку с встроенным методом клонирования, вы можете сделать себя помощником combine в нескольких строках код. Он объединяет объекты, и если у вас есть только один, он просто клонирует его.

/** Creates a new object that combines the properties of the specified objects. */
function combine(...objs: {}[]) {
    const combined = {};
    objs.forEach(o => Object.keys(o).forEach(p => combined[p] = o[p]));
    return combined;
}
0

Если ваш объект является классом (например, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes):

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

Затем в copiedObject у вас есть экземпляр originalObject с глубоким копированием со всеми его методами.

0

Хорошо, я знаю, что у него много ответов, но никто не указал, что EcmaScript5 имеет метод назначения, работу с FF и Chrome, он копирует перечисленные и собственные свойства и символы.

Назначить объект

  • 1
    Нет поддержки в IE :(
  • 1
    Почему вам нужно поддерживать браузер, который больше не существует? Серьезно, вам не нужно беспокоиться, по крайней мере, это то, что мы делаем здесь, просто не поддержите и не предупредите пользователя, если он использует какую-либо версию IE.
Показать ещё 2 комментария
0

Чтобы обрабатывать круглые объекты, которые не может обрабатывать JSON.stringify, вы можете ввести библиотеку под названием JSOG, которая сериализует и десериализует произвольные графики в формате JSON.

var clone = JSOG.parse(JSOG.stringify(original));

Также может быть интересно попробовать исправить JSOG для клонирования с помощью этого трюка (на данный момент у вас нет времени, но если кто-то хочет сделать снимок...):

Сериализовать простую функцию:

foo.f = function(a) { return a }
var stringForm = foo.f.toString() // "function (a) { return a }"

Отключить функцию:

eval("foo.f = " + stringForm)

Необходимы некоторые условные обозначения (возможно, имя свойства) для идентификации функций с регулярными строками (возможно, @func_f).

Конечно, если функция вызывает вторую функцию, вторая функция должна существовать так же, как и для оригинала.

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

Отказ от ответственности: Я не тестировал скорость JSOG stringify/parse vs JSON stringify/parse, но он работает с простыми (круговыми) объектами, с которыми я тестировал его.

0

Проблема с копированием объекта, который, в конечном итоге, может указывать на себя, может быть решена простой проверкой. Добавьте эту проверку, каждый раз, когда есть действие копирования. Он может быть медленным, но он должен работать.

Я использую функцию toType(), чтобы явно указать тип объекта. У меня также есть моя собственная функция copyObj(), которая довольно похожа на логику, которая отвечает на все три случая Object(), Array() и Date().

Я запускаю его в NodeJS.

НЕ ИСПРАВЛЕНО, ДА.

// Returns true, if one of the parent children is the target.
// This is useful, for avoiding copyObj() through an infinite loop!
function isChild(target, parent) {
  if (toType(parent) == '[object Object]') {
    for (var name in parent) {
      var curProperty = parent[name];

      // Direct child.
      if (curProperty = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curProperty) == '[object Object]' || toType(curProperty) == '[object Array]') {
        if (isChild(target, curProperty)) return true;
      }
    }
  } else if (toType(parent) == '[object Array]') {
    for (var i=0; i < parent.length; i++) {
      var curItem = parent[i];

      // Direct child.
      if (curItem = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curItem) == '[object Object]' || toType(curItem) == '[object Array]') {
        if (isChild(target, curItem)) return true;
      }
    }
  }

  return false;     // Not the target.
}
0

Если у вас есть объект с функциями, вы можете сделать это с помощью JSONfn, см. http://www.eslinstructor.net/jsonfn/.

var obj= {
    name:'Marvin',
    getName :  function(){
      return this.name;
    }
}
var cobj = JSONfn.parse(JSONfn.stringify(obj));
0

Я пришел на эту страницу из-за того же вопроса, но я не использую JQuery, и ни один из методов clone-методов не работал для моих собственных объектов.

Я знаю, что мой ответ не слишком сильно связан с этим вопросом, потому что это другой подход. Вместо использования функций clone я использую функцию create. Он работал у меня для следующих (к сожалению, ограничивающих) целей:

  • Я использую в основном JSP-сгенерированный Javascript
  • В начале я знаю, какой объект должен быть сгенерирован (в моем случае это информация из базы данных, которая получает один раз и должна быть развернута чаще в JS.

Сначала я определил мои объекты следующим образом:

var obj= new Object();
obj.Type='Row';
obj.ID=1;
obj.Value='Blah blah';

Теперь я переместил все как:

function getObjSelektor(id_nummer,selected){
var obj = document.createElement("select");
obj.setAttribute("id","Selektor_"+id_nummer);
obj.setAttribute("name","Selektor");
obj.setAttribute("size","1");

var obj_opt_1 = document.createElement("option");
obj_opt_1.setAttribute("value","1");
if(1==selected)
    posopval_opt_1.setAttribute("selected","selected");
obj_opt_1.innerHTML="Blah blah";
obj.appendChild(obj_opt_1);

var obj_opt_2 = document.createElement("option");
obj_opt_2.setAttribute("value","2");
if(2==selected)
    obj_opt_2.setAttribute("selected","selected");
obj_opt_2.innerHTML="2nd Row";
obj.appendChild(obj_opt_2);

...

return obj;
}

И вызовите функцию в обычном коде:

myDiv.getObjSelektor(getObjSelektor(anotherObject.ID));

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

0
//
// creates 'clone' method on context object
//
//  var 
//     clon = Object.clone( anyValue );
//
!((function (propertyName, definition) {
    this[propertyName] = definition();
}).call(
    Object,
    "clone",
    function () {
        function isfn(fn) {
            return typeof fn === "function";
        }

        function isobj(o) {
            return o === Object(o);
        }

        function isarray(o) {
            return Object.prototype.toString.call(o) === "[object Array]";
        }

        function fnclon(fn) {
            return function () {
                fn.apply(this, arguments);
            };
        }

        function owns(obj, p) {
            return obj.hasOwnProperty(p);
        }

        function isemptyobj(obj) {
            for (var p in obj) {
                return false;
            }
            return true;
        }

        function isObject(o) {
            return Object.prototype.toString.call(o) === "[object Object]";
        }
        return function (input) {
            if (isfn(input)) {
                return fnclon(input);
            } else if (isobj(input)) {
                var cloned = {};
                for (var p in input) {
                    owns(Object.prototype, p)
                    || (
                        isfn(input[p])
                        && ( cloned[p] = function () { return input[p].apply(input, arguments); } )
                        || ( cloned[p] = input[p] )
                    );
                }
                if (isarray(input)) {
                    cloned.length = input.length;
                    "concat every filter forEach indexOf join lastIndexOf map pop push reduce reduceRight reverse shift slice some sort splice toLocaleString toString unshift"
                    .split(" ")
                    .forEach(
                      function (methodName) {
                        isfn( Array.prototype[methodName] )
                        && (
                            cloned[methodName] =
                            function () {
                                return Array.prototype[methodName].apply(cloned, arguments);
                            }
                        );
                      }
                    );
                }
                return isemptyobj(cloned)
                       ? (
                          isObject(input)
                          ? cloned
                          : input
                        )
                       : cloned;
            } else {
                return input;
            }
        };
    }
));
//
  • 1
    Почему этот ответ будет лучше, чем любой другой?
  • 0
    Это не лучше, чем другие, хотя это довольно хорошо, я думаю ...
Показать ещё 2 комментария
0

В моем коде я часто определяю функцию (_) для обработки копий, чтобы я мог передавать "по значению" функции. Этот код создает глубокую копию, но сохраняет наследование. Он также отслеживает суб-копии, так что самореферентные объекты могут быть скопированы без бесконечного цикла. Не стесняйтесь использовать его.

Это может быть не самый элегантный, но он еще не подвел меня.

_ = function(oReferance) {
  var aReferances = new Array();
  var getPrototypeOf = function(oObject) {
    if(typeof(Object.getPrototypeOf)!=="undefined") return Object.getPrototypeOf(oObject);
    var oTest = new Object();
    if(typeof(oObject.__proto__)!=="undefined"&&typeof(oTest.__proto__)!=="undefined"&&oTest.__proto__===Object.prototype) return oObject.__proto__;
    if(typeof(oObject.constructor)!=="undefined"&&typeof(oTest.constructor)!=="undefined"&&oTest.constructor===Object&&typeof(oObject.constructor.prototype)!=="undefined") return oObject.constructor.prototype;
    return Object.prototype;
  };
  var recursiveCopy = function(oSource) {
    if(typeof(oSource)!=="object") return oSource;
    if(oSource===null) return null;
    for(var i=0;i<aReferances.length;i++) if(aReferances[i][0]===oSource) return aReferances[i][1];
    var Copy = new Function();
    Copy.prototype = getPrototypeOf(oSource);
    var oCopy = new Copy();
    aReferances.push([oSource,oCopy]);
    for(sPropertyName in oSource) if(oSource.hasOwnProperty(sPropertyName)) oCopy[sPropertyName] = recursiveCopy(oSource[sPropertyName]);
    return oCopy;
  };
  return recursiveCopy(oReferance);
};

// Examples:
Wigit = function(){};
Wigit.prototype.bInThePrototype = true;
A = new Wigit();
A.nCoolNumber = 7;
B = _(A);
B.nCoolNumber = 8; // A.nCoolNumber is still 7
B.bInThePrototype // true
B instanceof Wigit // true
0

Из Правила кодирования JavaScript Apple:

// Create an inner object with a variable x whose default
// value is 3.
function innerObj()
{
        this.x = 3;
}
innerObj.prototype.clone = function() {
    var temp = new innerObj();
    for (myvar in this) {
        // this object does not contain any objects, so
        // use the lightweight copy code.
        temp[myvar] = this[myvar];
    }
    return temp;
}

// Create an outer object with a variable y whose default
// value is 77.
function outerObj()
{
        // The outer object contains an inner object.  Allocate it here.
        this.inner = new innerObj();
        this.y = 77;
}
outerObj.prototype.clone = function() {
    var temp = new outerObj();
    for (myvar in this) {
        if (this[myvar].clone) {
            // This variable contains an object with a
            // clone operator.  Call it to create a copy.
            temp[myvar] = this[myvar].clone();
        } else {
            // This variable contains a scalar value,
            // a string value, or an object with no
            // clone function.  Assign it directly.
            temp[myvar] = this[myvar];
        }
    }
    return temp;
}

// Allocate an outer object and assign non-default values to variables in
// both the outer and inner objects.
outer = new outerObj;
outer.inner.x = 4;
outer.y = 16;

// Clone the outer object (which, in turn, clones the inner object).
newouter = outer.clone();

// Verify that both values were copied.
alert('inner x is '+newouter.inner.x); // prints 4
alert('y is '+newouter.y); // prints 16

Стив

  • 4
    Атрибуты объекта, у которых нет метода клонирования, будут скопированы этим кодом. Таким образом, изменения в оригинале могут повлиять на копию. Так что это не решит проблему.
-1

Использование Prototype framework:

var y = Object.clone(x);

Учитывая clone реализация:

...
Object.extend = function(destination, source) {
  for (var property in source)
    destination[property] = source[property];
  return destination;
};
...
clone: function(object) {
    return Object.extend({ }, object);
  }
...
-2

Мои любимые и элегантные объекты JS clone -

function CloneObject() {}
function cloneObject(o) {
   CloneObject.prototype = o;
   return new CloneObject();
}

Используйте cloneObject(object), чтобы получить клон объекта JS.

В отличие от многих решений copy, этот клон поддерживает отношения прототипов в клонированном объекте.

  • 1
    Я думаю, что это не ответить на вопрос, опубликованный оп. code var o1 = {a: 1} var o2 = cloneObject (o1) o2.b = 2 console.log (o1) // будет code {a: 1, b: 2}
  • 1
    Это то же самое, что и Object.create .
Показать ещё 5 комментариев
-3
function clone(obj)
{
    var cloneObj = Object.create(obj);

    return cloneObj;
}

В объектах Javascript индивидуально наследуется другой объект (Prototypal inheritance). Object.create(obj) возвращает объект, являющийся под-объектом или дочерним объектом obj. В приведенной выше функции он будет эффективно возвращать копию объекта.

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

  • 1
    Это наследование прототипа, а не клонирование. Новый объект не имеет своих собственных свойств, он просто использует свойства прототипа.
  • 0
    @ d13 Мне не нужны новые свойства нового объекта, я просто хочу свойства родительского объекта нового, так почему бы не наследовать?
Показать ещё 4 комментария
-3

В чистом JS вы не получите более элегантного, чем это:

function copy(variable)
{
    var newVariable= {};
    for (var i in variable)
        newVariable[i] = variable[i];

    return newVariable;
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню