Nodejs: jak sklonować obiekt


Odpowiedzi:


178

W przypadku narzędzi i klas, w których nie ma potrzeby wyciskania każdej kropli wydajności, często oszukuję i po prostu używam JSON do wykonania głębokiej kopii:

function clone(a) {
   return JSON.parse(JSON.stringify(a));
}

To nie jest jedyna odpowiedź ani najbardziej elegancka odpowiedź; wszystkie pozostałe odpowiedzi należy wziąć pod uwagę w przypadku wąskich gardeł produkcyjnych. Jest to jednak szybkie i brudne rozwiązanie, dość skuteczne i przydatne w większości sytuacji, w których sklonowałbym prosty skrót właściwości.


2
@djechlin Jasne, że tak. Wypróbuj: jsfiddle.net/katowulf/E5jC3 (testowane z węzłem 0.10.11) Nie będzie w stanie odtworzyć funkcji ani danych prototypowych, ale wartości są w porządku.
Kato

13
Spowoduje to konwersję dat na łańcuchy
wstecz

4
@Backus Jak również obiekty i funkcje.
Kato,

2
@Kato, Dlaczego cykliczne odniesienia sugerują, że nie ma potrzeby wykonywania głębokich kopii? To dwie niezwiązane ze sobą rzeczy. Z pewnością możliwe jest napisanie dobrej metody klonowania dla NodeJS, która obsługuje odwołania cykliczne i nie opiera się na używaniu JSON do klonowania. github.com/pvorb/node-clone
Samuel Neff

2
Już odnotowano w komentarzach i opisie. Nie możesz sklonować funkcji, prototypu obiektu ani innych rzeczy, których nie powinieneś próbować robić za pomocą ogólnej metody klonowania. Będziesz potrzebował do tego więcej niż jednej linii kodu i prawdopodobnie i tak nie powinieneś klonować tych śmieci - umieść metodę klonowania w swojej klasie, która wie, jak obsługiwać elementy wewnętrzne i zrób tonewObj = obj.clone(args...);
Kato,

35

W żadnej z powyższych odpowiedzi nie wspomniano o Object. assign.

let cloned = Object.assign({}, source);

Jeśli korzystasz z ES6, możesz użyć operatora spreadu:

let cloned = { ... source };

Źródła: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/ assign


29
Zauważ, że oba są płytkimi klonami, a operator rozprzestrzeniania na obiektach wymaga node.js 8+.
jlh

1
Jak wspomniał @jlh, nie będzie to działać w przypadku obiektów zagnieżdżonych. Możesz przeczytać więcej o problemach z głębokim klonem tej metody tutaj: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Shnd

Jak powiedział Shnd, ta odpowiedź jest nieprawidłowa. W przypadku głębokiego klonowania musimy użyć innych alternatyw, ponieważ Object. assign () kopiuje wartości właściwości. Jeśli wartość źródłowa jest odniesieniem do obiektu, kopiuje tylko tę wartość odniesienia.
troy

1
To nie jest klonem, to po prostu odwołuje się do oryginalnego obiektu, czyli jeśli coś zmienić to pójdzie do pana
Aidan Welch

@AidanWelch Nie sądzę, żeby to było poprawne. let a = {x:1}; let b = Object.assign({}, a); a.x = 2; a.y = 1; console.log(a, b);
Miguel Pynto

20

Istnieje kilka modułów Node, jeśli nie chcesz „tworzyć własnych”. Ten wygląda dobrze: https://www.npmjs.com/package/clone

Wygląda na to, że obsługuje różne rzeczy, w tym odwołania cykliczne. Ze strony github :

clone masters klonuje obiekty, tablice, obiekty Date i obiekty RegEx. Wszystko jest klonowane rekurencyjnie, dzięki czemu można na przykład klonować daty w tablicach w obiektach. [...] Okólnik? Tak!


10

Trudno jest wykonać ogólną, ale użyteczną operację klonowania, ponieważ to, co powinno być klonowane rekurencyjnie, a co powinno zostać skopiowane, zależy od tego, jak powinien działać określony obiekt.

Coś, co może być przydatne, to

function clone(x)
{
    if (x === null || x === undefined)
        return x;
    if (typeof x.clone === "function")
        return x.clone();
    if (x.constructor == Array)
    {
        var r = [];
        for (var i=0,n=x.length; i<n; i++)
            r.push(clone(x[i]));
        return r;
    }
    return x;
}

W tym kodzie logika jest

  • w przypadku nulllub undefinedpo prostu zwróć to samo (potrzebny jest specjalny przypadek, ponieważ próba sprawdzenia, czy clonemetoda jest obecna, jest błędem )
  • czy obiekt ma clonemetodę? następnie użyj tego
  • czy obiekt jest tablicą? następnie wykonaj operację klonowania rekurencyjnego
  • w przeciwnym razie po prostu zwróć tę samą wartość

