Jaki jest najskuteczniejszy sposób głębokiego klonowania obiektu w JavaScript?


5180

Jaki jest najskuteczniejszy sposób klonowania obiektu JavaScript? Widziałem, że jestem obj = eval(uneval(o));używany, ale to niestandardowe i obsługiwane tylko przez Firefox .

Robiłem takie rzeczy, obj = JSON.parse(JSON.stringify(o));ale kwestionowałem efektywność.

Widziałem także funkcje kopiowania rekurencyjnego z różnymi wadami.
Dziwi mnie, że nie ma kanonicznego rozwiązania.


566
Eval nie jest zły. Niewłaściwe korzystanie z eval jest. Jeśli boisz się jego skutków ubocznych, źle go używasz. Skutki uboczne, których się obawiasz, są powodem, aby je stosować. Czy ktoś przy okazji faktycznie odpowiedział na twoje pytanie?
James

15
Klonowanie obiektów to trudna sprawa, szczególnie w przypadku niestandardowych obiektów o dowolnych kolekcjach. Prawdopodobnie dlatego nie ma gotowego sposobu na zrobienie tego.
b01

12
eval()jest ogólnie złym pomysłem, ponieważ wiele optymalizatorów silnika Javascript musi się wyłączyć, gdy mamy do czynienia ze zmiennymi ustawionymi za pomocąeval . Sam eval()kod może prowadzić do gorszej wydajności.
user56reinstatemonica8


12
Zauważ, że JSONmetoda straci wszystkie typy Javascript, które nie mają odpowiednika w JSON. Na przykład: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))wygeneruje{a: null, b: null, c: null, g: false}
oriadam

Odpowiedzi:


4731

Natywne głębokie klonowanie

Nazywa się to „klonowaniem strukturalnym”, działa eksperymentalnie w węźle 11 i późniejszych i mam nadzieję, że wyląduje w przeglądarkach. Zobacz tę odpowiedź, aby uzyskać więcej informacji.

Szybkie klonowanie z utratą danych - JSON.parse / stringify

Jeśli nie używać Dates, funkcji, undefined, Infinity, wyrażenia regularne, mapy, zestawy, Blobs, FileLists, ImageDatas, rzadki tablic, wpisane tablic lub innych typów złożonych w swoim obiekcie, bardzo prosta wkładka do głębokiego klonu przedmiotem jest:

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()

Zobacz odpowiedź Corban jest dla odniesienia.

Niezawodne klonowanie przy użyciu biblioteki

Ponieważ klonowanie obiektów nie jest trywialne (typy złożone, odwołania kołowe, funkcja itp.), Większość głównych bibliotek zapewnia funkcję klonowania obiektów. Nie wymyślaj na nowo koła - jeśli już korzystasz z biblioteki, sprawdź, czy ma ona funkcję klonowania obiektów. Na przykład,

ES6

Dla kompletności zwróć uwagę, że ES6 oferuje dwa mechanizmy płytkiej kopii: Object.assign()i składnię stron widzących . który kopiuje wartości wszystkich wyliczalnych własnych właściwości z jednego obiektu do drugiego. Na przykład:

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

7
@ThiefMaster github.com/jquery/jquery/blob/master/src/core.js w linii 276 (jest trochę kodu, który robi coś innego, ale kod „jak to zrobić w JS” jest tam :)
Rune FS

7
Oto kod JS stojący za głęboką kopią jQuery, dla wszystkich zainteresowanych: github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W

194
Łał! Żeby było super jasne: nie mam pojęcia, dlaczego ta odpowiedź została wybrana jako właściwa odpowiedź, była to odpowiedź na odpowiedzi podane poniżej: stackoverflow.com/a/122190/6524 (co było zalecane .clone(), co nie jest właściwym kodem za pomocą w tym kontekście). Niestety to pytanie przeszło tak wiele wersji, że pierwotna dyskusja nie jest już nawet widoczna! Postępuj zgodnie z radą Corbana i napisz pętlę lub skopiuj właściwości bezpośrednio do nowego obiektu, jeśli zależy Ci na szybkości. Lub sprawdź to sam!
John Resig

9
To jest pytanie JavaScript (bez wzmianki o jQuery).
gphilip

60
Jak można to zrobić bez użycia jQuery?
Awesomeness01

2264

Sprawdź ten test: http://jsben.ch/#/bWfk9

W moich poprzednich testach, gdzie szybkość była głównym problemem, stwierdziłem

JSON.parse(JSON.stringify(obj))

być najwolniejszym sposobem głębokiego klonowania obiektu (jest wolniejszy niż jQuery.extend z deepflagą ustawioną na 10-20%).

jQuery.extend działa dość szybko, gdy deepflaga jest ustawiona na false(płytki klon). Jest to dobra opcja, ponieważ zawiera dodatkową logikę do sprawdzania poprawności typu i nie kopiuje niezdefiniowanych właściwości itp., Ale to również trochę spowolni.

Jeśli znasz strukturę obiektów, które próbujesz sklonować lub możesz uniknąć głęboko zagnieżdżonych tablic, możesz napisać prostą for (var i in obj)pętlę, aby sklonować obiekt podczas sprawdzania hasOwnProperty i będzie on znacznie szybszy niż jQuery.

Wreszcie, jeśli próbujesz sklonować znaną strukturę obiektu w gorącej pętli, możesz uzyskać DUŻO WIĘKSZĄ WYDAJNOŚĆ, po prostu wstawiając procedurę klonowania i ręcznie konstruując obiekt.

Mechanizmy śledzenia JavaScript są do bani w optymalizacji for..inpętli, a sprawdzenie hasOwnProperty również spowolni. Klonowanie ręczne, gdy prędkość jest absolutną koniecznością.

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

Uważaj przy użyciu JSON.parse(JSON.stringify(obj))metody na Dateobiektach - JSON.stringify(new Date())zwraca ciąg znaków reprezentujący datę w formacie ISO, który JSON.parse() nie jest konwertowany z powrotem na Dateobiekt. Zobacz tę odpowiedź, aby uzyskać więcej informacji .

Dodatkowo pamiętaj, że przynajmniej w Chrome 65 natywne klonowanie nie jest dobrym rozwiązaniem. Według JSPerf, natywne klonowanie poprzez utworzenie nowej funkcji jest prawie 800 razy wolniejsze niż użycie JSON.stringify, który jest niesamowicie szybki na całej planszy.

Aktualizacja dla ES6

Jeśli używasz Javascript ES6, wypróbuj tę natywną metodę klonowania lub płytkiej kopii.

Object.assign({}, obj);

4
@trysis Object.create nie klonuje obiektu, używa obiektu prototypowego ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser

105
Ta metoda usunie również keysz twojego object, które mają functionsjako wartości, ponieważ JSONnie obsługuje funkcji.
Karlen Kishmiryan

39
Należy również pamiętać, że użycie JSON.parse(JSON.stringify(obj))obiektów w dniu spowoduje również konwersję daty z powrotem do UTC w postaci ciągu znaków w formacie ISO8601 .
dnlgmzddr

31
Podejście JSON dusi także odniesienia cykliczne.
bogate przypomnienie

28
@velop, Object.assign ({}, objToClone) wydaje się jednak, że robi płytki klon - używając go podczas zabawy w konsoli narzędzi deweloperskich, klon obiektu wciąż wskazywał na odniesienie do sklonowanego obiektu. Więc nie sądzę, żeby miało to tutaj zastosowanie.
Garrett Simpson

473

Zakładając, że masz tylko zmienne, a nie jakieś funkcje w swoim obiekcie, możesz po prostu użyć:

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

86
Wadą tego podejścia, które właśnie odkryłem, jest to, że jeśli twój obiekt ma jakieś funkcje (mój ma wewnętrzne moduły pobierające i ustawiające), to są one tracone po strunowaniu. Jeśli to wszystko, czego potrzebujesz, ta metoda jest w porządku ..
Markive

31
@Jason, Powodem, dla którego ta metoda jest wolniejsza niż płytkie kopiowanie (na głębokim obiekcie) jest to, że ta metoda z definicji jest głębokim kopiowaniem. Ponieważ jednak JSONjest implementowany w natywnym kodzie (w większości przeglądarek), będzie to znacznie szybsze niż przy użyciu jakiegokolwiek innego rozwiązania do głębokiego kopiowania opartego na javascript, a czasem może być szybsze niż technika płytkiego kopiowania oparta na javascript (patrz: jsperf.com/cloning -an-object / 79 ).
MiJyn

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

32
technika ta zniszczy także wszystkie Dateobiekty przechowywane w obiekcie, przekształcając je w ciąg znaków.
fstab

13
Kopiowanie czegokolwiek, co nie jest częścią specyfikacji JSON ( json.org ), nie powiedzie się
cdmckay

397

Klonowanie strukturalne

Standard HTML zawiera wewnętrzny strukturalny algorytm klonowania / serializacji, który może tworzyć głębokie klony obiektów. Nadal jest ograniczony do niektórych wbudowanych typów, ale oprócz kilku typów obsługiwanych przez JSON obsługuje także Daty, RegExps, Mapy, Zestawy, Obiekty Blob, Listy plików, ImageDatas, Rzadkie Tablice, Tablice Typowane i prawdopodobnie więcej w przyszłości . Zachowuje również odwołania w sklonowanych danych, umożliwiając obsługę struktur cyklicznych i rekurencyjnych, które powodowałyby błędy dla JSON.

Wsparcie w Node.js: Experimental 🙂

v8Moduł w node.js obecnie (od węzła 11) naraża strukturze serializacji API bezpośrednio , ale ta funkcjonalność jest nadal oznaczone jako „eksperymentalne”, i mogą ulec zmianie lub usunięciu w przyszłych wersjach. Jeśli używasz kompatybilnej wersji, klonowanie obiektu jest tak proste, jak:

const v8 = require('v8');

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

Bezpośrednie wsparcie w przeglądarkach: może ostatecznie? 😐

Przeglądarki nie zapewniają obecnie bezpośredniego interfejsu dla strukturalnego algorytmu klonowania, ale structuredClone()funkcja globalna została omówiona w whatwg / html # 793 na GitHub . Zgodnie z obecnie proponowanym użyciem do większości celów byłoby tak proste, jak:

const clone = structuredClone(original);

Jeśli nie zostanie to dostarczone, strukturyzowane implementacje klonów przeglądarki są ujawniane tylko pośrednio.

Obejście asynchroniczne: możliwe do użycia. 😕

Niższym sposobem na utworzenie strukturalnego klonu z istniejącymi interfejsami API jest opublikowanie danych przez jeden port MessageChannels . Drugi port wyemituje messagezdarzenie ze strukturalnym klonem dołączonego .data. Niestety nasłuchiwanie tych zdarzeń jest z konieczności asynchroniczne, a synchroniczne alternatywy są mniej praktyczne.

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

Przykład użycia:

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

Obejścia synchroniczne: Okropne! 🤢

Nie ma dobrych opcji synchronicznego tworzenia uporządkowanych klonów. Oto kilka niepraktycznych hacków.

history.pushState()i history.replaceState()oba tworzą ustrukturyzowany klon pierwszego argumentu i przypisują tę wartość do history.state. Możesz użyć tego do utworzenia strukturalnego klonu dowolnego obiektu takiego jak ten:

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

Przykład użycia:

Chociaż synchroniczny, może być bardzo wolny. Powoduje to narzut związany z manipulowaniem historią przeglądarki. Wielokrotne wywoływanie tej metody może spowodować, że Chrome przestanie odpowiadać.

NotificationKonstruktor tworzy strukturze klon związanych z nią danych. Próbuje również wyświetlić użytkownikowi powiadomienie z przeglądarki, ale po cichu zakończy się niepowodzeniem, chyba że użytkownik poprosi o pozwolenie na powiadomienie. Jeśli masz pozwolenie na inne cele, natychmiast zamkniemy utworzone przez nas powiadomienie.

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

Przykład użycia:


3
@rynah Ponownie przejrzałem specyfikację i masz rację: obie metody history.pushState()i history.replaceState()synchronicznie ustawione history.statena ustrukturyzowanego klona pierwszego argumentu. Trochę dziwne, ale działa. Aktualizuję teraz swoją odpowiedź.
Jeremy Banks

40
To jest tak źle! Tego interfejsu API nie należy używać w ten sposób.
Fardin K.,

209
Jako facet, który wdrożył pushState w Firefoksie, czuję dziwną mieszankę dumy i wstrętu po tym hacku. Dobra robota chłopaki.
Justin L.,

Funkcja pushState lub Notification hack nie działa w przypadku niektórych typów obiektów, takich jak funkcja
Shishir Arora

323

Jeśli nie było żadnego wbudowanego, możesz spróbować:

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
Rozwiązanie JQuery będzie działać dla elementów DOM, ale nie tylko dla każdego obiektu. Mootools ma ten sam limit. Chciałbym, żeby mieli ogólny „klon” dla dowolnego obiektu ... Rozwiązanie rekurencyjne powinno działać na wszystko. To chyba najlepsza droga.
jschrab

5
Ta funkcja nie działa, jeśli klonowany obiekt ma konstruktor wymagający parametrów. Wygląda na to, że możemy go zmienić na „var temp = new Object ()” i sprawić, by działał w każdym przypadku, prawda?
Andrew Arnott,

3
Andrew, jeśli zmienisz go na var temp = new Object (), wtedy twój klon nie będzie miał takiego samego prototypu jak oryginalny obiekt. Spróbuj użyć: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); '
limscoder

1
Podobnie do odpowiedzi limscodera, zobacz moją odpowiedź poniżej, jak to zrobić bez wywoływania konstruktora: stackoverflow.com/a/13333781/560114
Matt Browne

3
W przypadku obiektów, które zawierają odniesienia do podelementów (tj. Sieci obiektów), to nie działa: Jeśli dwa odniesienia wskazują na ten sam podobiekt, kopia zawiera dwie różne jego kopie. A jeśli istnieją rekurencyjne odwołania, funkcja nigdy się nie zakończy (cóż, przynajmniej nie tak, jak chcesz :-) W tych ogólnych przypadkach musisz dodać słownik obiektów już skopiowanych i sprawdzić, czy już go skopiowałeś ... Programowanie jest skomplikowane, gdy używasz prostego języka
virtualnobi,

153

Wydajny sposób klonowania (nie głębokiego klonowania) obiektu w jednym wierszu kodu

Object.assignMetoda jest częścią standardu (2015) ES6 ECMAScript i robi dokładnie to, czego potrzebujesz.

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

Metoda Object.assign () służy do kopiowania wartości wszystkich możliwych do wyliczenia własnych właściwości z jednego lub więcej obiektów źródłowych do obiektu docelowego.

Czytaj więcej...

Polyfill do obsługi starszych przeglądarek:

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
Nie kopiuje się rekurencyjnie, więc tak naprawdę nie oferuje rozwiązania problemu klonowania obiektu.
biały

5
Ta metoda zadziałała, chociaż przetestowałem kilka i _.extend ({}, (obj)) BYŁO DALEJ najszybszy: na przykład 20x szybszy niż JSON.parse i 60% szybszy niż Object.assign. Całkiem dobrze kopiuje wszystkie podobiekty.
Nico,

11
@mwhite istnieje różnica między klonowaniem a głębokim klonowaniem. Ta odpowiedź faktycznie klonuje, ale nie klonuje głęboko.
Meirion Hughes

57
op poprosił o głęboki klon. to nie robi głębokiego klonowania.
user566245

9
Ta metoda tworzy PŁYTKĄ kopię , a nie GŁĘBĄ kopię ! Z tego powodu jest to całkowicie błędna odpowiedź !
Bharata

97

Kod:

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

Test:

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
co var obj = {}iobj.a = obj
neaumusic

5
Nie rozumiem tej funkcji. Załóżmy, że from.constructorjest Datena przykład. Jak ifmożna osiągnąć trzeci test, gdy drugi iftest zakończy się powodzeniem i spowoduje powrót funkcji (od tego czasu Date != Object && Date != Array)?
Adam McKee

1
@AdamMcKee Ponieważ przekazywanie argumentów javascript i przypisywanie zmiennych jest trudne . Podejście to działa świetnie, włączając daty (które faktycznie są obsługiwane przez drugi test) - fiddle, aby przetestować tutaj: jsfiddle.net/zqv9q9c6 .
brichins

1
@NickSweeting: Spróbuj - może to zadziała. Jeśli nie - napraw to i zaktualizuj odpowiedź. Tak to działa tutaj w społeczności :)
Kamarey

1
Ta funkcja nie klonuje wyrażenia regularnego w teście, warunek „from.constructor! = Object && from.constructor! = Array” zawsze zwraca wartość true dla innych konstruktorów, takich jak Number, Date itd.
aMarCruz

95

Tego używam:

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
To nie wydaje się właściwe. cloneObject({ name: null })=>{"name":{}}
Niyaz

13
Wynika to z kolejnej głupiej rzeczy w javascript, typeof null > "object"ale Object.keys(null) > TypeError: Requested keys of a value that is not an object.zmień warunek naif(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us 16.04.13

Spowoduje to przypisanie odziedziczonych wyliczalnych właściwości obj bezpośrednio do klonu i zakłada, że obj jest zwykłym obiektem.
RobG

Powoduje to również bałagan tablic, które są konwertowane na obiekty za pomocą klawiszy numerycznych.
ostrze

Nie stanowi problemu, jeśli nie użyjesz wartości null.
Jorge Bucaran

78

Głębokie kopiowanie według wydajności: od najlepszej do najgorszej

  • Ponowne przypisanie "=" (tablice ciągów, tablice liczb - tylko)
  • Plaster (tablice ciągów, tablice liczb - tylko)
  • Łączenie (tablice ciągów, tablice liczb - tylko)
  • Funkcja niestandardowa: kopiowanie w pętli lub rekurencyjne
  • jQuery's $ .extend
  • JSON.parse (tablice łańcuchowe, tablice liczb, tablice obiektowe - tylko)
  • Underscore.js 's ..clone (tablice ciągów, tablice liczb - tylko)
  • Lo-Dash's _.cloneDeep

Głęboko skopiuj tablicę ciągów lub liczb (jeden poziom - bez wskaźników odniesienia):

Gdy tablica zawiera liczby i łańcuchy - funkcje takie jak .slice (), .concat (), .splice (), operator przypisania „=” i funkcja klonowania Underscore.js; utworzy głęboką kopię elementów tablicy.

Jeśli zmiana przypisania ma najszybszą wydajność:

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

A .slice () ma lepszą wydajność niż .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

Głęboko skopiuj tablicę obiektów (dwa lub więcej poziomów - wskaźniki odniesienia):

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

Napisz niestandardową funkcję (ma wyższą wydajność niż $ .extend () lub 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);

Użyj funkcji narzędziowych innych firm:

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

Tam, gdzie $ .extend jQuery ma lepszą wydajność:


Testowałem kilka i _.extend ({}, (obj)) BYŁO DALEJ najszybszy: na przykład 20x szybszy niż JSON.parse i 60% szybszy niż Object.assign. Całkiem dobrze kopiuje wszystkie podobiekty.
Nico,

4
Wszystkie twoje przykłady są płytkie, jeden poziom. To nie jest dobra odpowiedź. Pytanie dotyczyło głębokiego klonowania, tj. Co najmniej dwóch poziomów.
Karl Morrison,

1
Głęboka kopia ma miejsce, gdy obiekt jest kopiowany w całości bez użycia wskaźników odniesienia do innych obiektów. Techniki w sekcji „Głębokie kopiowanie tablicy obiektów”, takie jak jQuery.extend () i funkcja niestandardowa (która jest rekurencyjna) kopiują obiekty z „co najmniej dwoma poziomami”. Nie wszystkie przykłady są kopiami „jednego poziomu”.
tfmontague

1
Podoba mi się twoja niestandardowa funkcja kopiowania, ale powinieneś wykluczyć wartości null, w przeciwnym razie wszystkie wartości null są konwertowane na obiekty, tj .:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi

2
@HossamMourad - Błąd został naprawiony przez Josi 1 lutego (w powyższym komentarzu) i nie udało mi się poprawnie zaktualizować odpowiedzi. Przepraszamy, że ten błąd spowodował refaktoryzację bazy kodu.
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});

Dobra odpowiedź, ale nie udaje się to w przypadku odniesień cyklicznych.
Łukasz

59

Głębokie kopiowanie obiektów w JavaScript (myślę, że najlepsze i najprostsze)

1. Korzystanie z JSON.parse (JSON.stringify (obiekt));

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.Zastosowanie utworzonej metody

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. Korzystanie z linku _.cloneDeep Lo-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. Korzystanie z metody 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 }  

ALE ŹLE KIEDY

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.Korzystanie z Underscore.js _.clone link 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 }  

ALE ŹLE KIEDY

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 Performance Benchmarking Playground 1 ~ 3 http://jsben.ch/KVQLd Wydajność Głębokie kopiowanie obiektów w JavaScript


5
Object.assign()nie wykonuje głębokiej kopii
Roymunson,

1
powinieneś dodać dla nich punkty odniesienia; byłoby to bardzo pomocne
jcollum

kiedy użyłem „metody utworzonej” na obiekcie zawierającym tablicę, nie byłem w stanie użyć na nim pop () ani splice (), nie rozumiem dlaczego? let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();it throw: TypeError: tmp.title.pop is not a function(oczywiście pop () działa dobrze, jeśli tylko do let tmp = data; ale potem nie mogę modyfikować tmp bez wpływu na dane)
hugogogo 24.03.19

Hej, twój ostatni przykład jest zły. Moim zdaniem, musisz użyć _clone, a nie _cloneDeep dla złego przykładu.
kenanyildiz,

Ta stworzona metoda (2.) nie będzie działać dla tablic, prawda?
Toivo Säwén,

57

Istnieje biblioteka (zwana „klon”) , która robi to całkiem dobrze. Zapewnia najbardziej kompletne rekurencyjne klonowanie / kopiowanie dowolnych obiektów, jakie znam. Obsługuje również odwołania cykliczne, które nie są jeszcze objęte innymi odpowiedziami.

Możesz znaleźć na npm . Może być używany zarówno w przeglądarce, jak i Node.js.

Oto przykład, jak go używać:

Zainstaluj za pomocą

npm install clone

lub zapakuj go w Endera .

ender build clone [...]

Możesz także pobrać kod źródłowy ręcznie.

Następnie możesz użyć go w kodzie źródłowym.

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

(Uwaga: Jestem autorem biblioteki).


3
Klon npm był dla mnie nieoceniony przy klonowaniu dowolnie zagnieżdżonych obiektów. To jest właściwa odpowiedź.
Andy Ray,

jaka jest wydajność twojej biblioteki lib w porównaniu z powiedzmy JSON.parse(JSON.stringify(obj))?
pkyeck

Oto biblioteka, która stwierdza, że ​​są szybsze opcje. Jednak nie testowałem.
pvorb

Dobre rozwiązanie, które obsługuje cykliczne odwołania (w przeciwieństwie do parsowania JSON)
Łukasz

55

Cloning obiekt zawsze był przedmiotem zainteresowania w JS, ale o to chodziło przed ES6, poniżej wymieniam różne sposoby kopiowania obiektu w JavaScript, wyobraź sobie, że masz obiekt poniżej i chciałbyś mieć jego głęboką kopię:

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

Istnieje kilka sposobów skopiowania tego obiektu bez zmiany źródła:

1) ES5 +, korzystając z prostej funkcji, aby wykonać kopię za Ciebie:

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 +, używając JSON.parse i 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) UnderscoreJs & Loadash:

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

Mam nadzieję, że te pomoc ...


2
klon w podkreśleniu nie jest głębokim klonem w aktualnej wersji
Rogelio

Dzięki. tak jako nowy dokument dla podkreślenia ... clone_.clone (obiekt) Utwórz płytko skopiowany klon udostępnionego zwykłego obiektu. Wszelkie zagnieżdżone obiekty lub tablice zostaną skopiowane przez odniesienie, a nie powielone. _.clone ({name: 'moe'}); => {name: 'moe'};
Alireza

59
Object.assignnie nie głęboko skopiować. Przykład: var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". Gdyby to była głęboka kopia, y.a.bnadal by była c, ale jest teraz d.
kba

8
Object.assign () klonuje tylko pierwszy poziom właściwości!
haemse

5
czym jest funkcja cloneSO ()?
pastorello,

53

Wiem, że to stary post, ale pomyślałem, że może to pomóc następnej osobie, która się potyka.

Tak długo, jak nie przypisujesz obiektu do czegokolwiek, nie zachowuje on żadnych odniesień w pamięci. Aby stworzyć obiekt, który chcesz udostępnić innym obiektom, musisz utworzyć fabrykę w następujący sposób:

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

16
Ta odpowiedź nie jest tak naprawdę istotna, ponieważ pytanie brzmi: w danym przypadku b jak tworzy się kopię c PODCZAS nie wiedząc o fabryce a lub nie chcąc korzystać z fabryki a. Powodem, dla którego nie chcemy korzystać z fabryki, jest fakt, że po utworzeniu instancji b mogło zostać zainicjowane dodatkowymi danymi (np. Danymi wprowadzonymi przez użytkownika).
Noel Abrahams,

12
To prawda, że ​​tak naprawdę nie jest to odpowiedź na pytanie, ale myślę, że ważne jest, aby było tutaj, ponieważ jest to odpowiedź na pytanie, które, jak podejrzewam, wielu ludzi tutaj naprawdę chce zadać.
Semicolon

8
Przepraszam chłopaki, tak naprawdę nie rozumiem, dlaczego tak wiele pozytywnych opinii. Klonowanie obiektu jest dość jasną koncepcją, stożek obiekt Z INNEGO obiektu i nie ma wiele wspólnego z tworzeniem nowego z wzorcem fabrycznym.
opensas

