Clonación de un objeto en Node.js

203

¿Cuál es la mejor manera de clonar un objeto en node.js?

Por ejemplo, quiero evitar la situación en la que:

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

El objeto puede contener tipos complejos como atributos, por lo que un simple para (var x en obj1) no resolvería. ¿Necesito escribir un clon recursivo yo mismo o hay algo incorporado que no estoy viendo?

astuto
fuente
23
1. npm install underscore2. var _ = require('underscore')3 _.clone(objToClone).;
Salman von Abbas
44
Tenga en cuenta que en el comentario de @ SalmanPK anterior, este es un clon superficial . funcionará para el ejemplo de slifty, pero si hay matrices u objetos anidados, serán referencias. : /
Jesse
1
Este artículo me pareció muy útil: heyjavascript.com/4-creative-ways-to-clone-objects
Jordan Hudson
3
@ Jordan Hudson - Muy buen uso de JSON en el segundo ejemplo. var newObj = JSON.parse (JSON.stringify (oldObj)); // ahora newObj es un clon. El único problema es que stringify no funcionará en referencias recursivas, por lo que debe tener cuidado.
Kfir Erez

Respuestas:

298

Posibilidad 1

Copia profunda de bajo costo:

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

Posibilidad 2 (en desuso)

Atención: esta solución ahora está marcada como obsoleta en la documentación de Node.js :

El método util._extend () nunca fue diseñado para usarse fuera de los módulos internos de Node.js. La comunidad lo encontró y lo usó de todos modos.

Está en desuso y no debe usarse en un código nuevo. JavaScript viene con una funcionalidad incorporada muy similar a través de Object.assign ().

Respuesta original :

Para una copia superficial, use la función incorporada de Node util._extend().

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

El código fuente de la _extendfunción de Node está aquí: 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
fuente
55
La pregunta requería específicamente un clon recursivo. Este es un clon superficial.
Benjamin Atkin
28
¿No se _*supone que un nombre significa que es un método privado y que no se debe confiar en él?
Fluffy
77
Cada proyecto de JavaScript de cualquier tamaño tiene una o más implementaciones de extend (), y Node no es una excepción. El núcleo Node.js hace un uso extensivo de esta función. Citando a Isaacs, "No irá a ningún lado en el corto plazo".
jimbo
2
Funcionó perfectamente para mí. mucho mejor que jugar con el prototipo de objeto imo
Michael Dausmann
12
Esta es la respuesta INCORRECTA. Según la documentación del nodo: nodejs.org/api/util.html#util_util_extend_obj El util._extend()método nunca fue diseñado para usarse fuera de los módulos internos de Node.js. La comunidad lo encontró y lo usó de todos modos. Está en desuso y no debe usarse en un código nuevo. JavaScript viene con una funcionalidad incorporada muy similar a través deObject.assign().
Jordie
265

Me sorprende que Object.assignno se haya mencionado.

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

Si está disponible (por ejemplo, Babel), puede usar el operador de propagación de objetos :

let cloned = { ... source };
djanowski
fuente
1
¡Salvaste mi día! Gracias
wzr1337
2
esta es una solución mucho mejor que tener que importar una biblioteca de terceros o usar la solución imperfecta de JSON. ¡Gracias!
Neil S
75
esta es una copia superficial
Jordan Davidson
14
Advertencia para Deep Clone, para Deep Clone todavía necesita usar otras alternativas. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
gsalgadotoledo
1
Por lo que puedo decir, el operador de propagación de objetos no es una cosa de ES6 sino una propuesta de etapa 3. lo que significa que puedes usarlo con babel pero no sin lo que entiendo. 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;
    }
});

Esto definirá un método extendido que puede usar. El código proviene de este artículo.

Michael Dillon
fuente
No veo cómo se supone que esto funcione. ¡Modifica el objeto original! ¿Cómo se supone que debo usar esta función para obtener un clon de un objeto? ¿Puedes agregar algún código de uso aquí? Después de leer su publicación y la publicación del blog, todavía no puedo entender cómo se pretende que esto se use para clonar un objeto.
Brad
3
¿Esto realmente funciona? "if (name in dest)": solo cambiará la propiedad si ya existe en dest. Debe ser negado.
memical
8
¿No se supone que modificar Object.prototype sea verboten? También ese enlace del artículo está roto.
Daniel Schaffer
Acabo de probar el enlace del artículo y funciona para mí. Tal vez fue una falla en la red cuando lo probaste.
Michael Dillon
Basado en una serie de comentarios, he actualizado la respuesta para incluir una variante que no se agrega al prototipo del objeto.
Shamasis Bhattacharya
20
var obj2 = JSON.parse(JSON.stringify(obj1));
usuario2516109
fuente
2
Esto ya se sugirió en esta respuesta existente, no tiene sentido repetirlo.
Shadow Wizard es Ear For You
@ShadowWizard estos son diferentes métodos. Este simplemente se convierte a json y de nuevo a objeto, mientras que la respuesta vinculada se usa Object.keys()para iterar a través del objeto
mente
Esta respuesta es incorrecta. JSON.stringify toma un objeto de fecha y lo reduce a una cadena, y luego, después de analizarlo, lo deja como una cadena para que mute el estado de su objeto dejándolo con un objeto diferente al inicialmente en el caso de las fechas.
twboc
8

