C贸mo copiar objetos en JavaScript

    Introducci贸n

    Una tarea muy com煤n en la programaci贸n, independientemente del lenguaje, es copiar (o clonar) un objeto por valor, en lugar de copiar por referencia. La diferencia es que al copiar por valor, tiene dos objetos no relacionados con el mismo valor o datos. Copiar por referencia significa que tiene dos objetos que apuntan a los mismos datos en la memoria. Esto significa que si manipula el objeto A, por ejemplo, tambi茅n manipular谩 el objeto B ya que ambos hacen referencia a los mismos datos subyacentes.

    En este art铆culo, repasar茅 algunas de las formas en que puede copiar objetos por valor en JavaScript. Le mostrar茅 c贸mo puede hacer esto usando bibliotecas de terceros y escribiendo su propia funci贸n de copia.

    Nota: Dado que Node.js es solo un tiempo de ejecuci贸n creado en el motor JavaScript V8, todos los m茅todos de clonaci贸n que muestro en este art铆culo tambi茅n funcionar谩n para Node.

    Bibliotecas de terceros

    Hay una serie de bibliotecas de terceros populares que tienen esta funcionalidad incorporada, que veremos en las siguientes secciones. En mi opini贸n, estas son la mejor soluci贸n para la mayor铆a de los casos de uso simples, ya que se han probado exhaustivamente y se actualizan continuamente. Escribir este tipo de c贸digo no es f谩cil, por lo que es muy 煤til poder usar un c贸digo que tenga muchos ojos en 茅l.

    Lodash

    los Lodash Library proporciona algunos m茅todos diferentes para copiar o clonar objetos, seg煤n su caso de uso.

    El m茅todo m谩s gen茅rico es el clone() m茅todo, que proporciona copias superficiales de objetos. Funciona simplemente pasando el objeto como primer argumento y se devolver谩 la copia:

    const _ = require('lodash');
    
    let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
    let copy = _.clone(arrays);
    console.log(copy);
    
    { first: [ 1, 2, 3 ], second: [ 4, 5, 6 ] }
    

    Esto significa que el objeto de “nivel superior” (o matriz, b煤fer, mapa, etc.) se clona, 鈥嬧媝ero los objetos m谩s profundos se copiar谩n por referencia. El siguiente c贸digo demuestra que el first matriz en el original arrays objeto es el mismo objeto que el first matriz en el copy objeto:

    const _ = require('lodash');
    
    let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
    let copy = _.clone(arrays);
    console.log(copy.first === arrays.first);
    
    true
    

    Si prefiere que todos los objetos, tanto superficiales como profundos, se copien, entonces querr谩 usar el cloneDeep() m茅todo en su lugar:

    const _ = require('lodash');
    
    let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
    let copy = _.cloneDeep(arrays);
    console.log(copy);
    
    { first: [ 1, 2, 3 ], second: [ 4, 5, 6 ] }
    

    Este m茅todo funciona mediante la clonaci贸n recursiva de todos los valores en cualquier nivel de profundidad.

    Ejecutando la misma verificaci贸n de igualdad de arriba, podemos ver que las matrices original y copiada ya no son iguales ya que son copias 煤nicas:

    const _ = require('lodash');
    
    let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
    let copy = _.cloneDeep(arrays);
    console.log(copy.first === arrays.first);
    
    false
    

    Lodash ofrece algunos m茅todos de clonaci贸n m谩s, que incluyen cloneWith() y cloneDeepWith(). Ambos m茅todos aceptan otro par谩metro llamado customizer, que es una funci贸n que se utiliza para ayudar a producir el valor copiado.

    Entonces, si desea usar alguna l贸gica de copia personalizada, puede pasar una funci贸n para manejarla dentro del m茅todo de Lodash. Por ejemplo, supongamos que tiene un objeto que contiene algunos Date objetos, pero desea que se conviertan en marcas de tiempo al ser copiados, puede hacerlo as铆:

    const _ = require('lodash');
    
    let tweet = {
        username: '@ScottWRobinson',
        text: 'I didn't actually tweet this',
        created_at: new Date('December 21, 2018'),
        updated_at: new Date('January 01, 2019'),
        deleted_at: new Date('February 28, 2019'),
    };
    let tweetCopy = l.cloneDeepWith(tweet, (val) => {
        if (l.isDate(val)) {
            return val.getTime();
        }
    });
    console.log(tweetCopy);
    
    { username: '@ScottWRobinson',
      text: 'I didn't actually tweet this',
      created_at: 1545372000000,
      updated_at: 1546322400000,
      deleted_at: 1551333600000 }
    

    Como puede ver, los 煤nicos datos que fueron alterados por nuestro m茅todo fueron los Date objetos, que ahora se han convertido en marcas de tiempo de Unix.

    Guion bajo

    los Guion bajo clone() El m茅todo funciona de la misma manera que el de Lodash. clone() m茅todo. Solo proporciona una copia superficial del objeto dado, y los objetos anidados se copian por referencia.

    El mismo ejemplo que antes demuestra esto:

    const _ = require('underscore');
    
    let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
    let copy = _.clone(arrays);
    console.log(copy.first === arrays.first);
    
    true
    

    Desafortunadamente, la biblioteca de Underscore no parece tener ning煤n m茅todo para manejar la copia profunda. Puede implementar esta l贸gica por su cuenta (usando parte de la l贸gica que se muestra a continuaci贸n) y a煤n usar el de Underscore clone m茅todo para la copia superficial, o puede probar una de las otras soluciones de este art铆culo.

    Soluciones personalizadas

    Como mencion茅 anteriormente, asumir este desaf铆o por s铆 mismo es dif铆cil, ya que hay muchos casos (y casos extremos complicados) de manejar al clonar un objeto en JavaScript. Sin embargo, si se hace correctamente, podr谩 agregar una buena personalizaci贸n dentro de su m茅todo que de otro modo no ser铆a posible.

    Usar m茅todos JSON

    Una soluci贸n que se cita a menudo es simplemente utilizar el JSON.stringify y JSON.parse m茅todos a su favor, como este:

    let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
    let copy = JSON.parse(JSON.stringify(arrays));
    console.log(copy);
    
    { first: [ 1, 2, 3 ], second: [ 4, 5, 6 ] }
    

    Esto te dejar谩 con un objeto copiado profundamente y funciona muy bien para objetos simples que se convierten f谩cilmente a JSON.

    Podemos verificar esto nuevamente usando la misma verificaci贸n que la anterior:

    console.log(copy.first === arrays.first);
    
    false
    

    Si sabe que su objeto se puede serializar f谩cilmente, esta podr铆a ser una buena soluci贸n para usted.

    Escribiendo el tuyo desde cero

    Si por alguna raz贸n ninguna de las otras soluciones funciona para usted, tendr谩 que escribir su propio m茅todo de clonaci贸n.

    Como no conf铆o en m铆 mismo para implementar correctamente un m茅todo de clonaci贸n completa (y me arriesgo a que los lectores copien mis errores en su c贸digo de producci贸n), he copiado la siguiente funci贸n de clonaci贸n de esta esencia, que copia objetos de forma recursiva y parece funcionar en muchos de los tipos de datos comunes con los que se ejecutar谩 en JavaScript.

    function clone(thing, opts) {
        var newObject = {};
        if (thing instanceof Array) {
            return thing.map(function (i) { return clone(i, opts); });
        } else if (thing instanceof Date) {
            return new Date(thing);
        } else if (thing instanceof RegExp) {
            return new RegExp(thing);
        } else if (thing instanceof Function) {
            return opts && opts.newFns ? new Function('return ' + thing.toString())() : thing;
        } else if (thing instanceof Object) {
            Object.keys(thing).forEach(function (key) { newObject[key] = clone(thing[key], opts); });
            return newObject;
        } else if ([ undefined, null ].indexOf(thing) > -1) {
            return thing;
        } else {
            if (thing.constructor.name === 'Symbol') {
                return Symbol(thing.toString().replace(/^Symbol(/, '').slice(0, -1));
            }
            return thing.__proto__.constructor(thing);
        }
    }
    

    Esta funci贸n funciona manejando casos espec铆ficos cuando es necesario (como matrices, expresiones regulares, funciones, etc.), y luego, para todos los dem谩s tipos de datos (como n煤meros, cadenas, booleanos, etc.) por defecto es thingpropio constructor para copiar el valor. Si el thing es un objeto en s铆 mismo, entonces simplemente se llama recursivamente a s铆 mismo en los atributos secundarios de thing.

    Consulte la esencia completa en el enlace anterior para ver todos los tipos de datos y casos extremos en los que se ha probado.

    Conclusi贸n

    Si bien es simple en teor铆a, en la pr谩ctica copiar un objeto en JavaScript es todo menos simple. Afortunadamente, existen bastantes soluciones para que las use, como cloneDeep en Lodash, o incluso el incorporado JSON m茅todos. Y si por alguna raz贸n ninguno de ellos es adecuado, entonces es posible escribir su propio m茅todo de clonaci贸n, siempre que lo pruebe a fondo.

    Buena suerte y sabremos si tiene alguna idea, idea o sugerencia en los comentarios.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *