Jak głęboko klonować w javascript


105

Jak głęboko sklonować obiekt Javascript?

Wiem, że istnieją różne funkcje oparte na frameworkach, takich jak JSON.parse(JSON.stringify(o))i, $.extend(true, {}, o)ale nie chcę używać takiego frameworka.

Jaki jest najbardziej elegancki lub skuteczny sposób na stworzenie głębokiego klonu.

Dbamy o skrajne przypadki, takie jak klonowanie tablic. Nie przerywanie łańcuchów prototypów, zajmowanie się odniesieniem do siebie

Nie zależy nam na wspieraniu kopiowania obiektów DOM i podobnych, ponieważ .cloneNodeistnieje z tego powodu.

Ponieważ chcę głównie używać głębokich klonów w node.jskorzystaniu z funkcji ES5 silnika V8, jest to dopuszczalne.

[Edytować]

Zanim ktoś zasugeruje, pozwólcie mi wspomnieć, że istnieje wyraźna różnica między tworzeniem kopii poprzez prototypowe dziedziczenie po obiekcie a jego klonowaniem . To pierwsze powoduje bałagan w łańcuchu prototypów.

[Dalsza edycja]

Po przeczytaniu Twojej odpowiedzi doszedłem do irytującego odkrycia, że ​​klonowanie całych obiektów jest bardzo niebezpieczną i trudną grą. Weźmy na przykład następujący obiekt oparty na domknięciu

var o = (function() {
     var magic = 42;

     var magicContainer = function() {
          this.get = function() { return magic; };
          this.set = function(i) { magic = i; };
     }

      return new magicContainer;
}());

var n = clone(o); // how to implement clone to support closures

Czy istnieje sposób na napisanie funkcji klonującej, która klonuje obiekt, ma ten sam stan w momencie klonowania, ale nie może zmienić stanu obez pisania parsera JS w JS.

Taka funkcja nie powinna być już potrzebna. To tylko akademickie zainteresowanie.


2
Zanim został oznaczony jako duplikat, przejrzałem stackoverflow.com/questions/122102/ ... i nie znalazłem żadnej odpowiedzi, która dotyczyłaby wszystkich skrajnych przypadków.
Raynos

Wymóg w sekcji „dalsza edycja” jest niemożliwy do osiągnięcia bez „pomocy” samego obiektu, ponieważ takie zmienne prywatne są naprawdę prywatne , aw konsekwencji niedostępne dla clonefunkcji ogólnej . Przedmiotowy obiekt powinien eksponować własną, dostosowaną do potrzeb clonemetodę.
trincot

Czytałem o tym wiele dzisiejszego wieczoru, a wśród zasobów, które znalazłem, był ten brzydko wyglądający post na blogu, który zawiera kilka hacków umożliwiających dostęp do algorytmu klonowania strukturalnego w przeglądarce: dassur.ma/things/deep-copy
Cat

Odpowiedzi:


67

To naprawdę zależy od tego, co chcesz sklonować. Czy to naprawdę obiekt JSON, czy po prostu dowolny obiekt w JavaScript? Jeśli chcesz zrobić klon, możesz mieć kłopoty. Jaki problem? Wyjaśnię to poniżej, ale najpierw przykład kodu, który klonuje literały obiektów, wszelkie prymitywy, tablice i węzły DOM.

function clone(item) {
    if (!item) { return item; } // null, undefined values check

    var types = [ Number, String, Boolean ], 
        result;

    // normalizing primitives if someone did new String('aaa'), or new Number('444');
    types.forEach(function(type) {
        if (item instanceof type) {
            result = type( item );
        }
    });

    if (typeof result == "undefined") {
        if (Object.prototype.toString.call( item ) === "[object Array]") {
            result = [];
            item.forEach(function(child, index, array) { 
                result[index] = clone( child );
            });
        } else if (typeof item == "object") {
            // testing that this is DOM
            if (item.nodeType && typeof item.cloneNode == "function") {
                result = item.cloneNode( true );    
            } else if (!item.prototype) { // check that this is a literal
                if (item instanceof Date) {
                    result = new Date(item);
                } else {
                    // it is an object literal
                    result = {};
                    for (var i in item) {
                        result[i] = clone( item[i] );
                    }
                }
            } else {
                // depending what you would like here,
                // just keep the reference, or create new object
                if (false && item.constructor) {
                    // would not advice to do that, reason? Read below
                    result = new item.constructor();
                } else {
                    result = item;
                }
            }
        } else {
            result = item;
        }
    }

    return result;
}

var copy = clone({
    one : {
        'one-one' : new String("hello"),
        'one-two' : [
            "one", "two", true, "four"
        ]
    },
    two : document.createElement("div"),
    three : [
        {
            name : "three-one",
            number : new Number("100"),
            obj : new function() {
                this.name = "Object test";
            }   
        }
    ]
})

A teraz porozmawiajmy o problemach, które możesz napotkać, gdy zaczniesz klonować PRAWDZIWE obiekty. Mówię teraz o obiektach, które tworzysz robiąc coś takiego

var User = function(){}
var newuser = new User();

Oczywiście można je sklonować, to nie jest problem, każdy obiekt ujawnia właściwość konstruktora i można go użyć do klonowania obiektów, ale nie zawsze to zadziała. Na for intych obiektach też możesz zrobić proste , ale idzie to w tym samym kierunku - kłopoty. W kodzie zawarłem również funkcję klonowania, ale jest ona wykluczona przez if( false )instrukcję.

Dlaczego więc klonowanie może być uciążliwe? Po pierwsze, każdy obiekt / instancja może mieć jakiś stan. Nigdy nie możesz być pewien, że twoje obiekty nie mają na przykład zmiennych prywatnych, a jeśli tak jest, klonując obiekt, po prostu przełamiesz stan.

Wyobraź sobie, że nie ma państwa, w porządku. Wtedy wciąż mamy inny problem. Klonowanie metodą „konstruktora” spowoduje kolejną przeszkodę. To zależność od argumentów. Nigdy nie można być pewnym, że ktoś, kto stworzył ten obiekt, tego nie zrobił

new User({
   bike : someBikeInstance
});

Jeśli tak jest, nie masz szczęścia, prawdopodobnie someBikeInstance został utworzony w jakimś kontekście i ten kontekst jest nieznany dla metody clone.

Więc co robić? Nadal możesz znaleźć for inrozwiązanie i traktować takie obiekty jak zwykłe literały obiektu, ale może w ogóle jest pomysłem, aby w ogóle nie klonować takich obiektów i po prostu przekazać odniesienie do tego obiektu?

Innym rozwiązaniem jest - można by ustawić konwencję, że wszystkie obiekty, które mają zostać sklonowane, powinny samodzielnie implementować tę część i udostępniać odpowiednią metodę API (np. CloneObject). Coś, co cloneNoderobi dla DOM.

Ty decydujesz.


Przeszedłem przez przeszkodę związaną z przedmiotami, które używają zamknięć, aby ukryć stan. Jak można sklonować obiekt i cały jego stan, ale nadal zapewnić, że klon nie będzie mógł sam zmienić stanu oryginału. Punktem łączącym do result = new item.constructor();bycia złym jest to, że biorąc pod uwagę funkcję konstruktora i obiekt item, powinieneś być w stanie RE wszystkie parametry przekazane do konstruktora.
Raynos,

7
@Raynos: jeśli obiekty używają zamknięć do ukrycia stanu, nie można ich sklonować. Stąd termin „zamknięcie”. Jak mówi nemisj na końcu, najlepszym sposobem jest zaimplementowanie metody API do klonowania (lub serializacji / deserializacji), jeśli taka jest opcja.
Michiel Kalkman

@MichielKalkman Miałem przeczucie, że tak jest. Chociaż ktoś mógł mieć naprawdę sprytne rozwiązanie tego problemu.
Raynos

Jaki jest tego efekt false && item.constructor? Czy to nie jest ifbezużyteczne?
Gabriel Petrovay

2
@GabrielPetrovay To ifjest „bezużyteczne” z funkcjonalnego punktu widzenia, ponieważ nigdy nie zadziała, ale ma akademicki cel pokazania hipotetycznej implementacji, którą można by spróbować, a której autor nie radzi z powodu wyjaśnionego później. Więc tak, będzie wyzwalać elseklauzulę za każdym razem, gdy warunek jest oceniany, a jednak istnieje powód, dla którego kod tam się znajduje.
Gui Imamura

155

Bardzo prosty sposób, może zbyt prosty:

var cloned = JSON.parse(JSON.stringify(objectToClone));

12
Świetnie, chyba że wartość obiektu jest funkcją, w którym to momencie będziesz musiał użyć czegoś takiego jak zaakceptowana odpowiedź. Lub użyj funkcji pomocniczej, takiej jak cloneDeepw Lodash.
matthoiland

31
Jeśli wartość obiektu jest funkcją, obiekt nie ma formatu JSON.
Jos de Jong

5
Jaki przypadek użycia może uzasadniać sklonowanie funkcji, a nie tylko jej używanie?
G. Ghez

3
Jeśli dobrze pamiętam, to również konwertuje daty na ciągi.
Peter

3
@ G.Ghez, jeśli sklonujesz obiekt, który zawiera funkcję, ta funkcja zostanie utracona ..
Peter

38

JSON.parse(JSON.stringify())Połączenie do głębokiej kopii JavaScript obiektów jest nieskuteczna hack, ponieważ była przeznaczona dla danych JSON. Nie obsługuje wartości undefinedlub function () {}i po prostu zignoruje je (lub nullje) podczas „tworzenia łańcuchów” (kierowania) obiektu Javascript do formatu JSON.

Lepszym rozwiązaniem jest użycie funkcji głębokiego kopiowania. Poniższa funkcja głęboko kopiuje obiekty i nie wymaga biblioteki innej firmy (jQuery, LoDash itp.).

function copy(aObject) {
  if (!aObject) {
    return aObject;
  }

  let v;
  let bObject = Array.isArray(aObject) ? [] : {};
  for (const k in aObject) {
    v = aObject[k];
    bObject[k] = (typeof v === "object") ? copy(v) : v;
  }

  return bObject;
}

7
Z wyjątkiem sytuacji, gdy aObject (lub inny obiekt, który zawiera) zawiera odwołanie do siebie samego ... stackoverflow ™!
e2-e4

@ ringø - Czy możesz podać przykłady testowe „samodzielnego odniesienia”?
tfmontague

4
var o = { a:1, b:2 } ; o["oo"] = { c:3, m:o };
e2-e4

4
Podoba mi się to rozwiązanie. Jedyną poprawką dla mnie było radzenie sobie z wartościami zerowymi: bObject[k] = (v === null) ? null : (typeof v === "object") ? copy(v) : v;
David Kirkland

2
Ta funkcja jest prosta, zrozumiała i wyłapuje prawie wszystkie przypadki. W świecie JavaScript jest to tak doskonałe, jak tylko możesz.
icc97

21

Oto funkcja ES6, która będzie działać również dla obiektów z cyklicznymi odwołaniami:

function deepClone(obj, hash = new WeakMap()) {
    if (Object(obj) !== obj) return obj; // primitives
    if (hash.has(obj)) return hash.get(obj); // cyclic reference
    const result = obj instanceof Set ? new Set(obj) // See note about this!
                 : obj instanceof Map ? new Map(Array.from(obj, ([key, val]) => 
                                        [key, deepClone(val, hash)])) 
                 : obj instanceof Date ? new Date(obj)
                 : obj instanceof RegExp ? new RegExp(obj.source, obj.flags)
                 // ... add here any specific treatment for other classes ...
                 // and finally a catch-all:
                 : obj.constructor ? new obj.constructor() 
                 : Object.create(null);
    hash.set(obj, result);
    return Object.assign(result, ...Object.keys(obj).map(
        key => ({ [key]: deepClone(obj[key], hash) }) ));
}

// Sample data
var p = {
  data: 1,
  children: [{
    data: 2,
    parent: null
  }]
};
p.children[0].parent = p;

var q = deepClone(p);

console.log(q.children[0].parent.data); // 1

Uwaga dotycząca zestawów i map

Sposób postępowania z kluczami zestawów i map jest dyskusyjny: te klucze są często prymitywne (w takim przypadku nie ma debaty), ale mogą też być obiektami. W takim przypadku pojawia się pytanie: czy te klucze powinny zostać sklonowane?

Można by argumentować, że należy to zrobić, aby w przypadku mutacji tych obiektów w kopii nie miało to wpływu na obiekty w oryginale i odwrotnie.

