Який найефективніший спосіб глибокого клонування об’єкта в JavaScript?


5180

Який найефективніший спосіб клонування об’єкта JavaScript? Я бачив, obj = eval(uneval(o));як використовується, але це нестандартно і підтримується лише Firefox .

Я робив такі речі, obj = JSON.parse(JSON.stringify(o));але сумніваюся в ефективності.

Я також бачив рекурсивні функції копіювання з різними вадами.
Я здивований, що не існує канонічного рішення.


566
Евал - не зло. Використовувати eval погано є. Якщо ви боїтесь його побічних ефектів, ви неправильно використовуєте. Побічні ефекти, яких ви боїтесь, є причинами його використання. Хтось до речі насправді відповів на ваше запитання?
Джеймс

15
Клонування об'єктів - справа хитра, особливо зі спеціальними об'єктами довільних колекцій. Що, мабуть, тому не існує позашляхового способу зробити це.
b01

12
eval()як правило, погана ідея, оскільки багато оптимізаторів Javascript двигуна повинні вимкнутись при роботі зі змінними, які встановлюються черезeval . Просто наявність eval()у вашому коді може призвести до зниження продуктивності.
користувач56reinstatemonica8


12
Зауважте, що JSONметод втратить будь-які типи Javascript, які не мають еквівалента в JSON. Наприклад: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))буде генерувати{a: null, b: null, c: null, g: false}
oriadam

Відповіді:


4731

Рідне глибоке клонування

Це називається "структуроване клонування", експериментально працює у Вузлі 11 та пізніших версіях, і, сподіваємось, він потрапить у браузери. Дивіться цю відповідь для отримання більш детальної інформації.

Швидке клонування із втратою даних - JSON.parse / stringify

Якщо ви не використовуєте Dateс, функцією, undefined, Infinity, Regexps, карти, набори, Blobs, списки файлів, ImageDatas розріджених масивів, типізовані масиви або іншими складними типами в межах вашого об'єкта, дуже простий один вкладиш до глибоких клонувати об'єкт:

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'
  re: /.*/,  // lost
}
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()

Дивіться відповідь Корбана щодо орієнтирів.

Надійне клонування за допомогою бібліотеки

Оскільки об'єкти клонування не тривіальні (складні типи, кругові посилання, функція тощо), більшість основних бібліотек надають функцію клонування об'єктів. Не вигадуйте колесо - якщо ви вже використовуєте бібліотеку, перевірте, чи є у нього функція клонування об’єктів. Наприклад,

  • лодаш - cloneDeep; можна імпортувати окремо через модуль lodash.clonedeep і, мабуть, ваш найкращий вибір, якщо ви вже не використовуєте бібліотеку, яка забезпечує функцію глибокого клонування
  • AngularJS - angular.copy
  • jQuery - jQuery.extend(true, { }, oldObject); .clone()тільки клонує елементи DOM

ES6

Для повноти відзначить , що ES6 пропонує два неглибоко копіювання механізмів: Object.assign()і синтаксис поширення . яка копіює значення всіх перелічених власних властивостей з одного об'єкта на інший. Наприклад:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax

7
@TentistMaster github.com/jquery/jquery/blob/master/src/core.js у рядку 276 (є трохи коду, який робить щось інше, але код для "як це зробити в JS" є :)
Руна ФС

7
Ось код JS за глибокою копією jQuery для всіх, хто цікавиться: github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W

194
Вуа! Просто, щоб бути зрозумілим: не знаю, чому ця відповідь була обрана правильною відповіддю, це була відповідь на відповіді, наведені нижче: stackoverflow.com/a/122190/6524 (що було рекомендувати .clone(), що не є правильним кодом використання в цьому контексті). На жаль, це питання пережило стільки змін, що первісна дискусія вже навіть не очевидна! Будь ласка, просто дотримуйтесь порад Корбана і напишіть цикл або скопіюйте властивості безпосередньо на новий об’єкт, якщо вам важлива швидкість. Або випробувати це на собі!
Джон Резіг

9
Це питання JavaScript (жодної згадки про jQuery).
gphilip

60
Як би це зробити, не використовуючи jQuery?
Awesomeness01

2264

Ознайомтеся з цим еталоном: http://jsben.ch/#/bWfk9

У попередніх тестах, де швидкість була головною проблемою, я виявив

JSON.parse(JSON.stringify(obj))

бути найповільнішим способом глибокого клонування об'єкта (він повільніше, ніж jQuery.extend, при цьому deepпрапор встановлено на 10-20%).

jQuery.extend відбувається досить швидко, коли deepпрапор встановлений у false(дрібний клон). Це хороший варіант, оскільки він включає додаткову логіку для перевірки типу і не копіює невизначені властивості тощо, але це також трохи сповільнить вас.

Якщо ви знаєте структуру об'єктів, які ви намагаєтесь клонувати, або можете уникнути глибоких вкладених масивів, ви можете написати простий for (var i in obj)цикл для клонування вашого об'єкта під час перевірки hasOwnProperty, і це буде набагато швидше, ніж jQuery.

Нарешті, якщо ви намагаєтесь клонувати відому структуру об’єкта в гарячому циклі, ви можете отримати ВЕЛИКОШЕ ДІЛЬНЕ ВИКОНАННЯ, просто вклавши процедуру клонування та вручну побудувавши об’єкт.

for..inПрослідковування двигунів JavaScript всмоктує оптимізацію циклів, а перевірка hasOwnProperty також сповільнить вас. Ручний клон, коли швидкість є абсолютно необхідною.

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

Остерігайтеся використання JSON.parse(JSON.stringify(obj))методу на Dateоб'єктах - JSON.stringify(new Date())повертає рядкове подання дати у форматі ISO, яке JSON.parse() не конвертує назад у Dateоб'єкт. Дивіться цю відповідь для отримання більш детальної інформації .

Крім того, зауважте, що в Chrome 65, як мінімум, рідне клонування - це не такий шлях. За словами JSPerf, виконувати вроджене клонування шляхом створення нової функції майже на 800 разів повільніше, ніж використання JSON.stringify, який неймовірно швидкий у всьому світі.

Оновлення для ES6

Якщо ви використовуєте Javascript ES6, спробуйте цей нативний метод для клонування або дрібної копії.

Object.assign({}, obj);

4
@trysis Object.create не клонує об’єкт, використовує об’єкт-прототип ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser

105
Цей метод також видалить keysз ваших object, які мають functionsсвої значення, оскільки JSONвони не підтримують функції.
Карлен Кішмірян

39
Також пам’ятайте, що використання JSON.parse(JSON.stringify(obj))об’єктів дати також перетворить дату назад у UTC у рядковій формі у форматі ISO8601 .
dnlgmzddr

31
Підхід JSON також задавлюється циркулярними посиланнями.
багатий ремер

28
@velop, Object.assign ({}, objToClone) здається, що він робить неглибокий клон, хоча - використовуючи його під час гри на консолі інструментів розробки, клон об'єкта все ще вказував на посилання на клонований об’єкт. Тому я не думаю, що це насправді застосовно.
Гаррет Сімпсон

473

Якщо припустити, що у вашому об’єкті є лише змінні, а не будь-які функції, ви можете просто використовувати:

var newObject = JSON.parse(JSON.stringify(oldObject));

86
Підхід цього підходу, як я щойно з’ясував, полягає в тому, що якщо у вашого об’єкта є якісь функції (у мене є внутрішні геттери та сетери), вони втрачаються при пошануванні. Якщо це все, що вам потрібно, цей метод добре
Марківе

