¿Cómo se clona una matriz de objetos con guión bajo?

82
#!/usr/bin/env node
var _ = require('underscore');
var a = [{f: 1}, {f:5}, {f:10}];
var b = _.clone(a);
b[1].f = 55;
console.log(JSON.stringify(a));

Esto resulta en:

[{"f":1},{"f":55},{"f":10}]

¡La clonación no parece funcionar! Entonces hago RTFM, y veo esto:

http://underscorejs.org/#clone

Cree un clon del objeto con copia superficial. Todos los objetos o matrices anidados se copiarán por referencia, no se duplicarán.

Entonces _.clonees bastante inútil. ¿Hay alguna forma de copiar realmente la matriz de objetos?

Cadena
fuente
4
Hubo una solicitud de extracción para copia profunda que fue denegada: github.com/jashkenas/underscore/pull/595 Lo-Dash ha cloneDeep
epascarello
5
lol me acabo de dar cuenta del juego de palabras con subrayado. Guión bajo.
Jess
1
subrayado vs.lodash: stackoverflow.com/questions/13789618/…
Jess

Respuestas:

122

Bueno, ¡hay un truco! Si clonar no "clona" objetos anidados, puede forzarlo clonando explícitamente cada objeto dentro de una llamada de mapa. Me gusta esto:

#!/usr/bin/env node
var _ = require('underscore');
var a = [{f: 1}, {f:5}, {f:10}];
var b = _.map(a, _.clone);       // <----
b[1].f = 55;
console.log(JSON.stringify(a));

Huellas dactilares:

[{"f":1},{"f":5},{"f":10}]

¡Hurra! ano ha cambiado! ¡Ahora puedo editar ba mi gusto!

Cadena
fuente
48
Pero ten cuidado. Esto, por supuesto, solo funciona a dos niveles de profundidad. no para matrices u objetos que están anidados incluso más que este ejemplo.
Simon Zyx
1
Por diseño, Underscore tampoco clonará los valores de RegExp o Date correctamente
Mark K Cowan
1
Cualquiera que busque aquí debería ver mi respuesta a continuación.
gdibble
65

Otra solución extraída del problema en Github que funciona con cualquier nivel de datos anidados y no requiere subrayado:

JSON.parse(JSON.stringify(obj))
Nacho Coloma
fuente
13
Esto funciona a menos que el objeto tenga un ciclo, en cuyo caso JSON.stringifyarroja un error. Lo cual no es el caso en el original, pero sigue siendo un estado de cosas interesante. a = {simple: 'thing'}; a.cycle = a ; JSON.stringify(a).
mcdave
10
También vale la pena señalar que esta solución solo funciona para objetos con tipos simples. Por ejemplo, si su objeto tiene Dateo Regexinstancias, se serializarán en cadenas. No es el fin del mundo, pero debe manejarlo si está usando esto y espera Dateinstancias.
cayleyh
1
Y si cree que alguien podría intentar alimentar undefinedesto, querrá, de lo JSON.parse(JSON.stringify(obj) || null)contrario, arrojará un error.
Ian Mackinnon
1
Junto con lo que @cayleyh mencionó, esto se eliminará por functioncompleto.
Marko Grešak
13

FWIW, lodash tiene una función cloneDeep :

Este método es como _.clone excepto que clona recursivamente value.

Hertzel Guinness
fuente
9

Referencia de la API de subrayado :

_.toArray(list)Crea una matriz real de la lista (cualquier cosa sobre la que se pueda iterar). Útil para transmutar el objeto de argumentos.

... o en este caso, clonar una matriz. Prueba esto:

var _ = require('underscore');
var array1 =  [{a:{b:{c:1}}},{b:{c:{a:2}}},{c:{a:{b:3}}}];
var array2 = _.toArray(array1);
console.log(array1 === array2); --> false
console.log(array1[0] === array2[0]); --> true

El siguiente es un apéndice que creé después del comentario de Steve a continuación -thx

Un Vanilla JS (o usar _.clonesi se desea) ayudante recursivo de clonación profunda :

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 _.clone(thing);  // If you must use _ ;)
        return thing.__proto__.constructor(thing);
    }
}

