Nodejs: cómo clonar un objeto

90

Si clono una matriz, uso cloneArr = arr.slice()

Quiero saber cómo clonar un objeto en nodejs.

guilin 桂林
fuente

Respuestas:

175

Para las utilidades y clases donde no hay necesidad de exprimir cada gota de rendimiento, a menudo hago trampa y solo uso JSON para realizar una copia profunda:

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

Esta no es la única respuesta ni la más elegante; todas las demás respuestas deben considerarse para los cuellos de botella de producción. Sin embargo, esta es una solución rápida y sucia, bastante efectiva y útil en la mayoría de situaciones en las que clonaría un simple hash de propiedades.

Kato
fuente
2
@djechlin Seguro que sí. Pruébelo: jsfiddle.net/katowulf/E5jC3 (probado con el nodo 0.10.11) No podrá reconstituir funciones o datos prototípicos, pero obtiene los valores bien.
Kato
11
Esto convertirá las fechas en cadenas
backus
4
@Backus Además de objetos y funciones.
Kato
2
@Kato, ¿Por qué las referencias circulares implican que no hay necesidad de hacer copias profundas? Son dos cosas no relacionadas. Ciertamente, es posible escribir un buen método de clonación para NodeJS que admita referencias circulares y no recurra al uso de JSON para clonar. github.com/pvorb/node-clone
Samuel Neff
2
Ya se señaló en los comentarios y en la descripción. No puede clonar una función, un prototipo de objeto u otras cosas que no debería intentar hacer con un método de clonación genérico. Necesitará más de una línea de código para eso, y probablemente no debería clonar esa basura de todos modos: coloque un método de clonación en su clase que sepa cómo manejar las partes internas y lo haganewObj = obj.clone(args...);
Kato
34

Object.assign no se ha mencionado en ninguna de las respuestas anteriores.

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

Si está en ES6, puede usar el operador de propagación:

let cloned = { ... source };

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

Sam G
fuente
28
Tenga en cuenta que ambos son clones superficiales y el operador de propagación en objetos requiere node.js 8+.
jlh
1
Como mencionó @jlh, esto no funcionará para objetos anidados. Puede leer más sobre problemas de clonación profunda de este método aquí: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Shnd
Como dijo Shnd, esta respuesta es incorrecta. Para la clonación profunda, necesitamos usar otras alternativas porque Object.assign () copia los valores de propiedad. Si el valor de origen es una referencia a un objeto, solo copia ese valor de referencia.
troy
1
Esto no se clona, ​​solo hace referencia al objeto original, lo que significa que si cambia algo, irá al maestro
Aidan Welch
@AidanWelch No creo que eso sea correcto. let a = {x:1}; let b = Object.assign({}, a); a.x = 2; a.y = 1; console.log(a, b);
Miguel Pynto
20

Hay algunos módulos de Node por ahí si no quiere "lanzar los suyos". Este se ve bien: https://www.npmjs.com/package/clone

Parece que maneja todo tipo de cosas, incluidas las referencias circulares. Desde la página de github :

clon maestros clonando objetos, matrices, objetos Date y objetos RegEx. Todo se clona de forma recursiva, por lo que puede clonar fechas en matrices en objetos, por ejemplo. [...] ¿Referencias circulares? ¡Sí!

Clint Harris
fuente
10

Es difícil hacer una operación de clonación genérica pero útil porque lo que se debe clonar de forma recursiva y lo que se debe copiar depende de cómo se supone que funcione el objeto específico.

Algo que puede resultar útil es

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

En este código la lógica es

  • en caso de nullo undefinedsimplemente devolver el mismo (el caso especial es necesario porque es un error intentar ver si un clonemétodo está presente)
  • ¿el objeto tiene un clonemétodo? entonces usa eso
  • ¿Es el objeto una matriz? luego haz una operación de clonación recursiva
  • de lo contrario, devuelve el mismo valor

Esta función de clonación debería permitir implementar métodos de clonación personalizados fácilmente ... por ejemplo

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

Cuando en el objeto sepa que una operación de clonación correcta para una matriz específica es solo una copia superficial, puede llamar en values.slice()lugar de clone(values).

Por ejemplo, en el código anterior, exijo explícitamente que una clonación de un objeto poligonal clone los puntos, pero compartirá el mismo objeto de estilo. Si también quiero clonar el objeto de estilo, simplemente puedo pasar clone(this.style).

6502
fuente
1
+1 por mencionar que los objetos deben implementar el .clonemétodo por sí mismos. Esta es la mejor forma de lidiar con la clonación de objetos.
Raynos
3
if (x.clone)debería serif (typeof x.clone === 'function')
Yanick Rochon
@YanickRochon: Gracias, arreglado. Lo siento, de alguna manera no me di cuenta de este comentario antes ...
6502
9

No existe un método nativo para clonar objetos. Subrayar los implementos _.cloneque es un clon superficial.

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

O lo corta en rodajas o lo extiende.