31
@Jason, Причина, чому цей метод повільніше, ніж дрібне копіювання (на глибокому об'єкті), полягає в тому, що цей метод, за визначенням, копіюється. Але оскільки JSONвін реалізований у власному коді (у більшості браузерів), це буде значно швидше, ніж використання будь-якого іншого рішення для глибокого копіювання на основі JavaScript, а іноді може бути і швидшим, ніж метод дрібного копіювання на основі JavaScript (див.: Jsperf.com/cloning -ан-об’єкт / 79 ).
MiJyn

35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer

32
ця методика знищить також усі Dateоб'єкти, що зберігаються всередині об'єкта, перетворивши їх у строкову форму.
fstab

13
Не вдасться скопіювати все, що не входить до специфікації JSON ( json.org )
cdmckay

397

Структуроване клонування

Стандарт HTML включає внутрішній структурований алгоритм клонування / серіалізації який може створювати глибокі клони об'єктів. Він все ще обмежений певними вбудованими типами, але крім кількох типів, що підтримуються JSON, він також підтримує дати, RegExps, карти, набори, блоби, списки файлів, ImageDatas, розріджені масиви, набрані масиви та, можливо, більше в майбутньому . Він також зберігає посилання в межах клонованих даних, що дозволяє йому підтримувати циклічні та рекурсивні структури, які можуть спричинити помилки для JSON.

Підтримка в Node.js: Експериментальна 🙂

На v8даний момент модуль в Node.js (станом на вузол 11) безпосередньо піддається структурованому API серіалізації , але ця функція все ще позначена як "експериментальна" і підлягає зміні або видаленню в наступних версіях. Якщо ви користуєтеся сумісною версією, клонування об’єкта настільки ж просто:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

Пряма підтримка в браузерах: можливо, зрештою? 😐

Наразі браузери не надають прямий інтерфейс для структурованого алгоритму клонування, але глобальна structuredClone()функція обговорювалася в whatwg / html # 793 на GitHub . Як зараз пропонується, використовувати його для більшості цілей було б так само просто:

const clone = structuredClone(original);

Якщо це не постачається, структуровані клонування браузерів реалізуються лише побічно.

Асинхронний спосіб вирішення: Корисний. 😕

Нижній спосіб створення структурованого клону з існуючими API - це розміщення даних через один порт MessageChannels . Інший порт передаватиме messageподію зі структурованим клоном доданого файлу .data. На жаль, прослуховування цих подій обов'язково є асинхронним, а синхронні альтернативи менш практичні.

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

Приклад використання:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Синхронні обходи: жахливо! 🤢

Немає хороших варіантів створення структурованих клонів синхронно. Ось декілька непрактичних хак натомість.

history.pushState()і history.replaceState()обидва створюють структурований клон свого першого аргументу і присвоюють це значення history.state. Ви можете використовувати це, щоб створити структурований клон будь-якого подібного об'єкта:

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

Приклад використання:

Хоча це синхронно, це може бути надзвичайно повільним. Він несе всі накладні витрати, пов'язані з маніпулюванням історією браузера. Виклик цього методу неодноразово може спричинити тимчасовий відмова Chrome.

NotificationКонструктор створює структурований клон пов'язані з ним даними. Він також намагається відобразити сповіщення браузера користувачеві, але це мовчки не вдасться, якщо ви не попросите дозволу на сповіщення. Якщо у вас є дозвіл для інших цілей, ми негайно закриємо створене нами повідомлення.

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

Приклад використання:


3
@rynah Я просто знову переглянув специфікацію, і ти маєш рацію: history.pushState()і history.replaceState()методи, і синхронно налаштовані history.stateна структурований клон першого аргументу. Трохи дивно, але це працює. Зараз я актуалізую свою відповідь.
Джеремі Бенкс

40
Це просто так неправильно! Цей API не призначений для використання таким чином.
Фардін К.

209
Як хлопець, який впровадив pushState у Firefox, я відчуваю дивну суміш гордості та відрази при цьому руйнуванні. Молодці, хлопці.
Джастін Л.

Функція pushState або Notification не працює для деяких типів об'єктів, таких як Функція
Shishir Arora

323

Якщо вбудованого не було, ви можете спробувати:

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}

20
Рішення JQuery буде працювати для елементів DOM, але не для будь-якого об'єкта. Mootools має ту саму межу. Хотілося б, щоб вони мали загальний "клон" для будь-якого об'єкта ... Рекурсивне рішення повинно працювати для чого завгодно. Це, мабуть, шлях.
jschrab

5
Ця функція порушується, якщо об’єкт, який клонується, має конструктор, який потребує параметрів. Здається, що ми можемо змінити його на "var temp = new Object ()" і чи буде він працювати у кожному випадку, ні?
Ендрю Арнотт

3
Ендрю, якщо змінити його на var temp = new Object (), то ваш клон не буде мати такий самий прототип, як оригінальний об'єкт. Спробуйте використовувати: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); '
limscoder

1
Подібно до відповіді limscoder, дивіться мою відповідь нижче про те, як це зробити без виклику конструктора: stackoverflow.com/a/13333781/560114
Метт Браун

3
Для об’єктів, що містять посилання на підрозділи (тобто мережі об’єктів), це не працює: Якщо дві посилання вказують на один і той же суб’єкт, копія містить дві різні його копії. І якщо є рекурсивні посилання, функція ніколи не припиняється (ну, принаймні, не так, як вам потрібно :-) У цих загальних випадках вам потрібно додати словник вже скопійованих об’єктів і перевірити, чи ви вже скопіювали його ... Програмування є складним, коли ви використовуєте просту мову
virtualnobi

153

Ефективний спосіб клонування (не глибокого клонування) об'єкта в одному рядку коду

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

82
Це не рекурсивно копіює, тому насправді не пропонує вирішення проблеми клонування об'єкта.
mwhite

5
Цей метод спрацював, хоча я протестував декілька, і _.extend ({}, (obj)) був НАЙДОБРІЙ найшвидший: на 20 разів швидше JSON.parse і на 60% швидше, ніж Object.assign, наприклад. Він досить добре копіює всі суб’єкти.
Ніко

11
@mwhite є різниця між клоном і глибоким клоном. Ця відповідь насправді клонує, але не є глибоким клоном.
Meirion Hughes

57
оп просив глибокого клону. це не робить глибокого клонування.
user566245

9
Цей спосіб зробити копію SHALLOW , а не DEEP копію ! Через це абсолютно неправильна відповідь !
Бхарата

97

Код:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

Тест:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

3
про що var obj = {}іobj.a = obj
неавмузний

5
Я не розумію цієї функції. Припустимо from.constructor, Dateнаприклад. Як би ifдосягти третього тесту, коли 2-й ifтест вдався б і змусив функцію повернутися (з моменту Date != Object && Date != Array)?
Адам Маккі