2
Chociaż działa to w przypadku predefiniowanych obiektów, „klonowanie” w ten sposób nie rozpozna nowych właściwości dodanych do oryginalnego obiektu. Jeśli utworzysz a, dodaj nową właściwość do a, a następnie utwórz b. b nie będzie miał nowej właściwości. Zasadniczo wzór fabryczny jest niezmienny dla nowych właściwości. To nie jest paradygmatycznie klonowanie. Zobacz: jsfiddle.net/jzumbrun/42xejnbx
Jon

1
Myślę, że jest to ogólnie dobra rada, ponieważ zamiast korzystania const defaultFoo = { a: { b: 123 } };możesz iść, const defaultFoo = () => ({ a: { b: 123 } };a twój problem został rozwiązany. Jednak tak naprawdę nie jest to odpowiedź na pytanie. Mogłoby to mieć większy sens jako komentarz do pytania, a nie pełna odpowiedź.
Josh z Qaribou

48

Jeśli go używasz, biblioteka Underscore.js ma metodę klonowania .

var newObject = _.clone(oldObject);

24
lodash ma metodę cloneDeep, obsługuje także inny parametr do klonowania, aby uzyskać głębokie: lodash.com/docs#clone i lodash.com/docs#cloneDeep
opensas

12
@opensas zgodził się. Lodash jest na ogół lepszy od podkreślenia
nha

7
Opowiadam się za usunięciem tej i wszystkich innych odpowiedzi, które są tylko jednowierszowymi odniesieniami do .clone(...)metody biblioteki narzędziowej . Każda duża biblioteka będzie je mieć, a powtarzające się krótkie, nieokreślone odpowiedzi nie będą przydatne dla większości odwiedzających, którzy nie będą korzystać z tej konkretnej biblioteki.
Jeremy Banks,

41

Oto wersja powyższej odpowiedzi ConroyP, która działa, nawet jeśli konstruktor ma wymagane parametry:

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

Ta funkcja jest również dostępna w moim bibliotece simpleoo .

Edytować:

Oto bardziej niezawodna wersja (dzięki Justinowi McCandlessowi obsługuje teraz także cykliczne odwołania):

/**
 * 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

Poniższe tworzy dwa wystąpienia tego samego obiektu. Znalazłem i używam go obecnie. Jest prosty i łatwy w użyciu.

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

Czy coś jest nie tak z tą odpowiedzią? Jest to bardziej przydatne jako samodzielne rozwiązanie, ale proste; ale rozwiązanie jQuery jest bardziej popularne. Dlaczego?
ceremcem

Tak, proszę dać mi znać. Wygląda na to, że działa zgodnie z przeznaczeniem, jeśli gdzieś jest jakieś ukryte uszkodzenie, muszę użyć innego rozwiązania.
Nathan Rogers,

4
W przypadku prostego obiektu jest to około 6 razy wolniej w Chrome niż podana odpowiedź i staje się znacznie wolniejsze w miarę wzrostu złożoności obiektu. Skaluje się strasznie i może bardzo szybko zawęzić Twoją aplikację.
tic

1
Nie potrzebujesz danych, tylko zrozumienie, co się dzieje. Ta technika klonowania serializuje cały obiekt do łańcucha, a następnie analizuje serializację łańcucha, aby zbudować obiekt. Z natury rzeczy będzie to po prostu dużo wolniejsze niż po prostu ponowne uporządkowanie pamięci (co robią bardziej wyrafinowane klony). Ale biorąc to pod uwagę, w przypadku małych i średnich projektów (w zależności od definicji „średnich”) kogo to obchodzi, czy jest nawet 1000 razy mniej wydajne? Jeśli twoje obiekty są małe i nie sklonujesz ich ton 1000x praktycznie nic to wciąż praktycznie nic.
machineghost

3
Ponadto metoda ta traci metody (lub dowolne rzeczy, które nie są dozwolone w JSON), a ponadto - JSON.stringify przekształci obiekty Date w ciągi, ... a nie na odwrót;) Trzymaj się z dala od tego rozwiązania.
Pan MT,

22

Crockford sugeruje (i wolę) użycie tej funkcji:

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

var newObject = object(oldObject);

Jest zwięzły, działa zgodnie z oczekiwaniami i nie potrzebujesz biblioteki.


EDYTOWAĆ:

Jest to polifill dla Object.create, więc możesz również z niego korzystać.

var newObject = Object.create(oldObject);

UWAGA: W przypadku korzystania z niektórych z tych funkcji mogą wystąpić problemy z korzystaniem z iteracji hasOwnProperty. Ponieważ createutwórz nowy pusty obiekt, który będzie dziedziczyłoldObject . Ale nadal jest przydatny i praktyczny do klonowania obiektów.

Na przykład jeśli oldObject.a = 5;

newObject.a; // is 5

ale:

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

9
poprawcie mnie, jeśli się mylę, ale czyż funkcja Crockforda nie spłodzi prototypowego dziedziczenia? Jak to się ma do klonowania?
Alex Nolasco,

3
Tak, bałem się tej dyskusji: jaka jest praktyczna różnica między klonowaniem, kopiowaniem a dziedziczeniem prototypów, kiedy należy używać każdej z nich i jakie funkcje na tej stronie faktycznie robią? Znalazłem tę stronę SO, przeglądając „obiekt kopii javascript”. To, czego tak naprawdę szukałem, to powyższa funkcja, więc wróciłem, aby się nią podzielić. Domyślam się, że pytający też tego szukał.
Chris Broski,

51
Różnica między klonowaniem / kopiowaniem a dziedziczeniem polega na tym, że - na twoim przykładzie, kiedy zmieniam właściwość oldObject, właściwość ta zmienia się również w newObject. Jeśli wykonasz kopię, możesz robić, co chcesz, za pomocą oldObject bez zmiany newObject.
Ridcully,

13
Spowoduje to przerwanie sprawdzania hasOwnProperty, więc jest to dość hackerski sposób klonowania obiektu i daje nieoczekiwane wyniki.
Corban Brook,

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

Lodash ma niezłą metodę _.cloneDeep (wartość) :

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

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

5
Opowiadam się za usunięciem tej i wszystkich innych odpowiedzi, które są tylko jednowierszowymi odniesieniami do .clone(...)metody biblioteki narzędziowej . Każda duża biblioteka będzie je mieć, a powtarzające się krótkie, nieokreślone odpowiedzi nie będą przydatne dla większości odwiedzających, którzy nie będą korzystać z tej konkretnej biblioteki.
Jeremy Banks,

Łatwiejszym sposobem jest użycie _.merge({}, objA). Gdyby tylko lodash nie mutował obiektów, clonefunkcja nie byłaby konieczna.
Rebs

7
Wyszukiwania Google dotyczące klonowania obiektów JS odnoszą się tutaj. Używam Lodash, więc ta odpowiedź jest dla mnie istotna. Nie pozwólmy, aby wszystkie odpowiedzi „usuwania z wikipedii” były w odpowiedziach.
Rebs

2
W węźle 9 JSON.parse (JSON.stringify (arrayOfAbout5KFlatObjects)) jest znacznie szybszy niż _.deepClone (arrayOfAbout5KFlatObjects).
Dan Dascalescu,

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

17
Problem z metodą polegającą na tym, że jeśli masz obiekty podrzędne w obiekcie obj, ich odwołania zostaną sklonowane, a nie wartości każdego obiektu podrzędnego.
Kamarey

1
po prostu ustaw rekurencję, aby obiekty podrzędne zostały głęboko sklonowane.
fiatjaf

po prostu ciekawy ... czy zmienna klonowania nie będzie miała wskaźników do właściwości oryginalnego obiektu? ponieważ wydaje się, że nie ma nowej alokacji pamięci
Rupesh Patel

3
Tak. To tylko płytka kopia, więc klon będzie wskazywał dokładnie te same obiekty, na które wskazywał obiekt oryginalny.
Mark Cidade

To nie jest odpowiedź. Dosłownie po prostu wypełniasz obiekt odnośnikami do innego obiektu. Dokonanie zmian w obiekcie źródłowym spowoduje zmiany w „klonie”.
Shawn Whinnery,

19

Płytka kopia w jednym wierszu ( ECMAScript 5. edycja ):

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

I płytka kopia jednowarstwowa ( ECMAScript 6 edycja , 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
Może to być odpowiednie dla prostych obiektów, ale kopiuje tylko wartości właściwości. Nie dotyka łańcucha prototypów i używając Object.keysgo pomija właściwości niepoliczalne i dziedziczone. Ponadto traci deskryptory właściwości, wykonując bezpośrednie przypisanie.
Matt Bierner,

Jeśli skopiujesz również prototyp, nie będziesz mieć tylko niepoliczalnych i deskryptorów właściwości, tak? Całkiem dobre. :)
sam

Pomijając wydajność, jest to naprawdę wygodny sposób na płytkie kopiowanie obiektu. Często używam tego do pewnego rodzaju fałszywych właściwości spoczynkowych w zadaniu niszczenia w moich komponentach React.
mjohnsonengr

17

Tylko dlatego, że nie widziałem o AngularJS i pomyślałem, że ludzie mogą chcieć wiedzieć ...

angular.copy zapewnia również metodę głębokiego kopiowania obiektów i tablic.


lub może być użyty w taki sam sposób, jak rozszerzenie jQiery:angular.extend({},obj);
Galvani,

2
@Galvani: Należy zauważyć, że jQuery.extendi angular.extendsą obie kopie płytkie. angular.copyjest głęboką kopią.
Dan Atkinson

16

Wydaje się, że nie ma jeszcze idealnego operatora głębokiego klonowania dla obiektów podobnych do tablicy. Jak ilustruje poniższy kod, kloner jQuery Johna Resiga przekształca tablice o właściwościach nienumerycznych w obiekty, które nie są tablicami, a kloner JSON firmy RegDwight upuszcza właściwości nienumeryczne. Poniższe testy ilustrują te punkty w wielu przeglądarkach:

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
jak zauważyli inni w komentarzach do odpowiedzi Resiga, jeśli chcesz sklonować obiekt podobny do tablicy, zmień {} na [] w wywołaniu przedłużenia, np. jQuery.extend (true, [], obj)
Anentropic

15

Mam dwie dobre odpowiedzi w zależności od tego, czy Twoim celem jest sklonowanie „zwykłego, starego obiektu JavaScript”, czy nie.

Załóżmy również, że Twoim celem jest utworzenie pełnego klonu bez odwołań do prototypu z powrotem do obiektu źródłowego. Jeśli nie jesteś zainteresowany pełnym klonowaniem, możesz użyć wielu procedur Object.clone () podanych w niektórych innych odpowiedziach (wzór Crockforda).

W przypadku zwykłych starych obiektów JavaScript wypróbowany i naprawdę dobry sposób klonowania obiektu we współczesnych środowiskach wykonawczych jest po prostu:

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

Zauważ, że obiekt źródłowy musi być czystym obiektem JSON. Oznacza to, że wszystkie jego zagnieżdżone właściwości muszą być skalarami (takimi jak logiczna, łańcuch, tablica, obiekt itp.). Żadne funkcje ani obiekty specjalne, takie jak RegExp lub Date, nie będą klonowane.

Czy to jest wydajne? Pewnie, ze tak. Wypróbowaliśmy wszystkie metody klonowania i to działa najlepiej. Jestem pewien, że jakiś ninja wyczaruje szybszą metodę. Ale podejrzewam, że mówimy o marginalnych zyskach.

To podejście jest po prostu proste i łatwe do wdrożenia. Zapakuj go w wygodną funkcję, a jeśli naprawdę chcesz wycisnąć trochę korzyści, idź później.

Teraz w przypadku nieprzezroczystych obiektów JavaScript nie ma naprawdę prostej odpowiedzi. W rzeczywistości nie może być tak z powodu dynamicznej natury funkcji JavaScript i wewnętrznego stanu obiektu. Głębokie klonowanie struktury JSON z funkcjami wewnątrz wymaga odtworzenia tych funkcji i ich wewnętrznego kontekstu. A JavaScript po prostu nie ma znormalizowanego sposobu na zrobienie tego.

Prawidłowy sposób, aby to zrobić, jeszcze raz, polega na wygodnej metodzie, którą deklarujesz i używasz ponownie w swoim kodzie. Metodę wygody można wyposażyć w pewne zrozumienie własnych obiektów, aby zapewnić prawidłowe odtworzenie wykresu w nowym obiekcie.

Napisaliśmy własne, ale najlepsze ogólne podejście, jakie widziałem, jest tutaj:

http://davidwalsh.name/javascript-clone

To jest właściwy pomysł. Autor (David Walsh) skomentował klonowanie funkcji uogólnionych. Jest to coś, co możesz zrobić, w zależności od przypadku użycia.

Główną ideą jest to, że musisz specjalnie obsługiwać tworzenie instancji swoich funkcji (lub klas prototypowych, że tak powiem) dla poszczególnych typów. Tutaj podał kilka przykładów RegExp i Date.

Ten kod jest nie tylko krótki, ale także bardzo czytelny. Jest dość łatwy do rozszerzenia.

Czy to jest wydajne? Pewnie, ze tak. Biorąc pod uwagę, że celem jest stworzenie prawdziwego klonowania w głębokiej kopii, będziesz musiał przejść elementy wykresu obiektu źródłowego. Dzięki takiemu podejściu możesz dokładnie dostosować, które elementy podrzędne mają być traktowane i jak ręcznie obsługiwać typy niestandardowe.

Więc proszę bardzo. Dwa podejścia. Oba są moim zdaniem wydajne.


13

Nie jest to na ogół najbardziej wydajne rozwiązanie, ale robi to, czego potrzebuję. Proste przypadki testowe poniżej ...

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

Cykliczny test tablicowy ...

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

Test działania ...

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

11

AngularJS

Cóż, jeśli używasz kąta, możesz to zrobić

var newObject = angular.copy(oldObject);

11

Nie zgadzam się z odpowiedzią z największą liczbą głosów tutaj . Recursive Głębokie Clone jest znacznie szybsze niż JSON.parse (JSON.stringify (obj)) podejścia wymienione.

Oto funkcja szybkiego odniesienia:

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
Podobało mi się to podejście, ale nie obsługuje ono właściwie dat; rozważ dodanie czegoś takiego jak if(o instanceof Date) return new Date(o.valueOf());po sprawdzeniu, czy null `
Luis

Awarie odwołań cyklicznych.
Harry

W najnowszym stabilnym Firefoksie jest to znacznie dłużej niż inne strategie w tym linku Jsben.ch, o rząd wielkości lub więcej. Bije innych w złym kierunku.
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

Tylko wtedy, gdy możesz użyć ECMAScript 6 lub transpilatorów .

Funkcje:

  • Nie uruchomi gettera / settera podczas kopiowania.
  • Zachowuje gettera / setera.
  • Zachowuje prototypowe informacje.
  • Współpracuje z obu obiektowo dosłownych i funkcjonalnych OO stylów pisania.

Kod:

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

Oto kompleksowa metoda clone (), która może klonować dowolny obiekt JavaScript. Obsługuje prawie wszystkie przypadki:

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

Konwertuje prymitywy na obiekty otoki, co w większości przypadków nie jest dobrym rozwiązaniem.
Danubian Sailor

@DanubianSailor - nie sądzę, że tak ... wydaje się, że od razu zwraca prymitywy i nie robi nic, co zmieniłoby je w obiekty opakowania podczas ich zwracania.
Jimbo Jonny
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.