var a = {
    a: undefined,
    b: null,
    c: 'a',
    d: 0,
    e: Symbol('a'),
    f: {},
    g: { a:1 },
    h: [],
    i: [ { a:2 }, { a:3 } ],
    j: [ 1, 2 ],
    k: function (a) { return a; },
    l: /[a-z]/g,
    z: [ {
        a: undefined,
        b: null,
        c: 'b',
        d: 1,
        e: Symbol(1),
        f: {},
        g: { b:2 },
        h: { c:{ c:3 } },
        i: { a:Symbol('b') },
        j: { a:undefined, b:null },
        k: [],
        l: [ 1, [ 1, 2 ], [ [ 1, 2, 3 ] ] ],
        m: function (a) { return !a; },
        n: { a:function (a) { return !!a; } },
        o: /(a|b)/i
       } ]
};
var b = clone(a);
var c = clone(a, { newFns:true });


/* Results - value beneath each for reference:

a.a === b.a --> true
undefined

a.b === b.b --> true
null

a.c === b.c --> true
'a'

a.d === b.d --> true
0

a.e === b.e --> false
Symbol(a)

a.f === b.f --> false
{}

a.g === b.g --> false
{ a:1 }

a.h === b.h --> false
[]

a.i === b.i --> false
[ { a:2 }, { a:3 } ]

a.i[0] === b.i[0] --> false
{ a:2 }

a.i[0].a === b.i[0].a --> true
2

a.j === b.j --> false
[ 1, 2 ]

a.k === b.k --> true
a.k === c.k --> false
function (a) { return a; }

a.l === b.l --> false
/[a-z]/g

a.z === b.z --> false
[Object]

a.z[0].a === b.z[0].a --> true
undefined

a.z[0].b === b.z[0].b --> true
null

a.z[0].c === b.z[0].c --> true
'b'

a.z[0].d === b.z[0].d --> true
1

a.z[0].e === b.z[0].e --> 
false
Symbol(1)

a.z[0].f === b.z[0].f --> false
{}

a.z[0].g === b.z[0].g -- > false
{ b:2 }

a.z[0].g.b === b.z[0].g.b --> true
2

a.z[0].h === b.z[0].h --> false
{ c:{ c:3 } }

a.z[0].h.c === b.z[0].h.c --> false
{ c:3 }

a.z[0].h.c.c === b.z[0].h.c.c --> true
3

a.z[0].i === b.z[0].i --> false
{ a:Symbol(b) }

a.z[0].i.a === b.z[0].i.a --> false
Symbol(b)

a.z[0].j === b.z[0].j --> false
{ a:undefined, b:null }

a.z[0].j.a === b.z[0].j.a --> true
undefined

a.z[0].k === b.z[0].k --> false
[]

a.z[0].l === b.z[0].l --> false
[ 1, [ 1, 2 ], [ [ 1, 2, 3 ] ] ]

a.z[0].l[1] === b.z[0].l[1] --> false
[ 1, 2 ]

a.z[0].l[1][1] === b.z[0].l[1][1] --> true
2

a.z[0].m === b.z[0].m --> true
a.z[0].m === c.z[0].m --> false
function (a) { return !a; }

a.z[0].n === b.z[0].n --> false
{ a:function (a) { return !!a; } }

a.z[0].n.a === b.z[0].n.a --> true
a.z[0].n.a === c.z[0].n.a --> false
function (a) { return !!a; }

a.z[0].o === b.z[0].o --> false
/(a|b)/i

*/
gdibble
fuente
Esta es la mejor respuesta.
Pierre
_.toArray(list)no clona objetos en una matriz. var array1 = [{a: 1}, {a: 2}, {a: 3}]; var array2 = _.toArray(array1); array2[0].a = 999; console.log(array1[0]); --> {a: 999}
Steve Lang
@SteveLang gracias por señalar eso. ¡Ups! Por lo tanto, me tomé un tiempo para hacer el Vanilla JS fn anterior, que si el usuario realmente debe usarlo _.cloneen la elsecondición;)
gdibble