Як клонувати масив об’єктів за допомогою підкреслення?


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

Це призводить до:

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

Клон, здається, не працює! Отже, я RTFM, і бачу це:

http://underscorejs.org/#clone

Створіть неглибоко скопійований клон об’єкта. Будь-які вкладені об'єкти або масиви будуть скопійовані за посиланням, а не дубльовані.

Так що _.cloneдосить марно. Чи є спосіб насправді скопіювати масив об’єктів?


4
Був відхилений запит на глибоку копію: github.com/jashkenas/underscore/pull/595 Lo-Dash has cloneDeep
epascarello

5
ха-ха я щойно зрозумів каламбур підкреслення. Низький тире.
Джесс

1
Підкреслення проти lodash: stackoverflow.com/questions/13789618 / ...
Джесс

Відповіді:


122

Ну, є хитрість! Якщо клон не «клонує» вкладені об’єкти, ви можете змусити його, явно клонуючи кожен об’єкт усередині виклику карти! Подобається це:

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

Друк:

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

Ага! aзалишається незмінним! Тепер я можу редагувати bна свій смак!


48
Будьте обережні. Звичайно, це працює лише на два рівні глибини. не для масивів або об’єктів, які вкладені навіть більше, ніж цей приклад.
Саймон Зікс

1
За задумом, Підкреслення також не клонує належним чином значення RegExp або Date
Mark K Cowan

1
Той, хто заглядає сюди, повинен побачити мою відповідь нижче.
gdibble

65

Інше рішення, витягнуте з проблеми на Github, яке працює з будь-яким рівнем вкладених даних і не вимагає підкреслення:

JSON.parse(JSON.stringify(obj))

13
Це працює, якщо об’єкт не має циклу, і в цьому випадку JSON.stringifyвиникає помилка. Що не так у оригіналі, але все ще цікавий стан справ. a = {simple: 'thing'}; a.cycle = a ; JSON.stringify(a).
mcdave

10
Також варто зазначити, що це рішення працює лише для об'єктів простих типів. Наприклад, якщо ви маєте об'єкт Dateабо Regexекземпляри, вони будуть серіалізовані до рядків. Не кінець світу, але вам потрібно це обробити, якщо ви цим користуєтесь і очікуєте Dateекземплярів.
cayleyh

1
І якщо ви думаєте, що хтось може спробувати нагодувати undefinedце, вам захочеться, JSON.parse(JSON.stringify(obj) || null)інакше це призведе до помилки.
Ian Mackinnon

1
Разом із тим, що згадував @cayleyh, це взагалі відмовиться function.
Марко Грешак


9

Підкреслення посилання на API :

_.toArray(list)Створює справжній масив зі списку (все, що можна переглядати). Корисно для перетворення об'єкта аргументів.

... або в цьому випадку клонування масиву. Спробуйте це:

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

Далі подано додаток, який я створив після коментаря Стіва нижче -thx

Ванільний JS (або використовуючи, _.cloneякщо потрібно) рекурсивний помічник глибокого клонування :

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

*/

Це найкраща відповідь.
П'єр,

_.toArray(list)не клонує об’єкти в масиві. var array1 = [{a: 1}, {a: 2}, {a: 3}]; var array2 = _.toArray(array1); array2[0].a = 999; console.log(array1[0]); --> {a: 999}
Стів Ленг,

@SteveLang дякую, що вказали на це. На жаль Тому я зайняв певний час, щоб зробити ванільний JS fn вище, який, якщо користувач дійсно повинен використовувати the _.cloneв elseумові;)
gdibble
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.