Jak poprawnie sklonować obiekt JavaScript?


3075

Mam obiektu x. Chciałbym skopiować go jako obiekt y, aby zmiany ynie były modyfikowane x. Zdałem sobie sprawę, że kopiowanie obiektów pochodzących z wbudowanych obiektów JavaScript spowoduje dodatkowe, niepożądane właściwości. To nie jest problem, ponieważ kopiuję jeden z moich dosłownie skonstruowanych obiektów.

Jak poprawnie sklonować obiekt JavaScript?



256
W przypadku JSON używammObj=JSON.parse(JSON.stringify(jsonObject));
Lord Loh.

67
Naprawdę nie rozumiem, dlaczego nikt nie sugeruje Object.create(o), że robi wszystko, o co prosi autor?
froginvasion

43
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; Po zrobieniu tego y.deep.keybędzie również 2, stąd Object.create NIE MOŻE BYĆ UŻYWANY do klonowania ...
Ruben Stolk

17
@ r3wt to nie zadziała ... Proszę pisać tylko po wykonaniu podstawowego testu rozwiązania ..
akshay

Odpowiedzi:


1560

Aby to zrobić dla dowolnego obiektu w JavaScript, nie będzie proste ani jednoznaczne. Będziesz miał problem z błędnym pobieraniem atrybutów z prototypu obiektu, które powinny zostać w prototypie, a nie kopiowane do nowej instancji. Jeśli na przykład dodajesz clonemetodę Object.prototype, jak pokazują niektóre odpowiedzi, musisz jawnie pominąć ten atrybut. Ale co będzie, jeśli zostaną dodane inne dodatkowe metody Object.prototypelub inne pośrednie prototypy, o których nie wiesz? W takim przypadku skopiujesz atrybuty, których nie powinieneś, więc musisz wykryć nieprzewidziane, nielokalne atrybuty za pomocą tej hasOwnPropertymetody.

Oprócz niepoliczalnych atrybutów napotkasz trudniejszy problem podczas próby kopiowania obiektów o ukrytych właściwościach. Na przykład prototypejest ukrytą właściwością funkcji. Do prototypu obiektu odwołuje się również atrybut __proto__, który jest również ukryty i nie będzie kopiowany przez pętlę for / in iterującą atrybuty obiektu źródłowego. Myślę, że __proto__może być specyficzny dla interpretera JavaScript Firefoksa i może być coś innego w innych przeglądarkach, ale dostajesz obraz. Nie wszystko jest policzalne. Możesz skopiować ukryty atrybut, jeśli znasz jego nazwę, ale nie znam żadnego sposobu na jego automatyczne wykrycie.

Kolejną przeszkodą w poszukiwaniu eleganckiego rozwiązania jest problem z prawidłowym skonfigurowaniem dziedziczenia prototypu. Jeśli prototypem obiektu źródłowego jest Object, to po prostu {}będzie działać tworzenie nowego obiektu ogólnego , ale jeśli prototypem źródła jest jakiś potomek Object, będzie brakowało dodatkowych elementów z tego prototypu, który pominięto za pomocą hasOwnPropertyfiltra lub który były w prototypie, ale po pierwsze nie były wymienialne. Jednym z rozwiązań może być wywołanie właściwości obiektu źródłowego w constructorcelu uzyskania początkowego obiektu kopiowania, a następnie skopiowania atrybutów, ale wtedy nadal nie otrzymasz atrybutów niepoliczalnych. Na przykład Dateobiekt przechowuje swoje dane jako ukryty element członkowski:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

Ciąg daty d1będzie o 5 sekund opóźniony d2. Sposobem na uczynienie jednego Datetakim samym jak drugim jest wywołanie setTimemetody, ale jest to specyficzne dla Dateklasy. Nie sądzę, aby istniało ogólne rozwiązanie tego problemu w kuloodporny sposób, choć chętnie bym się mylił!

Gdy miałem wdrożyć ogólne realizuje głębokie kopiowanie skończyło się kompromisów przy założeniu, że będę tylko trzeba skopiować zwykły Object, Array, Date, String, Number, lub Boolean. Ostatnie 3 typy są niezmienne, więc mogłem wykonać płytką kopię i nie martwić się o jej zmianę. Ponadto założyłem, że wszelkie elementy zawarte w Objectlub Arraybędą również jednym z 6 prostych typów na tej liście. Można to osiągnąć za pomocą następującego kodu:

function clone(obj) {
    var copy;

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

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

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

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

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

Powyższa funkcja będzie działać poprawnie dla 6 prostych typów, o których wspomniałem, o ile dane w obiektach i tablicach tworzą strukturę drzewa. Oznacza to, że w obiekcie nie ma więcej niż jednego odwołania do tych samych danych. Na przykład:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

Nie będzie w stanie obsłużyć żadnego obiektu JavaScript, ale może być wystarczający do wielu celów, o ile nie zakładasz, że będzie on działał tylko do wszystkiego, co w niego rzucisz.


5
prawie działał dobrze w nodejs - musiałem tylko zmienić linię dla (var i = 0, var len = obj.length; i <len; ++ i) {na for (var i = 0; i <obj.length; ++ i) {
Trindaz

5
Dla przyszłych pracowników: ta sama głęboka kopia, rekurencyjne przekazywanie referencji zamiast używania instrukcji „return” na stronie gist.github.com/2234277
Trindaz

7
Czy w dzisiejszych czasach JSON.parse(JSON.stringify([some object]),[some revirer function])byłoby rozwiązanie?
KooiInc,

5
Czy na pewno w pierwszym fragmencie tak nie powinno być var cpy = new obj.constructor()?
cyon

8
Przypisywanie obiektów nie wydaje się tworzyć prawdziwej kopii w Chrome, zachowuje odniesienie do oryginalnego obiektu - skończyło się na JSON.stringify i JSON.parse do klonowania - działało idealnie
1owk3y

974

Jeśli nie używasz Dates, funkcji, niezdefiniowanej, regExp lub Infinity w swoim obiekcie, bardzo prosta jedna linijka to JSON.parse(JSON.stringify(object)):

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

Działa to dla wszelkiego rodzaju obiektów zawierających obiekty, tablice, ciągi znaków, wartości logiczne i liczby.

Zobacz także ten artykuł na temat uporządkowanego algorytmu klonowania przeglądarek, który jest używany podczas wysyłania wiadomości do i od pracownika. Zawiera także funkcję głębokiego klonowania.


42
Pamiętaj, że można tego użyć tylko do testowania. Po pierwsze, jest daleki od optymalnego pod względem czasu i zużycia pamięci. Po drugie, nie wszystkie przeglądarki mają tę metodę.
Nux,

2
Zawsze możesz dołączyć JSON2.js lub JSON3.js. W każdym razie potrzebujesz ich do swojej aplikacji. Ale zgadzam się, że to może nie być najlepsze rozwiązanie, ponieważ JSON.stringify nie zawiera odziedziczonych właściwości.
Tim Hong

78
@Nux, Dlaczego nie optymalny pod względem czasu i pamięci? MiJyn mówi: „Powodem, dla którego ta metoda jest wolniejsza niż płytkie kopiowanie (na głębokim obiekcie), jest to, że ta metoda z definicji, głębokie kopie. Ale ponieważ JSON jest 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 czasami może być szybsze niż technika płytkiego kopiowania oparta na javascript (patrz: jsperf.com/cloning-an-object/79). ” stackoverflow.com/questions/122102/…
BeauCielBleu

16
Chcę tylko dodać aktualizację do października 2014 r. Chrome 37+ jest szybszy dzięki JSON.parse (JSON.stringify (oldObject)); Zaletą korzystania z tego jest to, że silnik javascript bardzo łatwo widzi i optymalizuje w celu uzyskania lepszej jakości, jeśli chce.
mirhagk

17
Aktualizacja 2016: Powinno to działać prawie w każdej powszechnie używanej przeglądarce. (zobacz Czy mogę użyć ... ) Głównym pytaniem byłoby teraz, czy jest wystarczająco wydajny.
James Foster

783

Dzięki jQuery możesz płytko kopiować z rozszerzeniem :

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

kolejne zmiany copiedObjectnie będą miały wpływu na originalObjecti odwrotnie.

Lub zrobić głęboką kopię :

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

164
a nawet:var copiedObject = jQuery.extend({},originalObject);
Grant McLean,

82
Przydatne jest również określenie true jako pierwszego parametru dla głębokiego kopiowania:jQuery.extend(true, {}, originalObject);
Will Shaver

6
Tak, uważam ten link za pomocny (takie samo rozwiązanie jak Pascal) stackoverflow.com/questions/122102/...
Garry English,

3
Uwaga: to nie kopiuje proto konstruktora oryginalnego obiektu
Sam Jones

1
Według stackoverflow.com/questions/184710 /... wydaje się, że „płytka kopia” po prostu kopiuje odniesienie do originalObject, więc dlaczego tutaj to mówi ...subsequent changes to the copiedObject will not affect the originalObject, and vice versa.... Przepraszam, że byłem naprawdę zdezorientowany.
Carr

684

W ECMAScript 6 istnieje metoda Object.assign , która kopiuje wartości wszystkich wyliczalnych własnych właściwości z jednego obiektu do drugiego. Na przykład:

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

Należy jednak pamiętać, że zagnieżdżone obiekty są nadal kopiowane jako odniesienie.


Tak, wierzę, że Object.assignto jest właściwa droga. Łatwo jest go również wypełnić

22
Należy również pamiętać, że spowoduje to skopiowanie „metod” zdefiniowanych za pomocą literałów obiektowych (ponieważ są one policzalne), ale nie metod zdefiniowanych przez mechanizm „klasy” (ponieważ nie są one policzalne).
Marcus Junius Brutus

16
Myślę, że należy wspomnieć, że IE nie obsługuje tego z wyjątkiem Edge. Niektóre osoby nadal tego używają.
Saulius

1
To jest to samo co @EugeneTiurin w swojej odpowiedzi.
Wilt,

5
Mówiąc z doświadczenia tutaj ... NIE używaj tego. Obiekty są zaprojektowane w sposób hierarchiczny, a kopiujesz tylko pierwszy poziom, co prowadzi do przypisania całego skopiowanego obiektu. Zaufaj mi, może zaoszczędzić dni drapania głowy.
ow3n

232

Według MDN :

  • Jeśli chcesz płytkiej kopii, użyj Object.assign({}, a)
  • Do kopiowania „głębokiego” użyj JSON.parse(JSON.stringify(a))

Biblioteki zewnętrzne nie są potrzebne, ale najpierw należy sprawdzić zgodność przeglądarki .


8
JSON.parse (JSON.stringify (a)) wygląda pięknie, ale przed użyciem go zalecam przetestowanie czasu potrzebnego na twoją pożądaną kolekcję. W zależności od wielkości obiektu może to wcale nie być najszybszą opcją.
Edza,

3
Zauważyłem metodę JSON, która przekształca obiekty daty w ciągi znaków, ale nie w daty. Muszę radzić sobie z zabawą stref czasowych w Javascript i ręcznie ustalać daty. Mogą być podobne przypadki dla innych typów oprócz dat
Steve Seeger

dla Date użyłbym moment.js, ponieważ ma on clonefunkcjonalność. zobacz więcej tutaj momentjs.com/docs/#/parsing/moment-clone
Tareq

To dobrze, ale
uważaj,

Innym problemem związanym z parsowaniem jsonów są odwołania cykliczne. Jeśli wewnątrz obiektu znajdują się jakieś okrągłe odniesienia, to się
zepsuje

133

Istnieje wiele odpowiedzi, ale żadna nie wspomina o Object.create z ECMAScript 5, który wprawdzie nie daje dokładnej kopii, ale ustawia źródło jako prototyp nowego obiektu.

Dlatego nie jest to dokładna odpowiedź na pytanie, ale jest to rozwiązanie jednorzędowe, a zatem eleganckie. I działa najlepiej w 2 przypadkach:

  1. Gdzie takie dziedzictwo jest przydatne (duh!)
  2. Tam, gdzie obiekt źródłowy nie zostanie zmodyfikowany, dzięki czemu relacja między dwoma obiektami nie będzie problemem.

Przykład:

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

Dlaczego uważam to rozwiązanie za lepsze? Jest natywny, więc nie ma pętli, nie ma rekurencji. Jednak starsze przeglądarki będą wymagały wypełniania.


Uwaga: Object.create nie jest głęboką kopią (itpastorn wspomniał o braku rekurencji). Dowód:var a = {b:'hello',c:{d:'world'}}, b = Object.create(a); a == b /* false */; a.c == b.c /* true */;
Zamnuts

103
Jest to dziedzictwo prototypowe, a nie klonowanie. To są zupełnie różne rzeczy. Nowy obiekt nie ma żadnych własnych właściwości, po prostu wskazuje właściwości prototypu. Klonowanie polega na utworzeniu nowego, świeżego obiektu, który nie odwołuje się do żadnych właściwości w innym obiekcie.
d13

7
Całkowicie się z tobą zgadzam. Zgadzam się również, że nie jest to klonowanie, jak mogłoby być „zamierzone”. Ale chodźcie ludzie, weźcie charakter JavaScript, zamiast próbować znaleźć niejasne rozwiązania, które nie są znormalizowane. Oczywiście, nie lubisz prototypów i wszystkie one są dla ciebie „bła”, ale w rzeczywistości są bardzo przydatne, jeśli wiesz, co robisz.
froginvasion

4
@RobG: W tym artykule wyjaśniono różnicę między odniesieniami a klonowaniem: en.wikipedia.org/wiki/Cloning_(programming) . Object.createwskazuje na właściwości rodzica za pomocą referencji. Oznacza to, że jeśli wartości właściwości rodzica zmienią się, zmieni się również własność dziecka. Ma to zaskakujące skutki uboczne związane z zagnieżdżonymi tablicami i obiektami, które mogą prowadzić do trudnych do znalezienia błędów w kodzie, jeśli nie są ci one znane: jsbin.com/EKivInO/2 . Sklonowany obiekt jest całkowicie nowym, niezależnym obiektem, który ma takie same właściwości i wartości jak rodzic, ale nie jest połączony z rodzicem.
d13

1
To wprowadza w błąd ludzi ... Object.create () może być używany jako sposób dziedziczenia, ale klonowanie nie jest w pobliżu.
prajnavantha

128

Elegancki sposób klonowania obiektu JavaScript 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;
    }
  });
}

Przepraszam za głupie pytanie, ale dlaczego Object.assignbierze dwa parametry, skoro valuefunkcja w poliolinie zajmuje tylko jeden parametr?
Qwertie

@Qwertie wczoraj Wszystkie argumenty są iterowane i łączone w jeden obiekt, nadając priorytet właściwościom z ostatniego przekazanego arg
Eugene Tiurin

Och, rozumiem, dziękuję (wcześniej nie znałem tego argumentsobiektu). Mam problem ze znalezieniem Object()za pośrednictwem Google ... to typecast, prawda?
Qwertie,

44
spowoduje to jedynie płytkie „klonowanie”
Marcus Junius Brutus

1
Ta odpowiedź jest dokładnie taka sama: stackoverflow.com/questions/122102/ ... Wiem, że jest to ta sama osoba, ale powinieneś odwoływać się zamiast kopiować odpowiedź.
lesolorzanov

86

Istnieje wiele problemów z większością rozwiązań w Internecie. Postanowiłem więc podjąć dalsze działania, w tym wyjaśnić, dlaczego zaakceptowana odpowiedź nie powinna zostać zaakceptowana.

sytuacja początkowa

Chcę głęboko skopiować Javascript Objectze wszystkimi jego dziećmi i ich dziećmi i tak dalej. Ale ponieważ nie jestem normalnym programistą, mój Objectma normalny properties , circular structuresa nawetnested objects .