1
@AdamMcKee Оскільки передача аргументів JavaScript та призначення змінних є складними . Цей підхід чудово працює, включаючи дати (які справді обробляються другим тестом) - скрипка для тестування тут: jsfiddle.net/zqv9q9c6 .
бричін

1
@NickSweeting: Спробуйте - можливо, це спрацює. Якщо ні - виправте це та оновіть відповідь. Ось як це працює тут у спільноті :)
Камарей

1
Ця функція не клонує регекс у тесті, умова "from.constructor! = Об'єкт && from.constructor! = Масив" завжди повертає істинність для інших конструкторів, таких як Number, Date та ін.
aMarCruz

95

Це те, що я використовую:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

8
Це не здається правильним. cloneObject({ name: null })=>{"name":{}}
Ніяз

13
Це пов’язано з іншою тупою справою в JavaScript, typeof null > "object"але Object.keys(null) > TypeError: Requested keys of a value that is not an object.змініть умову наif(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us

Це присвоїть успадковані перелічені властивості obj безпосередньо клону та припускає, що obj є простим Об'єктом.
RobG

Це також заплутує масиви, які перетворюються на об'єкти з цифровими клавішами.
клинок

Не проблема, якщо ви не використовуєте null.
Хорхе Букаран

78

Глибока копія за продуктивністю: класифікується від найкращого до гіршого

  • Перепризначення "=" (рядкові масиви, масиви чисел - лише)
  • Фрагмент (рядкові масиви, масиви чисел - лише)
  • З'єднання (лише рядкові масиви, масиви чисел - лише)
  • Спеціальна функція: копія або рекурсивна копія
  • $ .extend jQuery
  • JSON.parse (рядкові масиви, масиви чисел, масиви об'єктів - лише)
  • Underscore.js «и _.clone (строкові масиви, масиви чисел - тільки)
  • _.CloneDeep Lo-Dash

Глибока копія масиву рядків або чисел (один рівень - відсутні вказівники):

Коли масив містить числа та рядки - такі функції, як .slice (), .concat (), .splice (), оператор присвоєння "=" та функція клонування Underscore.js; зробить глибоку копію елементів масиву.

Якщо перепризначення має найшвидші показники:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

І .slice () має кращі показники, ніж .concat (), http://jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

Глибока копія масиву об'єктів (двох або більше рівнів - опорні вказівники):

var arr1 = [{object:'a'}, {object:'b'}];

Напишіть власну функцію (має швидшу продуктивність, ніж $ .extend () або JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

Використовуйте сторонні утиліти:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

Де $ .extend jQuery має кращі показники:


Я перевірив кілька, і _.extend ({}, (obj)) був НАЙДОБНІШИЙ: на 20 разів швидше JSON.parse і на 60% швидше, ніж Object.assign, наприклад. Він досить добре копіює всі суб’єкти.
Ніко

4
Усі ваші приклади неглибокі, один рівень. Це не гарна відповідь. Питання стосувалося глибокого клонування, тобто принаймні двох рівнів.
Карл Моррісон

1
Глибока копія - це коли об'єкт копіюється у повному обсязі без використання опорних покажчиків на інші об’єкти. Методи в розділі "Глибока копія масиву об'єктів", таких як jQuery.extend () та спеціальна функція (яка є рекурсивною), копіюють об'єкти з "принаймні двома рівнями". Отже, не всі приклади - це "однорівневі" копії.
tfmontague

1
Мені подобається ваша спеціальна функція копіювання, але вам слід виключити нульові значення, інакше всі нульові значення перетворюються на об’єкти, тобто:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi

2
@HossamMourad - помилка була виправлена ​​Йосі в лютому1 (у коментарі вище), і я не зміг правильно оновити відповідь. Вибачте, що ця помилка призвела до рефактора вашої кодової бази.
tfmontague

64
var clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (var i in this) {
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        }
        else
        {
            newObj[i] = this[i];
        }
    }
    return newObj;
}; 

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});

Хороша відповідь, але це не вдається для кругових посилань.
Лука

59

Глибоке копіювання об'єктів у JavaScript (я вважаю найкращим і найпростішим)

1. Використання JSON.parse (JSON.stringify (об’єкт));

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2. Використання створеного методу

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3. Використання _.cloneDeep- посилання Loash -Dash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4. Використання методу Object.assign ()

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

АЛЕ НЕ ПРАВИЛЬНО

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5. Використання Underscore.js _.clone посилання Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

АЛЕ НЕ ПРАВИЛЬНО

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

JSBEN.CH Ігровий майданчик для продуктивного аналізу 1 ~ 3 http://jsben.ch/KVQLd Продуктивність Глибоке копіювання об'єктів у JavaScript


5
Object.assign()не виконує глибокої копії
Roymunson

1
для них слід додати орієнтири; це було б дуже корисно
jcollum