Z drugiej strony chciałoby się, aby w przypadku ustawienia / odwzorowania hasklucza było to prawdą zarówno w oryginale, jak i kopii - przynajmniej przed dokonaniem jakiejkolwiek zmiany w którymkolwiek z nich. Byłoby dziwne, gdyby kopia była zestawem / mapą, która ma klucze, które nigdy wcześniej nie występowały (ponieważ zostały utworzone podczas procesu klonowania): z pewnością nie jest to zbyt przydatne dla żadnego kodu, który musi wiedzieć, czy dany obiekt jest wprowadź ten zestaw / mapę, czy nie.

Jak zauważyłeś, jestem bardziej drugiej opinii: klucze zestawów i map to wartości (być może odniesienia ), które powinny pozostać takie same.

Takie wybory często pojawiają się również w przypadku innych (być może niestandardowych) obiektów. Nie ma ogólnego rozwiązania, ponieważ wiele zależy od oczekiwanego zachowania sklonowanego obiektu w konkretnym przypadku.


nie obsługuje daty i
wyrażenia regularnego

1
@mkeremguc, dzięki za komentarz. Zaktualizowałem kod, aby obsługiwał datę i wyrażenie regularne.
trincot

Ładne rozwiązanie, w tym mapa. Brakuje tylko obsługi klonowania zestawów es6.
Robert Biggs

Jeśli się nie mylę, prawdopodobnie możesz dodać obsługę setów za pomocą:if (object instanceof Set) Array.from(object, val => result.add(deepClone(val, hash)));
Robert Biggs

@RobertBiggs, to jest jedna możliwość, ale moim zdaniem, jeśli zestaw ma określony klucz, powinno to być również prawdziwe w sklonowanej wersji tego zestawu. W przypadku Twojego sugerowanego kodu nie miałoby to znaczenia, gdyby klucze były obiektami. Dlatego sugerowałbym nie klonować kluczy - naprawdę myślę, że zachowywałby się bardziej zgodnie z oczekiwaniami. Zobacz aktualizację mojej odpowiedzi w tym zakresie.
trincot

10

Biblioteka contrib underscore.js Biblioteka posiada funkcję o nazwie migawka , że głębokie klonów obiektu

fragment ze źródła:

snapshot: function(obj) {
  if(obj == null || typeof(obj) != 'object') {
    return obj;
  }

  var temp = new obj.constructor();

  for(var key in obj) {
    if (obj.hasOwnProperty(key)) {
      temp[key] = _.snapshot(obj[key]);
    }
  }

  return temp;
}

gdy biblioteka zostanie połączona z projektem, wywołaj funkcję po prostu używając

_.snapshot(object);

4
dobre rozwiązanie, tylko jedna kwestia do zapamiętania: klon i oryginał mają ten sam prototyp. jeśli jest to problem, można dodać "temp .__ proto__ = .snapshot (obj .__ proto_ );" tuż nad „return temp” i aby obsługiwać wbudowane klasy z właściwościami oznaczonymi jako „no enumerate”, można iterować po getOwnPropertyNames () zamiast „for (var key in obj)”
Ronen Ness

3

To jest metoda głębokiego klonowania, której używam. Myślę, że świetnie, mam nadzieję, że przedstawisz sugestie

function deepClone (obj) {
    var _out = new obj.constructor;

    var getType = function (n) {
        return Object.prototype.toString.call(n).slice(8, -1);
    }

    for (var _key in obj) {
        if (obj.hasOwnProperty(_key)) {
            _out[_key] = getType(obj[_key]) === 'Object' || getType(obj[_key]) === 'Array' ? deepClone(obj[_key]) : obj[_key];
        }
    }
    return _out;
}

3

Jak inni zauważyli w tej i podobnych kwestiach, klonowanie „obiektu” w sensie ogólnym jest wątpliwe w JavaScript.

Istnieje jednak klasa obiektów, które nazywam obiektami „danych”, to znaczy te zbudowane po prostu z { ... }literałów i / lub prostych przypisań właściwości lub deserializowane z formatu JSON, dla których warto sklonować. Właśnie dzisiaj chciałem sztucznie zawyżać dane otrzymane z serwera 5x, aby sprawdzić, co dzieje się z dużym zestawem danych, ale obiekt (tablica) i jego elementy potomne musiały być odrębnymi obiektami, aby rzeczy działały poprawnie. Klonowanie pozwoliło mi na pomnożenie mojego zbioru danych:

return dta.concat(clone(dta),clone(dta),clone(dta),clone(dta));

Innym miejscem, w którym często klonuję obiekty danych, jest przesyłanie danych z powrotem do hosta, w którym chcę usunąć pola stanu z obiektu w modelu danych przed wysłaniem. Na przykład mogę chcieć usunąć wszystkie pola zaczynające się od „_” z obiektu w trakcie jego klonowania.

Oto kod, który napisałem, aby zrobić to ogólnie, w tym obsługujące tablice i selektor do wyboru, które elementy mają zostać sklonowane (który używa ciągu „path” do określenia kontekstu):

function clone(obj,sel) {
    return (obj ? _clone("",obj,sel) : obj);
    }

function _clone(pth,src,sel) {
    var ret=(src instanceof Array ? [] : {});

    for(var key in src) {
        if(!src.hasOwnProperty(key)) { continue; }

        var val=src[key], sub;

        if(sel) {
            sub+=pth+"/"+key;
            if(!sel(sub,key,val)) { continue; }
            }

        if(val && typeof(val)=='object') {
            if     (val instanceof Boolean) { val=Boolean(val);        }
            else if(val instanceof Number ) { val=Number (val);        }
            else if(val instanceof String ) { val=String (val);        }
            else                            { val=_clone(sub,val,sel); }
            }
        ret[key]=val;
        }
    return ret;
    }

Najprostszym rozsądnym rozwiązaniem do głębokiego klonu, zakładającym niezerowy obiekt główny i bez wyboru elementu członkowskiego, jest:

function clone(src) {
    var ret=(src instanceof Array ? [] : {});
    for(var key in src) {
        if(!src.hasOwnProperty(key)) { continue; }
        var val=src[key];
        if(val && typeof(val)=='object') { val=clone(val);  }
        ret[key]=val;
        }
    return ret;
    }

2

Lo-Dash , teraz nadzbiór Underscore.js , ma kilka funkcji głębokiego klonowania:

Z odpowiedzi samego autora :

lodash underscore build zapewnia kompatybilność z najnowszą stabilną wersją Underscore.


pytanie brzmiało „nie chcę korzystać z bibliotek”
Femi Oni

@FemiOni to pytanie nie dotyczy bibliotek (nawet w starych edycjach) ... Kilka innych odpowiedzi tutaj również używa jednej lub drugiej biblioteki.
CPHPython

Odpowiedź @FemiOni odrzucona wczoraj. Zastanawiam się ... W każdym razie jest to miejsce do nauki i na wypadek, gdyby ktoś miał zamiar zaimplementować sam deep clone, kod źródłowy lodashbaseClone może dostarczyć pewnych pomysłów.
CPHPython

@FemiOni obiekt JSON nie jest ani biblioteką, ani frameworkiem ... Jeśli zamierzasz zaimplementować tę funkcję, proponuję zajrzeć do jednej z bibliotek open source i użyć potrzebnych części (wiele z nich zostało przetestowanych przez lata). W dłuższej perspektywie pozwoli to uniknąć błędów i pominiętych kwestii.
CPHPython

2

Poniższa funkcja jest najbardziej efektywnym sposobem głębokiego klonowania obiektów javascript.

function deepCopy(obj){
    if (!obj || typeof obj !== "object") return obj;

    var retObj = {};

    for (var attr in obj){
        var type = obj[attr];

        switch(true){
            case (type instanceof Date):
                var _d = new Date();
                _d.setDate(type.getDate())
                retObj[attr]= _d;
                break;

            case (type instanceof Function):
                retObj[attr]= obj[attr];
                break;

            case (type instanceof Array):
                var _a =[];
                for (var e of type){
                    //_a.push(e);
                    _a.push(deepCopy(e));
                }
                retObj[attr]= _a;
                break;

            case (type instanceof Object):
                var _o ={};
                for (var e in type){
                    //_o[e] = type[e];
                    _o[e] = deepCopy(type[e]);
                }
                retObj[attr]= _o;
                break;

            default:
                retObj[attr]= obj[attr];
        }
    }
    return retObj;
}

