Nodejs: come clonare un oggetto


90

Se clono un array, utilizzo cloneArr = arr.slice()

Voglio sapere come clonare un oggetto in nodejs.

Risposte:


175

Per le utilità e le classi in cui non è necessario spremere ogni goccia di prestazioni, spesso baro e uso solo JSON per eseguire una copia completa:

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

Questa non è l'unica risposta o la risposta più elegante; tutte le altre risposte dovrebbero essere prese in considerazione per i colli di bottiglia della produzione. Tuttavia, questa è una soluzione rapida e sporca, abbastanza efficace e utile nella maggior parte delle situazioni in cui clonerei un semplice hash di proprietà.


2
@djechlin Certo che lo fa. Provalo: jsfiddle.net/katowulf/E5jC3 (testato con il nodo 0.10.11) Non sarà in grado di ricostituire funzioni o dati prototipici, ma ottiene i valori più o meno bene.
Kato

11
Questo convertirà le date in stringhe
backus

4
@Backus Oltre a oggetti e funzioni.
Kato

2
@ Kato, perché i riferimenti circolari implicano che non è necessario fare copie profonde? Sono due cose non correlate. È certamente possibile scrivere un buon metodo di clonazione per NodeJS che supporti i riferimenti circolari e non ricada sull'uso di JSON per clonare. github.com/pvorb/node-clone
Samuel Neff,

2
Già notato nei commenti e nella descrizione. Non puoi clonare una funzione, un prototipo di oggetto o altre cose che non dovresti provare a fare con un metodo di clonazione generico. Avrai bisogno di più di una riga di codice per questo, e probabilmente non dovresti clonare comunque quella spazzatura - metti un metodo clone nella tua classe che sappia come gestire gli interni e fainewObj = obj.clone(args...);
Kato

34

Object.assign non è stato menzionato in nessuna delle risposte precedenti.

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

Se sei su ES6 puoi utilizzare l'operatore spread:

let cloned = { ... source };

Riferimento: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


28
Nota che entrambi sono cloni superficiali e l'operatore di diffusione sugli oggetti richiede node.js 8+.
jlh

1
Come menzionato da @jlh, questo non funzionerà per gli oggetti nidificati. Puoi leggere di più sui problemi di clonazione profonda di questo metodo qui: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Shnd

Come ha detto Shnd, questa risposta non è corretta. Per la clonazione profonda, dobbiamo usare altre alternative perché Object.assign () copia i valori delle proprietà. Se il valore di origine è un riferimento a un oggetto, copia solo quel valore di riferimento.
troy

1
Questo non clona, ​​fa solo riferimento all'oggetto originale, il che significa che se cambi qualcosa andrà al master
Aidan Welch

@AidanWelch Non penso che sia corretto. let a = {x:1}; let b = Object.assign({}, a); a.x = 2; a.y = 1; console.log(a, b);
Miguel Pynto

20

Ci sono alcuni moduli Node là fuori se non vuoi "lanciarne uno tuo". Questo sembra buono: https://www.npmjs.com/package/clone

Sembra che gestisca tutti i tipi di cose, inclusi i riferimenti circolari. Dalla pagina GitHub :

master clone clonando oggetti, array, oggetti Date e oggetti RegEx. Tutto viene clonato in modo ricorsivo, in modo che tu possa clonare le date negli array negli oggetti, per esempio. [...] Riferimenti circolari? Sì!


10

È difficile eseguire un'operazione di clonazione generica ma utile perché ciò che dovrebbe essere clonato in modo ricorsivo e ciò che dovrebbe essere semplicemente copiato dipende da come dovrebbe funzionare l'oggetto specifico.

Qualcosa che può essere utile è

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

In questo codice la logica è

  • in caso di nullo undefinedsemplicemente restituire lo stesso (il caso speciale è necessario perché è un errore provare a vedere se un clonemetodo è presente)
  • l'oggetto ha un clonemetodo? allora usa quello
  • l'oggetto è un array? quindi eseguire un'operazione di clonazione ricorsiva
  • altrimenti restituisci lo stesso valore

Questa funzione di clonazione dovrebbe consentire l'implementazione di metodi di clonazione personalizzati facilmente ... per esempio

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

Quando nell'oggetto sai che un'operazione di clonazione corretta per un array specifico è solo una copia superficiale, puoi chiamare values.slice()invece di clone(values).

Ad esempio, nel codice precedente, richiedo esplicitamente che la clonazione di un oggetto poligono clonerà i punti, ma condividerà lo stesso oggetto di stile. Se invece voglio clonare anche l'oggetto di stile, posso semplicemente passare clone(this.style).


1
+1 per menzionare che gli oggetti devono implementare il .clonemetodo da soli. Questo è il modo migliore per gestire la clonazione di oggetti.
Raynos

3
if (x.clone)dovrebbe essereif (typeof x.clone === 'function')
Yanick Rochon

@YanickRochon: Grazie, risolto. Scusa, in qualche modo non avevo notato questo commento prima ...
6502

9

Non esiste un metodo nativo per la clonazione di oggetti. Sottolineare gli attrezzi _.cloneche sono un clone superficiale.

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