Hay algunos módulos de Nodo por ahí si no quieres "rodar el tuyo". 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 :

Clone Master Clonación de objetos, matrices, objetos de fecha 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
7

Este código también funciona porque el método Object.create () crea un nuevo objeto con el objeto prototipo y las propiedades especificadas.

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

var obj2 = Object.create(obj1);

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

obj1.x; //5
Hiron
fuente
44
esta es una copia superficial
Radagast the Brown
6

La manera más simple y rápida de clonar un objeto en NodeJS es usar el método 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"}

El método Object.keys requiere JavaScript 1.8.5; nodeJS v0.4.11 admite este método

pero, por supuesto, para los objetos anidados es necesario implementar funciones recursivas


Otra solución es usar JSON nativo (implementado en JavaScript 1.7), pero es mucho más lento (~ 10 veces más lento) que el anterior

var a = {"a": i, "b": i*i};
var b = JSON.parse(JSON.stringify(a));
b.a = 0;
nihil
fuente
5

También hay un proyecto en Github que pretende ser un puerto más directo de jQuery.extend():

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

Un ejemplo, modificado de los documentos de 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);
Cachondo
fuente
4

Todos sufren pero la solución es simple.

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

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

Ngend Lio
fuente
3

Buscando una verdadera opción de clonación, me topé con el enlace de ridcully aquí:

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

Modifiqué la solución en esa página para que la función asociada al Objectprototipo no sea enumerable. Aquí está mi resultado:

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

Esperemos que esto ayude a alguien más también. Tenga en cuenta que hay algunas advertencias ... particularmente con las propiedades llamadas "clon". Esto funciona bien para mi. No tomo ningún crédito por escribirlo. Nuevamente, solo cambié la forma en que se estaba definiendo.

Puntilla
fuente
Esto está mal. El tipo de fechas es objeto, por lo que este código reemplazaría las fechas por objetos vacíos ... No use esto.
jtblin
0

Si está utilizando coffee-script, es tan fácil como:

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

Aunque este no es un clon profundo.

Balupton
fuente
0

Ninguna de las respuestas me satisfizo, varias no funcionan o son clones poco profundos, las respuestas de @ clint-harris y el uso de JSON.parse / stringify son buenas pero bastante lentas. Encontré un módulo que hace una clonación profunda rápidamente: https://github.com/AlexeyKupershtokh/node-v8-clone

jtblin
fuente
0

No hay una forma integrada de hacer un clon real (copia profunda) de un objeto en node.js. Hay algunos casos extremos complicados, por lo que definitivamente debe usar una biblioteca para esto. Escribí una función para mi biblioteca simpleoo . Puedes usar eldeepCopy función sin usar nada más de la biblioteca (que es bastante pequeña) si no la necesita. Esta función admite la clonación de múltiples tipos de datos, incluidas matrices, fechas y expresiones regulares, admite referencias recursivas y también funciona con objetos cuyas funciones de constructor tienen los parámetros necesarios.

Aquí está el código:

//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
fuente
0
npm install node-v8-clone

Clonador más rápido, abre el método de clonación nativo desde node.js

var clone = require('node-v8-clone').clone;
var newObj = clone(obj, true); //true - deep recursive clone
Oleksiy Chechel
fuente
0

Otra solución es encapsular directamente en la nueva variable usando: obj1= {...obj2}

Labelle
fuente
Esta es una copia superficial
Rémi Doolaeghe
0

También puede usar esta biblioteca de clonación para clonar objetos en profundidad.

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

const clonedObject = clone(sourceObject);
Lanil Marasinghe
fuente
-2

Puede crear un prototipo de objeto y luego llamar a la instancia de objeto cada vez que quiera usar y cambiar de objeto:

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

También puede pasar argumentos al constructor de objetos

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

Espero que esto sea útil.

usuario3459287
fuente