Stwórzmy więc circular structurea a nested objectfirst.

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

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

Połączmy wszystko w jednym Objectnazwanym a.

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

Następnie chcemy skopiować ado zmiennej o nazwie bi zmutować ją.

var b = a;

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

Wiesz, co się tutaj wydarzyło, bo jeśli nie, nie trafiłbyś nawet na to wspaniałe pytanie.

console.log(a, b);

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

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

Teraz znajdźmy rozwiązanie.

JSON

Pierwszą próbą, której próbowałem, było użycie JSON.

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

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

Nie marnuj na to zbyt wiele czasu, dostaniesz TypeError: Converting circular structure to JSON .

Kopiowanie rekurencyjne (zaakceptowana „odpowiedź”)

Rzućmy okiem na przyjętą odpowiedź.

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

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

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

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

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

Wygląda dobrze, heh? Jest to rekurencyjna kopia obiektu i obsługuje również inne typy Date, ale nie było to wymagane.

var b = cloneSO(a);

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

Rekurencja i circular structuresnie działa dobrze razem ...RangeError: Maximum call stack size exceeded

natywne rozwiązanie

Po kłótni z moim współpracownikiem mój szef zapytał nas, co się stało, a po pewnym czasie google znalazł proste rozwiązanie . To się nazywa Object.create.

var b = Object.create(a);

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

To rozwiązanie zostało dodane do Javascript jakiś czas temu, a nawet obsługuje circular structure.

console.log(a, b);

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

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

... i widzisz, to nie działało z zagnieżdżoną strukturą wewnątrz.

polyfill do natywnego rozwiązania

W Object.createstarszej przeglądarce, podobnie jak IE 8., znajduje się polifill . Jest to coś takiego, jak zalecane przez Mozillę i oczywiście nie jest idealne i powoduje ten sam problem co rozwiązanie natywne .

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

var b = clonePF(a);

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

Wyłączyłem Fzakres, abyśmy mogli zobaczyć, co instanceofnam mówi.

console.log(a, b);

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

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

console.log(typeof a, typeof b);

a --> object
b --> object

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

a --> true
b --> true

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

a --> false
b --> true

Ten sam problem co rozwiązanie natywne , ale nieco gorszy wynik.

lepsze (ale nie idealne) rozwiązanie

Podczas kopania znalazłem podobne pytanie (w JavaScript, kiedy wykonuję głęboką kopię, jak mogę uniknąć cyklu, ponieważ właściwość jest „tym”? ) Do tego, ale ze znacznie lepszym rozwiązaniem.

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

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

var b = cloneDR(a);

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

I spójrzmy na wynik ...

console.log(a, b);

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

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

console.log(typeof a, typeof b);

a --> object
b --> object

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

a --> true
b --> true

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

a --> false
b --> false

Wymagania są dopasowane, ale nadal istnieją pewne kwestie, w tym mniejsze zmieniając instanceod nestedi circdo Object.