Lo affetta o lo estende.

Ecco _.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;
};

Extend itera semplicemente attraverso tutti gli elementi e crea un nuovo oggetto con gli elementi al suo interno.

Puoi implementare la tua implementazione ingenua se lo desideri

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

Esistono buone ragioni per evitare la clonazione profonda perché le chiusure non possono essere clonate.

Ho fatto personalmente una domanda deep cloning objects beforee la conclusione a cui sono arrivato è che non lo fai.

La mia raccomandazione è utilizzare underscoreed è un _.clonemetodo per cloni superficiali


9

Per una copia superficiale, mi piace usare il modello di riduzione (di solito in un modulo o simile), in questo modo:

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

Ecco un jsperf per un paio di opzioni: http://jsperf.com/shallow-copying


semplice, elegante, brillante. Attendo con impazienza altri vostri contributi SO ^ _ ^
Grazie

7

Vecchia domanda, ma c'è una risposta più elegante di quanto suggerito finora; usa il built-in utils._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 } }

Notare l'uso del primo parametro con un oggetto vuoto {} - questo dice a Extended che gli oggetti copiati devono essere copiati in un nuovo oggetto. Se utilizzi un oggetto esistente come primo parametro, i secondi (e tutti i successivi) parametri verranno copiati in profondità sulla prima variabile del parametro.

Utilizzando le variabili di esempio sopra, puoi anche fare questo:

var anotherMergeVar = { foo: "bar" };

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

console.log(copiedObject);

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

Utilità molto utile, soprattutto dove sono abituato a estendere in AngularJS e jQuery.

Spero che questo aiuti qualcun altro; le sovrascritture dei riferimenti agli oggetti sono un problema e questo lo risolve ogni volta!


1
L'utilizzo di _extend è ora ammortizzato. Usa Object.assign () invece: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
MacroMan

7

Puoi usare anche lodash . Ha metodi clone e 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

A seconda di cosa vuoi fare con il tuo oggetto clonato, puoi utilizzare il meccanismo di ereditarietà prototipale di javascript e ottenere un oggetto in qualche modo clonato attraverso:

var clonedObject = Object.create(originalObject);

Ricorda solo che questo non è un clone completo, nel bene e nel male.

Un aspetto positivo è che in realtà non hai duplicato l'oggetto, quindi l'impronta di memoria sarà bassa.

Alcune cose difficili da ricordare riguardo a questo metodo sono che l'iterazione delle proprietà definite nella catena del prototipo a volte funziona in modo leggermente diverso e il fatto che qualsiasi modifica all'oggetto originale influenzerà anche l'oggetto clonato a meno che quella proprietà non sia stata impostata anche su se stessa .


3
Questo è così ridicolo pericoloso e completamente diverso da un clone. var a = {foo: "bar"}, b = Object.create(a); a.foo = "broken"; console.log(b.foo); // "broken";
Grazie

@naomik: b.foo = "working"; console.log(a.foo); // still "broken";Ovviamente bisogna essere consapevoli che i cambiamenti nell'oggetto originale si rifletteranno nel "clone" e che i cambiamenti nel "clone" non si rifletteranno nell'oggetto originale - ma non lo definirei pericoloso - dipende tutto da cosa vuoi fare con il tuo clone
VoxPelli

@naomik: sono abbastanza nuovo su JS, quindi vorrei alcune precisazioni. Il clone superficiale proposto da olli-k e che ti è piaciuto sembra (IMHO, ma potrei sbagliarmi) avere le stesse limitazioni rispetto alla soluzione proposta da voxpelli. Anche l'originale e la chiusura superficiale condivideranno gli stessi "oggetti", l'aggiornamento di un elemento nell'originale avrà anche un impatto sul clone superficiale. O?
bmorin

@bmornin, se usi la tecnica di Olli K, la modifica dell'oggetto originale non provocherà un cambiamento nel nuovo oggetto.
Grazie

1
@VoxPelli L'intero scopo di un "clone" è isolare le modifiche dall'oggetto originale, quindi questo metodo è più pericoloso.
Doug

1

Ho implementato una copia completa completa. Credo che sia la scelta migliore per un metodo di clonazione generico, ma non gestisce i riferimenti ciclici.

Esempio di utilizzo:

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

Il codice stesso:

Questo codice copia gli oggetti con i loro prototipi, copia anche le funzioni (potrebbe essere utile per qualcuno).

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

Con questa copia non riesco a trovare alcuna differenza tra l'originale e quello copiato tranne se l'originale utilizzava chiusure sulla sua costruzione, quindi penso che sia una buona implementazione.

spero possa essere d'aiuto


1

per array, si può usare

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

Gli oggetti e gli array in JavaScript utilizzano la chiamata per riferimento, se aggiorni il valore copiato potrebbe riflettere sull'oggetto originale. Per evitare ciò è possibile clonare in profondità l'oggetto, per impedire il passaggio del riferimento, utilizzando la libreria lodash cloneDeep metodo run command

npm installa lodash

const ld = require('lodash')
const objectToCopy = {name: "john", age: 24}
const clonedObject = ld.cloneDeep(objectToCopy)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.