Aquí está _.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 simplemente recorre todos los elementos y crea un nuevo objeto con los elementos que contiene.

Puede implementar su propia implementación ingenua si lo desea

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

Hay buenas razones para evitar la clonación profunda porque los cierres no se pueden clonar.

Personalmente hice una pregunta deep cloning objects beforey la conclusión a la que llegué es que simplemente no lo haces.

Mi recomendación es el uso underscorey su _.clonemétodo para clones poco profundos.

Raynos
fuente
9

Para una copia superficial, me gusta usar el patrón de reducción (generalmente en un módulo o similar), así:

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

Aquí hay un jsperf para un par de opciones: http://jsperf.com/shallow-copying

Olli K
fuente
simple, elegante, brillante. Espero sus otras contribuciones SO ^ _ ^
Gracias
7

Antigua pregunta, pero hay una respuesta más elegante que la sugerida hasta ahora; use el utils._extend incorporado:

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

Tenga en cuenta el uso del primer parámetro con un objeto vacío {} - esto le dice a extender que los objetos copiados deben copiarse en un nuevo objeto. Si utiliza un objeto existente como primer parámetro, el segundo (y todos los siguientes) parámetros se copiarán en profundidad sobre la primera variable de parámetro.

Usando las variables de ejemplo anteriores, también puede hacer esto:

var anotherMergeVar = { foo: "bar" };

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

console.log(copiedObject);

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

Utilidad muy útil, especialmente donde estoy acostumbrado a extenderme en AngularJS y jQuery.

Espero que esto ayude a alguien más; las sobreescrituras de referencias de objetos son una miseria, ¡y esto lo resuelve siempre!

Scott Byers
fuente
1
El uso de _extend ahora está depreciado. Use Object.assign () en su lugar: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
MacroMan
7

También puedes usar lodash . Tiene métodos clone y 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]);
Michal
fuente
1

Dependiendo de lo que quiera hacer con su objeto clonado, puede utilizar el mecanismo de herencia prototípico de javascript y lograr un objeto algo clonado a través de:

var clonedObject = Object.create(originalObject);

Solo recuerda que esto no es un clon completo, para bien o para mal.

Lo bueno de eso es que en realidad no ha duplicado el objeto, por lo que la huella de memoria será baja.

Sin embargo, algunas cosas difíciles de recordar sobre este método es que la iteración de las propiedades definidas en la cadena del prototipo a veces funciona de manera un poco diferente y el hecho de que cualquier cambio en el objeto original afectará al objeto clonado también a menos que esa propiedad también se haya establecido en sí mismo. .

VoxPelli
fuente
3
Esto es ridículamente peligroso y no se parece en nada a un clon. var a = {foo: "bar"}, b = Object.create(a); a.foo = "broken"; console.log(b.foo); // "broken";
Gracias
@naomik: b.foo = "working"; console.log(a.foo); // still "broken";uno, por supuesto, debe tener en cuenta que los cambios en el objeto original se reflejarán en el "clon" y que los cambios en el "clon" no se reflejarán en el objeto original, pero yo no lo llamaría peligroso - todo depende de lo que quieras hacer con tu clon
VoxPelli
@naomik: Soy bastante nuevo en JS, así que me gustaría algunas precisiones. El clon superficial propuesto por olli-k y que te gustó parece (en mi humilde opinión, pero podría estar equivocado) tener las mismas limitaciones que la solución propuesta por voxpelli. El cierre original y superficial también compartirán los mismos "elementos", la actualización de un elemento en el original también afectará al clon superficial. ¿O?
bmorin
@bmornin, si usa la técnica de Olli K, modificar el objeto original no provocará un cambio en el nuevo objeto.
Gracias
1
@VoxPelli El propósito de un "clon" es aislar los cambios del objeto original, por lo que este método es más peligroso.
Doug
1

Implementé una copia profunda completa. Creo que es la mejor opción para un método de clonación genérico, pero no maneja referencias cíclicas.

Ejemplo de uso:

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

El código en sí:

Este código copia objetos con sus prototipos, también copia funciones (puede ser útil para alguien).

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 esta copia no puedo encontrar ninguna diferencia entre el original y el copiado, excepto si el original usó cierres en su construcción, así que creo que es una buena implementación.

Espero que ayude

VinGarcia
fuente
1

para matriz, se puede usar

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

print ( arr_2 ); 

arr = arr.slice (0);

print ( arr ); 

arr[1]=9999; 

print ( arr_2 ); 
tlqtangok
fuente
1

Los objetos y las matrices en JavaScript usan llamadas por referencia, si actualiza el valor copiado, podría reflejarse en el objeto original. Para evitar esto, puede clonar en profundidad el objeto, para evitar que se pase la referencia, utilizando el comando de ejecución del método cloneDeep de la biblioteca lodash

npm instalar lodash

const ld = require('lodash')
const objectToCopy = {name: "john", age: 24}
const clonedObject = ld.cloneDeep(objectToCopy)
vinay dagar
fuente