Struktura drzew dzielących liść nie zostanie skopiowana, staną się dwoma niezależnymi liśćmi:

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

wniosek

Ostatnie rozwiązanie wykorzystujące rekurencję i pamięć podręczną może nie być najlepsze, ale jest to prawdziwa głęboka kopia obiektu. Zajmuje proste properties, circular structuresa nested object, ale to będzie bałagan instancji z nich podczas klonowania.

jsfiddle


11
więc najważniejsze jest uniknięcie tego problemu :)
mikus

@mikus, dopóki nie pojawi się prawdziwa specyfikacja, która obejmuje więcej niż tylko podstawowe przypadki użycia, tak.
Fabio Poloni

2
Dobra analiza powyższych rozwiązań, ale wnioski wyciągnięte przez autora wskazują, że nie ma rozwiązania tego pytania.
Amir Mog

2
Szkoda, że ​​JS nie zawiera natywnej funkcji klonowania.
tys.

1
Wśród wszystkich najlepszych odpowiedzi wydaje mi się, że jest to poprawne.
KTU

77

Jeśli nie masz problemu z płytką kopią, biblioteka underscore.js ma metodę klonowania .

y = _.clone(x);

lub możesz to przedłużyć

copiedObject = _.extend({},originalObject);

2
Dzięki. Korzystanie z tej techniki na serwerze Meteor.
Turbo

Aby szybko zacząć korzystać z lodash, polecam naukę npm, Browserify, a także lodash. Mam klon do pracy z „npm i --save lodash.clone”, a następnie „var clone = wymagają („ lodash.clone ”); ' Aby zacząć wymagać pracy, potrzebujesz czegoś takiego jak Browserify. Gdy go zainstalujesz i dowiesz się, jak to działa, będziesz używać „Browserify yourfile.js> bundle.js; uruchom chrome index.html” za każdym razem, gdy uruchomisz kod (zamiast bezpośrednio w Chrome). Spowoduje to zebranie pliku i wszystkich wymaganych plików z modułu npm do pliku bundle.js. Prawdopodobnie możesz zaoszczędzić czas i zautomatyzować ten krok za pomocą Gulp.
Aaron Bell,

64

OK, wyobraź sobie, że masz ten obiekt poniżej i chcesz go sklonować:

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

lub

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

odpowiedź zależy głównie od używanego skryptu ECMA , w ES6+którym można po prostu użyć Object.assigndo wykonania klonowania:

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

lub za pomocą operatora spreadu w następujący sposób:

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

Ale jeśli używasz ES5, możesz użyć kilku metod, ale JSON.stringifyupewnij się, że nie używasz dużej części danych do skopiowania, ale w wielu przypadkach może to być jeden wiersz przydatny, coś takiego:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over

Czy możesz podać przykład tego, co big chunk of databy się równało? 100kb? 100 MB? Dzięki!
user1063287,

Tak, @ user1063287, że w zasadzie większe dane, wydajność gorsza ... więc to naprawdę zależy, a nie KB, MB lub GB, to więcej o tym, ile razy też chcesz to zrobić ... Również to nie będzie działać dla funkcji i innych rzeczy ...
Alireza

3
Object.assignwykonuje płytką kopię (podobnie jak spread, @Alizera)
Bogdan D

Nie możesz użyć let in es5: ^) @Alireza
SensationSama

40

Jednym szczególnie nieelegacyjnym rozwiązaniem jest użycie kodowania JSON do tworzenia głębokich kopii obiektów, które nie mają metod składowych. Metodologia polega na kodowaniu JSON obiektu docelowego, a następnie poprzez dekodowanie otrzymujesz kopię, której szukasz. Możesz dekodować tyle razy, ile chcesz, aby wykonać tyle kopii, ile potrzebujesz.

Oczywiście funkcje nie należą do JSON, więc działa to tylko dla obiektów bez metod składowych.

Ta metodologia była idealna dla mojego przypadku użycia, ponieważ przechowuję obiekty BLS JSON w magazynie kluczy i wartości, a gdy są one ujawniane jako obiekty w JavaScript API, każdy obiekt faktycznie zawiera kopię oryginalnego stanu obiektu, więc może obliczyć deltę po tym, jak osoba wywołująca zmutuje odsłonięty obiekt.

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

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

object2.key = "a change";
console.log(object1);// returns value

Dlaczego funkcje nie należą do JSON? Widziałem ich przeniesionych jako JSON więcej niż raz ...
the_drow

5
Funkcje nie są częścią specyfikacji JSON, ponieważ nie są bezpiecznym (ani inteligentnym) sposobem przesyłania danych, do czego został stworzony JSON. Wiem, że natywny koder JSON w Firefoksie po prostu ignoruje przekazane mu funkcje, ale nie jestem pewien co do zachowania innych.
Kris Walker

