Klonowanie obiektu w Node.js

203

Jaki jest najlepszy sposób klonowania obiektu w node.js

np. chcę uniknąć sytuacji, w której:

var obj1 = {x: 5, y:5};
var obj2 = obj1;
obj2.x = 6;
console.log(obj1.x); // logs 6

Obiekt może zawierać złożone typy jako atrybuty, więc proste dla (var x w obj1) nie rozwiązałoby się. Czy muszę sam napisać rekurencyjny klon, czy jest coś wbudowanego, czego nie widzę?

slifty
źródło
23
1. npm install underscore2. var _ = require('underscore')3 _.clone(objToClone).;
Salman von Abbas
4
Zauważ, że w powyższym komentarzu @ SalmanPK jest to płytki klon. więc będzie działać na przykładzie slifty, ale jeśli istnieją zagnieżdżone tablice lub obiekty, będą to odniesienia. : /
Jesse
1
Uważam, że ten artykuł jest bardzo pomocny: heyjavascript.com/4-creative-ways-to-clone-objects
Jordan Hudson
3
@Jordan Hudson - Bardzo miłe użycie JSON w drugim przykładzie. var newObj = JSON.parse (JSON.stringify (oldObj)); // Teraz newObj jest klonem. Jedynym problemem jest to, że stringify nie będzie działać na odwołaniu rekurencyjnym, więc należy zachować ostrożność.
Kfir Erez

Odpowiedzi:

298

Możliwość 1

Głęboka kopia o niskiej falbance:

var obj2 = JSON.parse(JSON.stringify(obj1));

Możliwość 2 (przestarzałe)

Uwaga: To rozwiązanie jest teraz oznaczone jako przestarzałe w dokumentacji Node.js :

Metoda util._extend () nigdy nie była przeznaczona do użycia poza wewnętrznymi modułami Node.js. Społeczność i tak go znalazła i wykorzystała.

Jest przestarzały i nie powinien być używany w nowym kodzie. JavaScript ma bardzo podobne wbudowane funkcje dzięki Object.assign ().

Oryginalna odpowiedź: :

Aby uzyskać płytką kopię, użyj wbudowanej util._extend()funkcji Node .

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

var obj1 = {x: 5, y:5};
var obj2 = extend({}, obj1);
obj2.x = 6;
console.log(obj1.x); // still logs 5

Kod źródłowy funkcji Node znajduje _extendsię tutaj: https://github.com/joyent/node/blob/master/lib/util.js

exports._extend = function(origin, add) {
  // Don't do anything if add isn't an object
  if (!add || typeof add !== 'object') return origin;

  var keys = Object.keys(add);
  var i = keys.length;
  while (i--) {
    origin[keys[i]] = add[keys[i]];
  }
  return origin;
};
jimbo
źródło
5
Pytanie konkretnie wymagało klonowania rekurencyjnego. To jest płytki klon.
Benjamin Atkin
28
Czy nazwa nie _*powinna oznaczać, że jest to metoda prywatna i na której nie można polegać?
Puszysty
7
Każdy projekt JavaScript dowolnej wielkości ma jedną lub więcej implementacji ext (), a Węzeł nie jest wyjątkiem. Rdzeń Node.js szeroko wykorzystuje tę funkcję. Cytując Izaaka: „Niedługo nigdzie się to nie zmierza”.
jimbo
2
działało dla mnie idealnie. znacznie lepiej niż bawić się prototypem Object imo
Michael Dausmann
12
To jest ZŁA odpowiedź. Zgodnie z dokumentacją węzła: nodejs.org/api/util.html#util_util_extend_obj metoda nigdy nie był przeznaczony do stosowania na zewnątrz wewnętrznych modułów node.js. Społeczność i tak go znalazła i wykorzystała. Jest przestarzały i nie powinien być używany w nowym kodzie. JavaScript ma bardzo podobną wbudowaną funkcjonalność poprzezutil._extend() Object.assign().
Jordie
265

Dziwię się, Object.assignże nie wspomniano.

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

Jeśli jest dostępny (np. Babel), możesz użyć operatora rozkładania obiektów :

let cloned = { ... source };
djanowski
źródło
1
uratowałeś mi dzień! Dzięki
wzr1337,
2
jest to znacznie lepsze rozwiązanie niż zaimportowanie biblioteki innej firmy lub użycie niedoskonałego obejścia JSON. Dzięki!
Neil S
75
to jest płytka kopia
Jordan Davidson
14
Ostrzeżenie dla głębokiego klonowania, w przypadku głębokiego klonowania nadal należy używać innych alternatyw. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
gsalgadotoledo
1
Z tego, co mogę powiedzieć, operator rozkładania obiektów nie jest rzeczą ES6, ale propozycją etapu 3. co oznacza, że ​​możesz go używać z babelem, ale nie bez tego, co rozumiem. github.com/tc39/…
macdja38
24
Object.defineProperty(Object.prototype, "extend", {
    enumerable: false,
    value: function(from) {
        var props = Object.getOwnPropertyNames(from);
        var dest = this;
        props.forEach(function(name) {
            if (name in dest) {
                var destination = Object.getOwnPropertyDescriptor(from, name);
                Object.defineProperty(dest, name, destination);
            }
        });
        return this;
    }
});

Spowoduje to zdefiniowanie metody rozszerzania, której można użyć. Kod pochodzi z tego artykułu.

Michael Dillon
źródło
Nie rozumiem, jak to ma działać. Zmienia oryginalny obiekt! Jak mam użyć tej funkcji, aby uzyskać klon obiektu? Czy możesz tutaj dodać kod użytkowania? Po przeczytaniu Twojego postu i postu na blogu nadal nie mogę zrozumieć, w jaki sposób ma on służyć do klonowania obiektu.
Brad
3
czy to naprawdę działa? „if (name w dest)” - zmieni właściwość tylko, jeśli już istnieje w dest. należy to zanegować.
memical
8
Czy modyfikowanie Object.prototype nie powinno być słowne? Również ten link do artykułu jest zepsuty.
Daniel Schaffer,
Właśnie wypróbowałem link do artykułu i działa dla mnie. Być może był to blip sieciowy, gdy próbowałeś.
Michael Dillon,
W oparciu o szereg komentarzy zaktualizowałem odpowiedź, aby uwzględnić wariant, który nie dodaje się do prototypu obiektu.
Shamasis Bhattacharya
20
var obj2 = JSON.parse(JSON.stringify(obj1));
użytkownik2516109
źródło
2
Zostało to już zasugerowane w tej istniejącej odpowiedzi, nie ma sensu go powtarzać.
Shadow Wizard is Ear For You
@ShadowWizard są to różne metody. Ten po prostu konwertuje na json i wraca do obiektu, podczas gdy połączona odpowiedź używa Object.keys()do iteracji po obiekcie
mente
Ta odpowiedź jest zła. JSON.stringify pobiera obiekt daty i redukuje go do ciągu, a następnie po analizie pozostawia go jako ciąg, aby mutować stan obiektu, pozostawiając ci inny obiekt niż początkowo w przypadku dat.
twboc
8

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 wszystkie rodzaje rzeczy, w tym odwołania cykliczne. Ze strony github :

klonuj wzorce klonując obiekty, tablice, obiekty Data i obiekty RegEx. Wszystko jest klonowane rekurencyjnie, dzięki czemu można na przykład klonować daty w tablicach w obiektach. [...] Referencje okólne? Tak!

Clint Harris
źródło
7

Ten kod działa również, ponieważ metoda Object.create () tworzy nowy obiekt z określonym obiektem prototypowym i właściwościami.

var obj1 = {x:5, y:5};

var obj2 = Object.create(obj1);

obj2.x; //5
obj2.x = 6;
obj2.x; //6

obj1.x; //5
Hiron
źródło
4
jest to płytka kopia
Radagast the Brown
6

Najprostszym i najszybszym sposobem klonowania obiektu w NodeJS jest użycie metody Object.keys (obj)

var a = {"a": "a11", "b": "avc"};
var b;

for(var keys = Object.keys(a), l = keys.length; l; --l)
{
   b[ keys[l-1] ] = a[ keys[l-1] ];
}
b.a = 0;

console.log("a: " + JSON.stringify(a)); // LOG: a: {"a":"a11","b":"avc"} 
console.log("b: " + JSON.stringify(b)); // LOG: b: {"a":0,"b":"avc"}

Metoda Object.keys wymaga JavaScript 1.8.5; nodeJS v0.4.11 obsługuje tę metodę

ale oczywiście w przypadku obiektów zagnieżdżonych należy zaimplementować funkcję rekurencyjną


Innym rozwiązaniem jest użycie natywnego JSON (zaimplementowanego w JavaScript 1.7), ale jest on znacznie wolniejszy (~ 10 razy wolniejszy) niż poprzedni

var a = {"a": i, "b": i*i};
var b = JSON.parse(JSON.stringify(a));
b.a = 0;
nihil
źródło
5

Istnieje również projekt na Github, który ma być bardziej bezpośrednim portem jQuery.extend():

https://github.com/dreamerslab/node.extend

Przykład zmodyfikowany z dokumentów jQuery :

var extend = require('node.extend');

var object1 = {
    apple: 0,
    banana: {
        weight: 52,
        price: 100
    },
    cherry: 97
};

var object2 = {
    banana: {
        price: 200
    },
    durian: 100
};

var merged = extend(object1, object2);
Niespokojny
źródło
4

Wszyscy cierpicie, ale rozwiązanie jest proste.

