Wie klonen Sie ein Array von Objekten mit Unterstrich?

81
#!/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));

Das führt zu:

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

Klon scheint nicht zu funktionieren! Also ich RTFM und sehe das:

http://underscorejs.org/#clone

Erstellen Sie einen flach kopierten Klon des Objekts. Verschachtelte Objekte oder Arrays werden als Referenz kopiert und nicht dupliziert.

Ist _.clonealso ziemlich nutzlos. Gibt es eine Möglichkeit, das Array von Objekten tatsächlich zu kopieren?

Jess
quelle
4
Es gab eine Pull-Anfrage für eine tiefe Kopie, die abgelehnt wurde: github.com/jashkenas/underscore/pull/595 Lo-Dash hat cloneDeep
epascarello
5
lol Ich habe gerade das Wortspiel auf Unterstrich bemerkt. Niedriger Strich.
Jess
1
Unterstrich gegen lodash: stackoverflow.com/questions/13789618/…
Jess

Antworten:

122

Nun, es gibt einen Trick! Wenn der Klon verschachtelte Objekte nicht "klont", können Sie dies erzwingen, indem Sie jedes Objekt innerhalb eines Kartenaufrufs explizit klonen! So was:

#!/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));

Drucke:

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

Yay! aist unverändert! Ich kann jetzt bnach meinen Wünschen bearbeiten !

Jess
quelle
47
Sei aber vorsichtig. Dies funktioniert natürlich nur zwei Ebenen tief. Nicht für Arrays oder Objekte, die noch mehr als in diesem Beispiel verschachtelt sind.
Simon Zyx
1
Mit Absicht wird Underscore auch RegExp- oder Datumswerte nicht richtig klonen
Mark K Cowan
1
Wer hier sucht, sollte meine Antwort unten sehen.
Gdibble
64

Eine andere Lösung, die aus dem Problem in Github extrahiert wurde und mit jeder Ebene verschachtelter Daten funktioniert und keinen Unterstrich erfordert:

JSON.parse(JSON.stringify(obj))
Nacho Coloma
quelle
12
Dies funktioniert nur, wenn das Objekt einen Zyklus hat. In diesem Fall wird JSON.stringifyein Fehler ausgegeben. Was im Original nicht der Fall ist, aber immer noch ein interessanter Zustand ist. a = {simple: 'thing'}; a.cycle = a ; JSON.stringify(a).
Mcdave
9
Erwähnenswert ist auch, dass diese Lösung nur für Objekte mit einfachen Typen funktioniert. Zum Beispiel , wenn Sie Objekt Dateoder RegexInstanzen, werden sie in Strings serialisiert werden. Nicht das Ende der Welt, aber Sie müssen damit umgehen, wenn Sie dies verwenden und DateInstanzen erwarten .
Cayleyh
1
Und wenn Sie glauben, dass jemand versuchen könnte, dies zu füttern undefined, möchten Sie, JSON.parse(JSON.stringify(obj) || null)sonst wird ein Fehler ausgegeben.
Ian Mackinnon
1
Zusammen mit dem, was @cayleyh erwähnt hat, wird dies functioninsgesamt fallen.
Marko Grešak
13

FWIW, lodash hat eine cloneDeep- Funktion:

Diese Methode ähnelt _.clone, klont jedoch rekursiv den Wert.

Hertzel Guinness
quelle
7

Unterstrich API-Referenz :

_.toArray(list)Erstellt ein echtes Array aus der Liste (alles, was wiederholt werden kann). Nützlich zum Umwandeln des Argumentobjekts.

... oder in diesem Fall ein Array klonen . Versuche dies:

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

Das Folgende ist ein Nachtrag, den ich nach Steves Kommentar unter -thx erstellt habe

Ein rekursiver Helfer für das Klonen von Vanilla JS (oder _.clonewenn gewünscht) :

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
quelle
Dies ist die beste Antwort.
Pierre
_.toArray(list)klont keine Objekte in einem Array. 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 danke für den Hinweis. Hoppla. Daher habe ich einige Zeit gebraucht, um die Vanille JS fn oben zu machen, die, wenn der Benutzer wirklich muss, die _.clonein der elseBedingung verwenden kann;)
gdibble