1
@mark: { 'foo': function() { return 1; } }jest dosłownie skonstruowanym obiektem.
abarnert

Funkcje @abarnert nie są danymi. „Literały funkcji” są mylące - ponieważ funkcje mogą zawierać dowolny kod, w tym przypisania i wszelkiego rodzaju rzeczy, które nie są serializowane.
vemv

35

Możesz po prostu użyć właściwości spread do skopiowania obiektu bez odwołań. Ale bądź ostrożny (patrz komentarze), „kopia” jest tylko na najniższym poziomie obiektu / tablicy. Zagnieżdżone właściwości są nadal referencjami!


Kompletny klon:

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

// => mutate without references:

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

Klonuj z referencjami na drugim poziomie:

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

// => nested object is still a references:

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

JavaScript faktycznie nie obsługuje natywnie głębokich klonów. Użyj funkcji narzędzia. Na przykład Ramda:

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


1
To nie działa ... działałoby prawdopodobnie, gdy x będzie tablicą, na przykład x = ['ab', 'cd', ...]
Kamil Kiełczewski

3
Działa, ale pamiętaj, że jest to PŁYTKA kopia, dlatego wszelkie głębokie odniesienia do innych obiektów pozostają odniesieniami!
Królik Bugs

Częściowy klon może się również zdarzyć w ten sposób:const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}
Cristian Traìna

25

Dla osób korzystających z AngularJS istnieje również bezpośrednia metoda klonowania lub rozszerzania obiektów w tej bibliotece.

var destination = angular.copy(source);

lub

angular.copy(source, destination);

Więcej w dokumentacji angular.copy ...


2
To jest głęboka kopia FYI.
zamnuts,

22

Oto funkcja, której możesz użyć.

function clone(obj) {
    if(obj == null || typeof(obj) != 'object')
        return obj;    
    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);    
    return temp;
}

10
Ta odpowiedź jest dość bliska, ale niezupełnie poprawna. Jeśli spróbujesz sklonować obiekt Date, nie otrzymasz tej samej daty, ponieważ wywołanie funkcji konstruktora Date inicjuje nową datę z bieżącą datą / godziną. Ta wartość nie jest wyliczalna i nie będzie kopiowana przez pętlę for / in.
A. Levy,

Nie idealne, ale miłe w tych podstawowych przypadkach. Np. Umożliwia proste klonowanie argumentu, który może być podstawowym obiektem, tablicą lub łańcuchem.
james_womack

Upvoted za poprawne wywołanie konstruktora za pomocą new. Przyjęta odpowiedź nie.
GetFree,

działa na węźle wszystko inne! wciąż pozostawione linki referencyjne
użytkownik956584

Myśl rekurencyjna jest świetna. Ale jeśli wartością jest tablica, zadziała?
Q10Viking

22

Odpowiedź A.Levy jest prawie kompletna, oto mój mały wkład: istnieje sposób, w jaki sposób obsługiwać rekurencyjne referencje , zobacz ten wiersz

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

Jeśli obiektem jest element XML DOM, musimy zamiast tego użyć cloneNode

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

Zainspirowany wyczerpującymi badaniami A.Levy'ego i podejściem do prototypowania Calvina, oferuję to rozwiązanie:

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

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

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

Zobacz także notatkę Andy'ego Burke'a w odpowiedziach.


3
Date.prototype.clone = function() {return new Date(+this)};
RobG

22

Z tego artykułu: Jak skopiować tablice i obiekty w JavaScript autorstwa Briana Huismana:

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

4
Jest blisko, ale nie działa dla żadnego obiektu. Spróbuj sklonować przy użyciu tego obiektu Date. Nie wszystkie właściwości są policzalne, więc nie wszystkie będą wyświetlane w pętli for / in.
A. Levy,

Dodanie do prototypu obiektu takiego jak ten zepsuło dla mnie jQuery. Nawet kiedy przemianowałem na clone2.
iPadDeveloper2011

3
@ iPadDeveloper2011 W powyższym kodzie był błąd, w wyniku którego utworzono zmienną globalną o nazwie „i” (dla i w tym), a nie „(dla i i w tym)”. Mam wystarczającą ilość karmy, aby ją edytować i naprawić, więc tak zrobiłem.
mikemaccana

1
@ Calvin: należy utworzyć właściwość niewymienną, w przeciwnym razie w pętlach „for” pojawi się „klon”.
mikemaccana

2
dlaczego też nie var copiedObj = Object.create(obj);jest świetny sposób?
Dan P.

19

W ES-6 możesz po prostu użyć Object.assign (...). Dawny:

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

Dobry odnośnik znajduje się tutaj: https://googlechrome.github.io/samples/object-assign-es6/