коли я використовував "створений метод" на об'єкті, що містить масив, я не міг використовувати pop () або splice () на ньому, я не розумію, чому? let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();це кидок: TypeError: tmp.title.pop is not a function(звичайно, pop () працює добре, якщо я просто do let tmp = data; але тоді я не можу змінити tmp, не впливаючи на дані)
hugogogo

Гей, ваш останній приклад помиляється. На мою думку, ви повинні використовувати _clone, а не _cloneDeep для неправильного прикладу.
kenanyildiz

Цей створений метод (2.) не працюватиме для масивів, чи не так?
Toivo Säwén

57

Існує бібліотека (яка називається "клон") , що робить це досить добре. Він забезпечує найбільш повне рекурсивне клонування / копіювання довільних об'єктів, про які я знаю. Він також підтримує кругові посилання, які ще не висвітлюються в інших відповідях.

Ви також можете знайти його в npm . Його можна використовувати як для браузера, так і для Node.js.

Ось приклад того, як його використовувати:

Встановіть його за допомогою

npm install clone

або упакувати його з Ender .

ender build clone [...]

Ви також можете завантажити вихідний код вручну.

Тоді ви можете використовувати його у своєму вихідному коді.

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(Відмова: Я автор бібліотеки.)


3
Клон npm був для мене неоціненним для клонування довільно вкладених об'єктів. Це правильна відповідь.
Енді Рей

Яка продуктивність вашої ліб у порівнянні з припущенням JSON.parse(JSON.stringify(obj))?
pkyeck

Ось бібліотека, де зазначено, що існують швидші варіанти. Не перевірений, хоча.
пворб

Гарне рішення, і це підтримує кругові посилання (на відміну від JSON розбору)
Лука

55

Cloning Об'єкт завжди викликав занепокоєння в JS, але це все стосувалося до ES6, я перераховую різні способи копіювання об'єкта в JavaScript нижче, уявіть, що ви маєте Об'єкт внизу, і хотілося б мати глибоку копію цього:

var obj = {a:1, b:2, c:3, d:4};

Існує кілька способів скопіювати цей об’єкт, не змінюючи походження:

1) ES5 +, використовуючи просту функцію, щоб зробити копію для вас:

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    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 this object.");
}

2) ES5 +, використовуючи JSON.parse та JSON.stringify.

var  deepCopyObj = JSON.parse(JSON.stringify(obj));

3) AngularJs:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) Підкреслення та завантаження:

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

Сподіваюся, ці допоможуть ...


2
клон у підкресленні не є глибоким клоном у поточній версії
Rogelio

Дякую. так, як новий документ для підкреслення ... clone_.clone (об'єкт) Створіть дрібно скопійований клон наданого простого об’єкта. Будь-які вкладені об'єкти або масиви будуть скопійовані посиланням, а не дублюються. _.clone ({name: 'moe'}); => {ім'я: 'moe'};
Аліреза

59
Object.assignце НЕ глибока копія. Приклад: var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". Якби це була глибока копія, y.a.bвсе одно було б c, але це зараз d.
kba

8
Object.assign () клонує лише властивості першого рівня!
haemse

5
що таке функція cloneSO ()?
pastorello

53

Я знаю, що це старий пост, але я подумав, що це може допомогти наступній людині, яка спіткнеться.

До тих пір, поки ви не призначите об'єкт ні в чому, він не зберігає посилання в пам'яті. Отже, щоб зробити об'єкт, яким ви хочете поділитися між іншими об’єктами, вам доведеться створити фабрику на зразок:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);

16
Ця відповідь не дуже актуальна, тому що питання: даний примірник b, як створюється копія c БІЛЬШЕ не знаючи про завод або не бажаючи використовувати завод а. Причина, через яку може не хотіти використовувати завод, полягає в тому, що після екземпляра b може бути ініціалізований додатковими даними (наприклад, введеннями користувача).
Ноель Абрахамс

12
Це правда, що це насправді не відповідь на питання, але я вважаю, що важливо, щоб це було тут, тому що саме відповідь на питання, яке я підозрюю, багато хто з людей, які приїжджають сюди, насправді має сенс задати.
напівколонка

8
Вибачте, хлопці, я не дуже розумію, чому так багато грошей. Клонування об'єкта є досить чіткою концепцією, ви об'єднаєте об'єкт з ОБ'ЄДНОГО об’єкта, і це не має великого відношення до створення нового з заводським малюнком.
Відкривається

2
Хоча це працює для попередньо визначених об'єктів, "клонування" таким чином не розпізнає нові властивості, додані до вихідного об'єкта. Якщо ви створили a, додайте нову властивість до a, а потім створіть b. b не матиме нової властивості. По суті заводський зразок незмінний до нових властивостей. Це не парадигматичне клонування. Дивіться: jsfiddle.net/jzumbrun/42xejnbx
Jon

1
Я думаю, що це взагалі гарна порада, оскільки замість використання const defaultFoo = { a: { b: 123 } };ви можете піти const defaultFoo = () => ({ a: { b: 123 } };і ваша проблема вирішена. Однак це насправді не є відповіддю на питання. Це могло б мати більше сенсу як коментар до питання, а не повна відповідь.
Джош з Карібу

48

Якщо ви використовуєте його, бібліотека Underscore.js має метод клонування .

var newObject = _.clone(oldObject);

24
lodash має метод cloneDeep, він також підтримує ще один парам для клонування, щоб зробити його глибоким: lodash.com/docs#clone та lodash.com/docs#cloneDeep
відкривається

12
@opensas погодився. Lodash , як правило , перевершує підкреслення
NHA

7
Я виступаю за видалення цього та всіх інших відповідей, які є лише однорядними посиланнями на .clone(...)метод бібліотеки утиліт . Кожна велика бібліотека матиме їх, і повторні короткі не детальні відповіді не корисні для більшості відвідувачів, які не користуватимуться цією бібліотекою.
Джеремі Бенкс

41

Ось версія відповіді ConroyP вище, яка працює, навіть якщо конструктор має необхідні параметри:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

Ця функція також доступна в моєму простому інтерфейсі бібліотеці .

Редагувати:

Ось більш надійна версія (завдяки Джастіну МакКандлессу, тепер це підтримує циклічні посилання):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

30

Далі створюється два екземпляри одного об’єкта. Я знайшов його і зараз використовую. Це просто і просто у використанні.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));

Чи є щось не так у цій відповіді? Це корисніше як самостійне рішення, але просте; але рішення jQuery є більш популярним. Чому так?
ceremcem

Так, будь ласка, дайте мені знати. Здається, це працює за призначенням, якщо десь є якась прихована поломка, мені потрібно використовувати інше рішення.
Натан Роджерс

4
Для простого об'єкта в Chrome це приблизно в 6 разів повільніше, ніж дана відповідь, і стає набагато повільніше, коли складність об'єкта зростає. Це страшно масштабує і може дуже швидко поставити вузьку програму.
тик

1
Вам не потрібні дані, а лише розуміння того, що відбувається. Ця методика клонування серіалізує весь об'єкт у рядок, а потім аналізує серіалізацію рядків для створення об'єкта. За своєю суттю це просто буде набагато повільніше, ніж просто переупорядкувати деяку пам’ять (що і роблять більш складні клони). Але якщо говорити про те, що для малих та середніх проектів (залежно від вашого визначення поняття "середнього розміру") хто переймається, чи навіть 1000x менш ефективний? Якщо ваші об’єкти невеликі, і ви не клонуєте їх, тонна 1000x практично нічого, це практично практично нічого.
machineghost

3
Крім того, цей метод втрачає методи (або будь-які речі, які не дозволені в JSON), плюс - JSON.stringify буде перетворювати об'єкти дати в рядки, ... а не навпаки;) Не уникнути цього рішення.
Містер МТ

22

Крокфорд пропонує (і я вважаю за краще) використовувати цю функцію:

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

var newObject = object(oldObject);

Це коротко, працює, як очікувалося, і вам не потрібна бібліотека.


Редагувати:

Це поліфіл для Object.create, тому ви також можете використовувати це.

var newObject = Object.create(oldObject);

ПРИМІТКА. Якщо ви використовуєте щось із цього, у вас можуть виникнути проблеми з деякою ітерацією, яка використовується hasOwnProperty. Тому що, createстворюйте новий порожній об’єкт, який успадковуєoldObject . Але це все-таки корисно і практично для клонування об’єктів.

Для прикладу, якщо oldObject.a = 5;

newObject.a; // is 5

але:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

9
виправте мене, якщо я помиляюся, але чи не те, що Крокфорд починає функцію для наслідування прототипів? Як це стосується клонування?
Алекс Ноласко

3
Так, я побоювався цього обговорення: у чому полягає практична різниця між спадкуванням клонів, копій та прототипів, коли слід використовувати кожну та які функції на цій сторінці насправді роблять? Я знайшов цю сторінку SO за допомогою googling "об'єкт копіювання javascript". Те, що я справді шукав, - це функція вище, тому я повернувся, щоб поділитися. Я здогадуюсь, що запитувач теж шукав цього.
Кріс Броскі

51
Різниця між клоном / копією та успадкуванням полягає в тому, що - використовуючи ваш приклад, коли я змінюю властивість oldObject, властивість також змінюється в newObject. Якщо ви робите копію, ви можете робити все, що завгодно, з oldObject, не змінюючи newObject.
Ridcully

13
Це дозволить зламати перевірку hasOwnProperty, так що це досить шалений спосіб клонування об'єкта і дасть вам несподівані результати.
Корбан Брук

var extendObj = function(childObj, parentObj) { var tmpObj = function () {} tmpObj.prototype = parentObj.prototype; childObj.prototype = new tmpObj(); childObj.prototype.constructor = childObj; };... davidshariff.com/blog/javascript-inheritance-patterns
Cody

22

У Лодаша є приємний метод _.cloneDeep (значення) :

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

5
Я виступаю за видалення цього та всіх інших відповідей, які є лише однорядними посиланнями на .clone(...)метод бібліотеки утиліт . Кожна велика бібліотека матиме їх, і повторні короткі не детальні відповіді не корисні для більшості відвідувачів, які не користуватимуться цією бібліотекою.
Джеремі Бенкс

Простіший спосіб - це використовувати _.merge({}, objA). Якби тільки lodash не мутував об'єкти в першу чергу, cloneфункція не була б необхідною.
Ребс

7
Тут шукаються пошуки Google щодо клонування об’єктів JS. Я використовую Лодаш, тому ця відповідь мені стосується. Давайте не переходимо на всі "wikipedia deletionist" на відповіді, будь ласка.
Ребс

2
У Вузлі 9 JSON.parse (JSON.stringify (arrayOf About5KFlatObjects)) набагато швидше, ніж _.deepClone (масивOf About5KFlatObjects).
Дан Даскалеску

21
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }

17
Проблема методу полягає в тому, що якщо у вас є суб'єкти в межах obj, їх посилання будуть клоновані, а не значення кожного під об'єкта.
Камарей

1
просто зробіть це рекурсивно, щоб суб'єкти були глибоко клоновані.
fiatjaf

просто цікаво ... не буде, чи змінна клона матиме покажчики на властивості вихідного об'єкта? тому що, здається, немає нового розподілу пам’яті
Rupesh Patel

3
Так. Це просто неглибока копія, тому клон буде вказувати на ті самі об'єкти, на які вказував оригінальний об’єкт.
Марк Сідаде

Це не відповідь. Ви буквально просто заповнюєте об'єкт посиланнями на інший об'єкт. Внесення змін до вихідного об’єкта внесе зміни в "клон".
Shawn Whinnery

19

Неглибока копія одного вкладиша ( ECMAScript 5-е видання ):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

І неглибока копія однолінійки ( ECMAScript 6-е видання , 2015 р.):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

6
Це може бути добре для простих об'єктів, але він копіює лише значення властивостей. Він не торкається ланцюга прототипу і за допомогою Object.keysнього пропускає не перелічені та успадковані властивості. Крім того, він втрачає дескриптори властивості, виконуючи пряме призначення.
Метт Бірнер

Якщо ви також копіюєте прототип, вам не вистачало б лише нечислимих чи дескрипторів властивостей, так? Досить добре. :)
сам

Ефективність убік, це дійсно зручний спосіб дрібної копіювання об'єкта. Я часто використовую це для сортування підроблених властивостей спокою в задачі руйнування в своїх компонентах React.
mjohnsonengr

17

Просто тому, що я не бачив згаданого AngularJS і думав, що люди можуть захотіти знати ...

angular.copy також передбачає метод глибокої копіювання об'єктів та масивів.


або він може бути використаний так само, як jQiery extension:angular.extend({},obj);
Galvani

2
@Galvani: Слід зазначити , що jQuery.extendі angular.extendє дрібними примірниками. angular.copyє глибокою копією.
Ден Аткінсон

16

Здається, ще немає ідеального оператора глибокого клонування для об’єктів, подібних до масиву. Як показано нижче в коді, клонер jQuery Джона Ресіга перетворює масиви з нечисловими властивостями в об'єкти, які не є масивами, а JSON-клонер RegDwight скидає нечислові властивості. Наступні тести ілюструють ці моменти у кількох браузерах:

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)

14
як інші вказували у коментарях до відповіді Ресіга, якщо ви хочете клонувати об’єкт, схожий на масив, ви змінюєте {} на [] у виклику розширення, наприклад jQuery.extend (true, [], obj)
Anentropic

15

У мене є два хороших відповіді, залежно від того, ваша мета - клонувати "звичайний старий об'єкт JavaScript" чи ні.

Припустимо також, що ваш намір полягає у створенні повного клону без посилань прототипу на вихідний об'єкт. Якщо вас не цікавить повний клон, ви можете використовувати багато підпрограм Object.clone (), наданих у деяких інших відповідях (схема Крокфорда).

Для простих старих об’єктів JavaScript, перевірений і справді хороший спосіб клонувати об’єкт у сучасних умовах виконання досить просто:

var clone = JSON.parse(JSON.stringify(obj));

Зауважте, що вихідний об'єкт повинен бути чистим об'єктом JSON. Це означає, що всі його вкладені властивості повинні бути скалярами (як булева, рядова, масив, об'єкт тощо). Будь-які функції або спеціальні об'єкти, такі як RegExp або Date не будуть клоновані.

Це ефективно? Чорт так. Ми спробували всі види методів клонування, і це найкраще працює. Я впевнений, що якийсь ніндзя міг придумати більш швидкий метод. Але я підозрюю, що ми говоримо про граничні вигоди.

Цей підхід просто простий і легкий у здійсненні. Увімкніть його у функцію зручності, і якщо вам дійсно потрібно вичавити деякий приріст, продовжуйте пізніше.

Тепер на непрості об’єкти JavaScript не існує дійсно простої відповіді. Насправді цього не може бути через динамічний характер функцій JavaScript та стан внутрішнього об'єкта. Глибоке клонування структури JSON з функціями всередині вимагає відтворення цих функцій та їх внутрішнього контексту. І JavaScript просто не має стандартизованого способу зробити це.

Правильний спосіб зробити це ще раз - це зручний метод, який ви заявляєте та повторно використовуєте у своєму коді. Метод зручності може бути наділений певним розумінням власних об’єктів, щоб ви могли переконатися у правильному відтворенні графіка в новому об’єкті.

Ми написані самі, але найкращий загальний підхід, який я бачив, тут розкрито:

http://davidwalsh.name/javascript-clone

Це правильна ідея. Автор (Девід Уолш) прокоментував клонування узагальнених функцій. Це ви можете вибрати, залежно від вашого випадку використання.

Основна ідея полягає в тому, що вам потрібно спеціально обробляти моменти своїх функцій (або прототипних класів, так би мовити) на основі кожного типу. Тут він подав кілька прикладів для RegExp та Date.

Цей код не тільки короткий, але він також дуже читабельний. Це досить легко розширити.

Це ефективно? Чорт так. Враховуючи, що мета полягає в тому, щоб створити справжній клон глибокої копії, то вам доведеться ходити членами графіка вихідного об'єкта. При такому підході ви можете налаштувати, до яких саме членів дитини звертатися та як вручну обробляти власні типи.

Так що ви йдете. Два підходи. На мій погляд, обидва ефективні.


13

Це, як правило, не найефективніше рішення, але воно робить те, що мені потрібно. Прості тестові приклади нижче ...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

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

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

Тест на циклічний масив ...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

Тест функції ...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false

11

КутовийJS

Добре, якщо ви використовуєте кутовий, ви можете зробити це теж

var newObject = angular.copy(oldObject);

11

Я не згоден з відповіддю з найбільшими голосами тут . Рекурсивний Deep Clone це набагато швидше , ніж JSON.parse (JSON.stringify (OBJ)) підхід згадується.

Ось функція для швидкої довідки:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}

2
Мені сподобався такий підхід, але він не обробляє дати належним чином; розгляньте можливість додати щось на кшталт if(o instanceof Date) return new Date(o.valueOf());після перевірки на null `
Луїс

Збитки на кругових посиланнях.
Гаррі

В останньому стабільному Firefox цей шлях довший, ніж інші стратегії на цьому посиланні Jsben.ch, на порядок і більше. Він б’є інших у неправильному напрямку.
WBT

11
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

10

Тільки тоді, коли ви можете використовувати ECMAScript 6 або транспілятори .

Особливості:

  • Не буде запускати геттер / сетер під час копіювання.
  • Зберігає геттер / сетер.
  • Зберігає інформацію про прототип.
  • Працює як з об'єктно-буквальним, так і з функціональним стилем написання ОО .

Код:

function clone(target, source){

    for(let key in source){

        // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
        let descriptor = Object.getOwnPropertyDescriptor(source, key);
        if(descriptor.value instanceof String){
            target[key] = new String(descriptor.value);
        }
        else if(descriptor.value instanceof Array){
            target[key] = clone([], descriptor.value);
        }
        else if(descriptor.value instanceof Object){
            let prototype = Reflect.getPrototypeOf(descriptor.value);
            let cloneObject = clone({}, descriptor.value);
            Reflect.setPrototypeOf(cloneObject, prototype);
            target[key] = cloneObject;
        }
        else {
            Object.defineProperty(target, key, descriptor);
        }
    }
    let prototype = Reflect.getPrototypeOf(source);
    Reflect.setPrototypeOf(target, prototype);
    return target;
}

9

Ось комплексний метод clone (), який може клонувати будь-який об’єкт JavaScript. Він обробляє майже всі випадки:

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

Він перетворює примітиви в обгорткові предмети, що не є гарним рішенням у більшості випадків.
Дунайський матрос

@DanubianSailor - Я не думаю, що це робить ... це, здається, повертає примітивів одразу з самого початку, і, схоже, не робить нічого з цим, що перетворило б їх у обгорткові об'єкти по мірі їх повернення.
Jimbo Jonny
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.