var obj1 = {x: 5, y:5};

var obj2 = {...obj1}; // Bum

Ngend Lio
źródło
3

Szukając prawdziwej opcji klonowania, natknąłem się na link Ridcully do tutaj:

http://my.opera.com/GreyWyvern/blog/show.dml/1725165

Zmodyfikowałem rozwiązanie na tej stronie, aby funkcja dołączona do Objectprototypu nie była wymienna. Oto mój wynik:

Object.defineProperty(Object.prototype, 'clone', {
    enumerable: false,
    value: function() {
        var newObj = (this instanceof Array) ? [] : {};
        for (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;
    }
});

Mam nadzieję, że pomoże to również komuś innemu. Należy pamiętać, że istnieją pewne zastrzeżenia ... szczególnie w przypadku właściwości o nazwie „klon”. To działa dobrze dla mnie. Nie biorę żadnego uznania za napisanie tego. Ponownie zmieniłem tylko sposób, w jaki był definiowany.

Ćwiek
źródło
To jest źle. Typ daty to obiekt, więc ten kod zastąpiłby daty pustymi obiektami ... Nie używaj tego.
jtblin,
0

Jeśli używasz skryptu do kawy, jest to tak proste, jak:

newObject = {}
newObject[key] = value  for own key,value of oldObject

Chociaż to nie jest głęboki klon.

balupton
źródło
0

Żadna z odpowiedzi mnie nie zadowoliła, kilka nie działa lub są tylko płytkimi klonami, odpowiedzi @ clint-harris i użycie JSON.parse / stringify są dobre, ale dość powolne. Znalazłem moduł, który szybko dokonuje głębokiego klonowania: https://github.com/AlexeyKupershtokh/node-v8-clone

jtblin
źródło
0

Nie ma wbudowanego sposobu wykonania prawdziwego klonowania (głębokiej kopii) obiektu w node.js. Istnieje kilka trudnych przypadków, więc zdecydowanie powinieneś użyć do tego biblioteki. Taką funkcję napisałem dla mojej biblioteki simpleoo . Możesz użyć tej deepCopyfunkcji bez użycia czegokolwiek z biblioteki (która jest dość mała), jeśli jej nie potrzebujesz. Ta funkcja obsługuje klonowanie wielu typów danych, w tym tablic, dat i wyrażeń regularnych, obsługuje odwołania rekurencyjne, a także działa z obiektami, których funkcje konstruktora mają wymagane parametry.

Oto kod:

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

/**
 * 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 = function deepCopy(src, /* INTERNAL */ _visited) {
    if(src == null || typeof(src) !== 'object'){
        return src;
    }

    // Initialize the visited objects array if needed
    // This is used to detect cyclic references
    if (_visited == undefined){
        _visited = [];
    }
    // Ensure src has not already been visited
    else {
        var i, len = _visited.length;
        for (i = 0; i < len; i++) {
            // If src was already visited, don't try to copy it, just return the reference
            if (src === _visited[i]) {
                return src;
            }
        }
    }

    // Add this object to the visited array
    _visited.push(src);

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

    //Special cases:
    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice(0) would soft clone
        ret = src.slice();
        var i = ret.length;
        while (i--){
            ret[i] = deepCopy(ret[i], _visited);
        }
        return ret;
    }
    //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);
    }

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

    //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 ret = object_create(proto);

    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
        ret[key] = deepCopy(src[key], _visited);
    }
    return ret;
};
Matt Browne
źródło
0
npm install node-v8-clone

Najszybszy kloner, otwiera natywną metodę klonowania z node.js

var clone = require('node-v8-clone').clone;
var newObj = clone(obj, true); //true - deep recursive clone
Oleksiy Chechel
źródło
0

Innym rozwiązaniem jest enkapsulacja bezpośrednio w nowej zmiennej przy użyciu: obj1= {...obj2}

etykieta
źródło
To płytka kopia
Rémi Doolaeghe,
0

Możesz także użyć tej biblioteki klonowania do głębokiego klonowania obiektów.

 npm install --save clone
const clone = require('clone');

const clonedObject = clone(sourceObject);
Lanil Marasinghe
źródło
-2

Możesz prototypować obiekt, a następnie wywoływać instancję obiektu za każdym razem, gdy chcesz użyć i zmienić obiekt:

function object () {
  this.x = 5;
  this.y = 5;
}
var obj1 = new object();
var obj2 = new object();
obj2.x = 6;
console.log(obj1.x); //logs 5

Możesz także przekazać argumenty do konstruktora obiektów

function object (x, y) {
   this.x = x;
   this.y = y;
}
var obj1 = new object(5, 5);
var obj2 = new object(6, 6);
console.log(obj1.x); //logs 5
console.log(obj2.x); //logs 6

Mam nadzieję, że to jest pomocne.

użytkownik3459287
źródło