12
Nie powoduje głębokiego klonowania obiektu.
sierpień

To zadanie, a nie kopia. clone.Title = „just a clone” oznacza, że ​​obj.Title = „just a clone”.
HoldOffHunger

@HoldOffHunger Mylisz się. Sprawdź to w konsoli JS przeglądarki ( let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); clone.title = "Whazzup";)
podsumowanie

@collapsar: Dokładnie to sprawdziłem, wtedy console.log (osoba) będzie „Whazzup”, a nie „Thor Odinson”. Zobacz komentarz Augusta.
HoldOffHunger

1
@HoldOffHunger Nie dzieje się w Chrome 60.0.3112.113 ani w Edge 14.14393; Komentarz Augusta nie ma zastosowania, ponieważ wartości pierwotnych typów objwłaściwości są rzeczywiście klonowane. Wartości właściwości, które same są obiektami, nie będą klonowane.
Collapsar

18

W ECMAScript 2018

let objClone = { ...obj };

Pamiętaj, że zagnieżdżone obiekty są nadal kopiowane jako odniesienie.


1
Dzięki za podpowiedź, że zagnieżdżone obiekty są nadal kopiowane jako odniesienie! Niemal zwariowałem podczas debugowania mojego kodu, ponieważ zmodyfikowałem zagnieżdżone właściwości w „klonie”, ale oryginał został zmodyfikowany.
Benny Neugebauer

To jest ES2016, a nie 2018, a tej odpowiedzi udzielono dwa lata wcześniej .
Dan Dascalescu,

więc co jeśli chcę również kopię właściwości zagnieżdżonej
Sunil Garg

1
@SunilGarg Aby skopiować również właściwość zagnieżdżoną, możesz użyć const objDeepClone = JSON.parse(JSON.stringify(obj));
Pavan Garre

16

Korzystanie z Lodash:

var y = _.clone(x, true);

5
OMG, szalenie byłoby na nowo odkryć klonowanie. To jedyna rozsądna odpowiedź.
Dan Ross,

5
Wolę, _.cloneDeep(x)ponieważ zasadniczo jest to to samo, co powyżej, ale czyta lepiej.
garbanzio,


13

Możesz sklonować obiekt i usunąć dowolne odniesienie z poprzedniego, używając jednego wiersza kodu. Po prostu wykonaj:

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

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

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

W przeglądarkach / silnikach, które obecnie nie obsługują Object.create, możesz użyć tego wypełniania:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}

1
+1 Object.create(...)wydaje się zdecydowanie właściwą drogą.
René Nyffenegger

Idealna odpowiedź. Może mógłbyś dodać wyjaśnienie Object.hasOwnProperty? W ten sposób ludzie wiedzą, jak zapobiec przeszukiwaniu prototypowego łącza.
froginvasion

Działa dobrze, ale w jakich przeglądarkach działa polifill?
Ian Lunn

11
To tworzy obj2 z obj1 jako prototypem. Działa tylko dlatego, że przesłaniasz element textczłonkowski w obj2. Nie tworzysz kopii, po prostu odkładasz na łańcuch prototypów, gdy nie ma elementu na obj2.
Nick Desaulniers

2
To NIE tworzy go „bez referencji”, po prostu przenosi referencję do prototypu. To wciąż referencja. Jeśli właściwość zmieni się w oryginale, zmieni się również właściwość prototypu w „klonie”. To wcale nie jest klon.
Jimbo Jonny

13

Nowa odpowiedź na stare pytanie! Jeśli masz przyjemność korzystać z ECMAScript 2016 (ES6) z Spread Syntax , jest to łatwe.

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

Zapewnia to czystą metodę płytkiej kopii obiektu. Wykonywanie głębokiej kopii, co oznacza tworzenie nowej kopii każdej wartości w każdym rekurencyjnie zagnieżdżonym obiekcie, wymaga jednego z cięższych rozwiązań powyżej.

JavaScript ewoluuje.


2
nie działa, gdy masz zdefiniowane funkcje na obiektach
Petr Marek

o ile widzę, operator rozprzestrzeniania działa tylko z iterables - developer.mozilla.org mówi: var obj = {'key1': 'value1'}; var array = [...obj]; // TypeError: obj is not iterable
Oleh

@Oleh, więc używaj `{... obj} zamiast [... obj];`
manikant gautam

@manikantgautam Wcześniej korzystałem z Object.assign (), ale teraz rzeczywiście składnia rozprzestrzeniania obiektów jest obsługiwana w najnowszym Chrome, Firefox (wciąż nie w Edge i Safari). Jego propozycja ECMAScript ... ale Babel obsługuje ją tak dalece, jak widzę, więc prawdopodobnie jest bezpieczny w użyciu.
Oleh

12
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

Rozwiązanie ES6, jeśli chcesz (płytko) sklonować instancję klasy, a nie tylko obiekt właściwości.


Czym to się różni let cloned = Object.assign({}, obj)?
ceztko

10

Myślę, że jest prosta i działająca odpowiedź. W głębokim kopiowaniu istnieją dwie obawy:

  1. Zachowaj właściwości niezależne od siebie.
  2. I utrzymuj metody przy życiu na sklonowanym obiekcie.

Myślę więc, że jednym prostym rozwiązaniem będzie najpierw serializacja i deserializacja, a następnie przypisanie jej do funkcji kopiowania.

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

Chociaż na to pytanie jest wiele odpowiedzi, mam nadzieję, że to również pomaga.


Chociaż jeśli mogę importować lodash, wolę używać lodash cloneDeep.
ConductClever

2
Używam JSON.parse (JSON.stringify (źródło)). Zawsze pracujący.
Misha

2
@Misha, w ten sposób przegapisz funkcje. Termin „działa” ma wiele znaczeń.
ConductClever

I pamiętaj, że, jak wspomniałem, tylko funkcje pierwszej warstwy zostaną skopiowane. Jeśli więc mamy w sobie jakieś obiekty, to jedynym sposobem jest rekurencyjne kopiowanie pola po polu.
ConductClever

9

Aby uzyskać głębokie kopiowanie i klonowanie, JSON.stringify, a następnie JSON.parse obiektu:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}

całkiem sprytne ... jakieś wady tego podejścia?
Aleks

8

Jest to adaptacja kodu A. Levy'ego do obsługi klonowania funkcji i wielokrotnych / cyklicznych odwołań - oznacza to, że jeśli dwie właściwości w klonowanym drzewie są odwołaniami do tego samego obiektu, klonowane drzewo obiektów będzie miało te właściwości wskazują na jeden i ten sam klon odnośnego obiektu. To rozwiązuje również przypadek cyklicznych zależności, które, jeśli nie są obsługiwane, prowadzą do nieskończonej pętli. Złożoność algorytmu wynosi O (n)

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

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

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

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

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

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

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

            clonedObjectsArray[objectId(obj)] = copy;

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

            return copy;
        }       


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



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

    return cloneObj;
}

Kilka szybkich testów

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

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

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

objclone = clone(obj);

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

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

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));

1
Od września 2016 r. Jest to jedyne prawidłowe rozwiązanie tego pytania.
DomQ

6

Chciałem tylko dodać do wszystkich Object.createrozwiązań w tym poście, że nie działa to w pożądany sposób z nodejs.

W Firefox wynik

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

jest

{test:"test"}.

W nodejs jest

{}

Jest to dziedzictwo prototypowe, a nie klonowanie.
d13

1
@ d13, dopóki twój argument jest poprawny, zwróć uwagę, że w JavaScript nie ma standardowego sposobu klonowania obiektu. Jest to dziedzictwo prototypowe, ale można je jednak wykorzystać jako klony, jeśli zrozumiesz pojęcia.
froginvasion

@froginvasion. Jedynym problemem związanym z używaniem Object.create jest to, że zagnieżdżone obiekty i tablice są po prostu referencjami do zagnieżdżonych obiektów i tablic prototypu. jsbin.com/EKivInO/2/edit?js,console . Technicznie „sklonowany” obiekt powinien mieć własne unikalne właściwości, które nie są wspólnymi odniesieniami do właściwości innych obiektów.
d13

@ d13 ok, rozumiem teraz twój punkt widzenia. Ale miałem na myśli to, że zbyt wielu ludzi jest wyobcowanych z koncepcją dziedziczenia prototypowego i dla mnie nie rozumiem, jak to działa. Jeśli się nie mylę, twój przykład można naprawić, po prostu dzwoniąc, Object.hasOwnPropertyaby sprawdzić, czy jesteś właścicielem tablicy, czy nie. Tak, powoduje to dodatkową złożoność w przypadku dziedziczenia prototypowego.
froginvasion

6
function clone(src, deep) {

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

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

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

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

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

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

    }

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

    return ret;
};

2
if(!src && typeof src != "object"){. Myślę, że tak ||nie powinno być &&.
MikeM

6

Ponieważ mindeavour stwierdził, że klonowany obiekt jest obiektem „zbudowanym dosłownie”, rozwiązaniem może być po prostu wielokrotne wygenerowanie obiektu zamiast klonowania instancji obiektu:

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

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

6

Napisałem własną implementację. Nie jestem pewien, czy liczy się to jako lepsze rozwiązanie:

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

Poniżej przedstawiono wdrożenie:

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

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

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

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

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

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

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

    return result;
}

nie działa dla mojego obiektu, chociaż moja sprawa jest trochę skomplikowana.
Sajuuk
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.