var obj = {
    string: 'test',
    array: ['1'],
    date: new Date(),
    object:{c: 2, d:{e: 3}},
    function: function(){
        return this.date;
    }
};

var copyObj = deepCopy(obj);

console.log('object comparison', copyObj === obj); //false
console.log('string check', copyObj.string === obj.string); //true
console.log('array check', copyObj.array === obj.array); //false
console.log('date check', copyObj2.date === obj.date); //false
console.log('object check', copyObj.object === obj.object); //false
console.log('function check', copyObj.function() === obj.function()); //true

2
Czy masz jakieś argumenty na poparcie swojego roszczenia?
Fabian von Ellerts

Podałem przykład poniżej funkcji. Czy masz jakieś wątpliwości?
Kooldandy

Te przykłady pokazują, że funkcja działa, co jest fajne, ale dlaczego jest to „najbardziej efektywny sposób”?
Fabian von Ellerts

Ponieważ rekurencyjnie kopiuje atrybuty obiektu w pojedynczej pętli. Również data, funkcja, obiekt, tablica, liczba, łańcuch wszystkie tezy są obsługiwane prawidłowo. Czy masz inny sposób?
Kooldandy

2

Taka funkcja nie powinna być już potrzebna. To tylko akademickie zainteresowanie.

Jako czysto ćwiczenie jest to bardziej funkcjonalny sposób wykonania. Jest to rozszerzenie odpowiedzi @ tfmontague, ponieważ zasugerowałem dodanie tam bloku ochronnego. Ale widząc, że czuję się zmuszony do ES6 i funkcjonalizuję wszystkie rzeczy, oto moja wypasiona wersja. To komplikuje logikę, ponieważ musisz zmapować tablicę i zredukować obiekt, ale unika wszelkich mutacji.

const cloner = (x) => {
    const recurseObj = x => (typeof x === 'object') ? cloner(x) : x
    const cloneObj = (y, k) => {
        y[k] = recurseObj(x[k])
        return y
    }
    // Guard blocks
    // Add extra for Date / RegExp if you want
    if (!x) {
        return x
    }
    if (Array.isArray(x)) {
        return x.map(recurseObj)
    }
    return Object.keys(x).reduce(cloneObj, {})
}
const tests = [
    null,
    [],
    {},
    [1,2,3],
    [1,2,3, null],
    [1,2,3, null, {}],
    [new Date('2001-01-01')], // FAIL doesn't work with Date
    {x:'', y: {yx: 'zz', yy: null}, z: [1,2,3,null]},
    {
        obj : new function() {
            this.name = "Object test";
        }
    } // FAIL doesn't handle functions
]
tests.map((x,i) => console.log(i, cloner(x)))


1

Zauważyłem, że Mapa powinna wymagać specjalnego traktowania, dlatego przy wszystkich sugestiach w tym wątku kod będzie wyglądał następująco:

function deepClone( obj ) {
    if( !obj || true == obj ) //this also handles boolean as true and false
        return obj;
    var objType = typeof( obj );
    if( "number" == objType || "string" == objType ) // add your immutables here
        return obj;
    var result = Array.isArray( obj ) ? [] : !obj.constructor ? {} : new obj.constructor();
    if( obj instanceof Map )
        for( var key of obj.keys() )
            result.set( key, deepClone( obj.get( key ) ) );
    for( var key in obj )
        if( obj.hasOwnProperty( key ) )
            result[key] = deepClone( obj[ key ] );
    return result;
}

1

mój dodatek do wszystkich odpowiedzi

deepCopy = arr => {
  if (typeof arr !== 'object') return arr
  if(arr.pop) return [...arr].map(deepCopy)
  const copy = {}
  for (let prop in arr)
    copy[prop] = deepCopy(arr[prop])
  return copy
}

0

Działa to w przypadku tablic, obiektów i prymitywów. Podwójnie rekurencyjny algorytm, który przełącza się między dwiema metodami przechodzenia:

const deepClone = (objOrArray) => {

  const copyArray = (arr) => {
    let arrayResult = [];
    arr.forEach(el => {
        arrayResult.push(cloneObjOrArray(el));
    });
    return arrayResult;
  }

  const copyObj = (obj) => {
    let objResult = {};
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        objResult[key] = cloneObjOrArray(obj[key]);
      }
    }
    return objResult;
  }

  const cloneObjOrArray = (el) => {
    if (Array.isArray(el)) {
      return copyArray(el);
    } else if (typeof el === 'object') {
      return copyObj(el);
    } else {
      return el;
    }
  }

  return cloneObjOrArray(objOrArray);
}

0

Możemy wykorzystać rekurencję do tworzenia deepCopy. Może tworzyć kopię tablicy, obiektu, tablicy obiektu, obiektu z funkcją. jeśli chcesz, możesz dodać funkcję dla innego rodzaju struktury danych, takiej jak mapa itp.

function deepClone(obj) {
         var retObj;
        _assignProps = function(obj, keyIndex, retObj) {
               var subType = Object.prototype.toString.call(obj[keyIndex]);
               if(subType === "[object Object]" || subType === "[object Array]") {
                    retObj[keyIndex] = deepClone(obj[keyIndex]);
               }
               else {
                     retObj[keyIndex] = obj[keyIndex];
               }
        };

        if(Object.prototype.toString.call(obj) === "[object Object]") {
           retObj = {};
           for(key in obj) {
               this._assignProps(obj, key, retObj);
           }
        }
        else if(Object.prototype.toString.call(obj) == "[object Array]") {
           retObj = [];
           for(var i = 0; i< obj.length; i++) {
              this._assignProps(obj, i, retObj);
            }
        };

        return retObj;
    };

0

Użyj immutableJS

import { fromJS } from 'immutable';

// An object we want to clone
let objA = { 
   a: { deep: 'value1', moreDeep: {key: 'value2'} } 
};

let immB = fromJS(objA); // Create immutable Map
let objB = immB.toJS(); // Convert to plain JS object

console.log(objA); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }
console.log(objB); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }

// objA and objB are equalent, but now they and their inner objects are undependent
console.log(objA === objB); // false
console.log(objA.a === objB.a); // false
console.log(objA.moreDeep === objB.moreDeep); // false

Lub lodash / merge

import merge from 'lodash/merge'

var objA = {
    a: [{ 'b': 2 }, { 'd': 4 }]
};
// New deeply cloned object:
merge({}, objA ); 

// We can also create new object from several objects by deep merge:
var objB = {
    a: [{ 'c': 3 }, { 'e': 5 }]
};
merge({}, objA , objB ); // Object { a: [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }

0

Ten, używając okrągłego odniesienia, działa dla mnie

 //a test-object with circular reference :
 var n1 = {   id:0,   text:"aaaaa",   parent:undefined} 
 var n2 = {  id:1,   text:"zzzzz",   parent:undefined } 
 var o = { arr:[n1,n2],   parent:undefined } 
 n1.parent = n2.parent = o;
 var obj = {   a:1,   b:2,   o:o }
 o.parent = obj;

 function deepClone(o,output){ 

     if(!output) output = {};  
     if(o.______clone) return o.______clone;
     o.______clone = output.______clone = output;

   for(var z in o){

     var obj = o[z];
     if(typeof(obj) == "object") output[z] = deepClone(obj)
     else output[z] = obj; 
    }

   return output;
}

console.log(deepClone(obj));

0

var newDate = nowa data (this.oldDate); Przekazywałem oldDate do funkcji i generowałem newDate z this.oldDate, ale zmieniało to również this.oldDate, więc użyłem tego rozwiązania i zadziałało.


0

To rozwiązanie pozwoli uniknąć problemów z rekurencją podczas korzystania z [... target] lub {... target}

function shallowClone(target) {
  if (typeof a == 'array') return [...target]
  if (typeof a == 'object') return {...target}
  return target
}

/* set skipRecursion to avoid throwing an exception on recursive references */
/* no need to specify refs, or path -- they are used interally */
function deepClone(target, skipRecursion, refs, path) {
  if (!refs) refs = []
  if (!path) path = ''
  if (refs.indexOf(target) > -1) {
    if (skipRecursion) return null
    throw('Recursive reference at ' + path)
  }
  refs.push(target)
  let clone = shallowCopy(target)
  for (i in target) target[i] = deepClone(target, refs, path + '.' + i)
  return clone
}
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.