Ta funkcja klonowania powinna umożliwiać łatwe wdrażanie niestandardowych metod klonowania ... na przykład

function Point(x, y)
{
    this.x = x;
    this.y = y;

    ...
}

Point.prototype.clone = function()
{
    return new Point(this.x, this.y);
};



function Polygon(points, style)
{
    this.points = points;
    this.style = style;

    ...
}

Polygon.prototype.clone = function()
{
    return new Polygon(clone(this.points),
                       this.style);
};

Kiedy w obiekcie wiesz, że poprawna operacja klonowania dla określonej tablicy to tylko płytka kopia, możesz wywołać values.slice()zamiast clone(values).

Na przykład w powyższym kodzie wyraźnie wymagam, aby klonowanie obiektu wielokąta sklonowało punkty, ale będzie miało ten sam obiekt stylu. Jeśli zamiast tego chcę sklonować również obiekt stylu, mogę po prostu przejść clone(this.style).


1
+1 za wspomnienie, że obiekty muszą .clonesame zaimplementować metodę. To najlepszy sposób radzenia sobie z klonowaniem obiektów.
Raynos

3
if (x.clone)powinno byćif (typeof x.clone === 'function')
Yanick Rochon

@YanickRochon: Dzięki, naprawiono. Przepraszam, jakoś wcześniej nie zauważyłem tego komentarza ...
6502

9

Nie ma natywnej metody klonowania obiektów. Podkreślone narzędzia, _.clonektóre są płytkim klonem.

_.clone = function(obj) {
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

Tnie go lub wydłuża.

Tutaj jest _.extend

// extend the obj (first parameter)
_.extend = function(obj) {
  // for each other parameter
  each(slice.call(arguments, 1), function(source) {
    // loop through all properties of the other objects
    for (var prop in source) {
      // if the property is not undefined then add it to the object.
      if (source[prop] !== void 0) obj[prop] = source[prop];
    }
  });
  // return the object (first parameter)
  return obj;
};

Rozszerz po prostu iteruje po wszystkich elementach i tworzy nowy obiekt z zawartymi w nim elementami.

Jeśli chcesz, możesz wdrożyć własną naiwną implementację

function clone(o) {
  var ret = {};
  Object.keys(o).forEach(function (val) {
    ret[val] = o[val];
  });
  return ret;
}

Istnieją dobre powody, aby unikać głębokiego klonowania, ponieważ nie można klonować zamknięć.

Osobiście zadałem pytanie deep cloning objects beforei doszedłem do wniosku, że po prostu tego nie robisz.

Moją rekomendacją jest użycie underscorei jest to _.clonemetoda na płytkie klony


9

W przypadku płytkiej kopii lubię używać wzoru redukcji (zwykle w module lub takim), na przykład:

var newObject = Object.keys(original).reduce(function (obj, item) {
    obj[item] = original[item];
    return obj;
},{});

Oto plik jsperf dla kilku opcji: http://jsperf.com/shallow-copying


prosty, elegancki, genialny. Z niecierpliwością czekam na Wasze inne wkłady SO ^ _ ^
Dziękuję

7

Stare pytanie, ale istnieje bardziej elegancka odpowiedź niż to, co sugerowano do tej pory; użyj wbudowanych narzędzi ._extend:

var extend = require("util")._extend;

var varToCopy = { test: 12345, nested: { val: 6789 } };

var copiedObject = extend({}, varToCopy);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 } }

Zwróć uwagę na użycie pierwszego parametru z pustym obiektem {} - mówi to rozszerzeniu, że skopiowany obiekt (y) muszą zostać skopiowane do nowego obiektu. Jeśli użyjesz istniejącego obiektu jako pierwszego parametru, to drugi (i wszystkie kolejne) parametry zostaną głęboko scalone i skopiowane na pierwszą zmienną parametru.

Korzystając z przykładowych zmiennych powyżej, możesz również zrobić to:

var anotherMergeVar = { foo: "bar" };

extend(copiedObject, { anotherParam: 'value' }, anotherMergeVar);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 }, anotherParam: 'value', foo: 'bar' }

Bardzo przydatne narzędzie, szczególnie tam, gdzie jestem przyzwyczajony do rozszerzania w AngularJS i jQuery.

Mam nadzieję, że to pomoże komuś innemu; nadpisywanie odniesień do obiektów jest nieszczęściem, a to rozwiązuje je za każdym razem!


1
Korzystanie z _extend jest teraz amortyzowane. Zamiast tego
użyj Object. assign

7

Możesz również użyć lodash . Ma metody clone i cloneDeep .

var _= require('lodash');

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

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true

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

1

W zależności od tego, co chcesz zrobić ze sklonowanym obiektem, możesz wykorzystać prototypowy mechanizm dziedziczenia javascript i uzyskać sklonowany obiekt poprzez:

var clonedObject = Object.create(originalObject);

Pamiętaj tylko, że to nie jest pełny klon - na dobre lub na złe.

Dobrą rzeczą jest to, że w rzeczywistości nie zduplikowałeś obiektu, więc zużycie pamięci będzie niskie.

Niektóre trudne rzeczy do zapamiętania w tej metodzie są takie, że iteracja właściwości zdefiniowanych w łańcuchu prototypów czasami działa nieco inaczej oraz fakt, że wszelkie zmiany w oryginalnym obiekcie wpłyną również na sklonowany obiekt, chyba że ta właściwość została również ustawiona na siebie .


3
To jest tak absurdalnie niebezpieczne iw ogóle nie przypomina klona. var a = {foo: "bar"}, b = Object.create(a); a.foo = "broken"; console.log(b.foo); // "broken";
Dziękuję

@naomik: b.foo = "working"; console.log(a.foo); // still "broken";Trzeba oczywiście mieć świadomość, że zmiany w oryginalnym obiekcie zostaną odzwierciedlone w „klonie”, a zmiany w „klonie” nie zostaną odzwierciedlone w oryginalnym obiekcie - ale nie nazwałbym tego niebezpiecznym - wszystko zależy od tego, co chcesz zrobić ze swoim klonem
VoxPelli

@naomik: Jestem całkiem nowy w JS, więc chciałbym doprecyzować. Płytki klon zaproponowany przez olli-k i który podobał Ci się (IMHO, ale mogę się mylić) ma te same ograniczenia, co rozwiązanie zaproponowane przez voxpelli. Oryginalne i płytkie zamknięcie również będzie miało te same „elementy”, aktualizacja elementu w oryginale również wpłynie na płytki klon. Lub?
bmorin

@bmornin, jeśli używasz techniki Olli K, modyfikowanie oryginalnego obiektu nie spowoduje zmiany w nowym obiekcie.
Dziękuję

1
@VoxPelli Celem „klonu” jest odizolowanie zmian od oryginalnego obiektu, stąd ta metoda jest bardziej niebezpieczna.
Doug

1

Zaimplementowałem pełną głęboką kopię. Uważam, że jest to najlepszy wybór dla ogólnej metody klonowania, ale nie obsługuje ona odwołań cyklicznych.

Przykład użycia:

parent = {'prop_chain':3}
obj = Object.create(parent)
obj.a=0; obj.b=1; obj.c=2;

obj2 = copy(obj)

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3
console.log(obj2, obj2.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3

parent.prop_chain=4
obj2.a = 15

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 4
console.log(obj2, obj2.prop_chain)
// '{'a':15, 'b':1, 'c':2} 4

Sam kod:

Ten kod kopiuje obiekty wraz z ich prototypami, kopiuje również funkcje (może się to komuś przydać).

function copy(obj) {
  // (F.prototype will hold the object prototype chain)
  function F() {}
  var newObj;

  if(typeof obj.clone === 'function')
    return obj.clone()

  // To copy something that is not an object, just return it:
  if(typeof obj !== 'object' && typeof obj !== 'function' || obj == null)
    return obj;

  if(typeof obj === 'object') {    
    // Copy the prototype:
    newObj = {}
    var proto = Object.getPrototypeOf(obj)
    Object.setPrototypeOf(newObj, proto)
  } else {
    // If the object is a function the function evaluate it:
    var aux
    newObj = eval('aux='+obj.toString())
    // And copy the prototype:
    newObj.prototype = obj.prototype
  }

  // Copy the object normal properties with a deep copy:
  for(var i in obj) {
    if(obj.hasOwnProperty(i)) {
      if(typeof obj[i] !== 'object')
        newObj[i] = obj[i]
      else
        newObj[i] = copy(obj[i])
    }
  }

  return newObj;
}

W przypadku tej kopii nie mogę znaleźć żadnej różnicy między oryginałem a skopiowanym, z wyjątkiem tego, czy oryginał używał zamknięć w swojej konstrukcji, więc myślę, że jest to dobra implementacja.

Mam nadzieję, że to pomoże


1

dla tablicy można użyć

var arr = [1,2,3]; 
var arr_2 = arr ; 

print ( arr_2 ); 

arr = arr.slice (0);

print ( arr ); 

arr[1]=9999; 

print ( arr_2 ); 

1

Obiekty i tablice w JavaScript używają wywołania przez odniesienie, jeśli zaktualizujesz skopiowaną wartość, może to odzwierciedlać oryginalny obiekt. Aby temu zapobiec, możesz głęboko sklonować obiekt, aby zapobiec przekazaniu odniesienia, używając lodash biblioteki cloneDeep metoda uruchom polecenie

npm zainstaluj lodash

const ld = require('lodash')
const objectToCopy = {name: "john", age: 24}
const clonedObject = ld.cloneDeep(objectToCopy)
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.