Làm cách nào để bạn sao chép một mảng đối tượng bằng cách sử dụng dấu gạch dưới?


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

Kết quả này trong:

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

Bản sao dường như không hoạt động! Vì vậy, tôi RTFM và thấy điều này:

http://underscorejs.org/#clone

Tạo một bản sao được sao chép nông của đối tượng. Mọi đối tượng hoặc mảng lồng nhau sẽ được sao chép theo tham chiếu, không được sao chép.

Vì vậy, _.clonelà khá vô ích. Có cách nào để thực sự sao chép mảng đối tượng?


4
Có một yêu cầu kéo cho bản sao sâu mà đã bị từ chối: github.com/jashkenas/underscore/pull/595 Lo-Dash có cloneDeep
epascarello

5
lol Tôi vừa nhận ra cách chơi chữ trên gạch dưới. Dấu gạch ngang thấp.
Jess

1
gạch dưới so với lodash: stackoverflow.com/questions/13789618/…
Jess

Câu trả lời:


122

Chà, có một mẹo nhỏ! Nếu sao chép không "sao chép" các đối tượng lồng nhau, bạn có thể buộc nó phải sao chép rõ ràng từng đối tượng bên trong lệnh gọi bản đồ! Như thế này:

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

Bản in:

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

Yay! alà không thay đổi! Bây giờ tôi có thể chỉnh sửa btheo ý thích của mình!


48
Hãy cẩn thận, mặc dù. Tất nhiên điều này chỉ hoạt động sâu ở hai cấp độ. không dành cho các mảng hoặc đối tượng được lồng vào nhau nhiều hơn ví dụ này.
Simon Zyx

1
Theo thiết kế, dấu gạch dưới cũng sẽ không nhân bản RegExp hoặc ngày giá trị đúng
Đánh K Cowan

1
Bất cứ ai nhìn vào đây nên xem câu trả lời của tôi dưới đây.
gdibble,

65

Một giải pháp khác được trích xuất từ sự cố trên Github hoạt động với bất kỳ mức dữ liệu lồng nhau nào và không yêu cầu dấu gạch dưới:

JSON.parse(JSON.stringify(obj))

13
Điều này hoạt động trừ khi đối tượng có một chu kỳ, trong trường hợp này JSON.stringifysẽ tạo ra một lỗi. Đó không phải là trường hợp trong bản gốc nhưng vẫn là một trạng thái thú vị. a = {simple: 'thing'}; a.cycle = a ; JSON.stringify(a).
mcdave

10
Cũng cần lưu ý giải pháp này chỉ hoạt động với các đối tượng có kiểu đơn giản. Ví dụ: nếu đối tượng của bạn có Datehoặc các Regexphiên bản, chúng sẽ được tuần tự hóa thành chuỗi. Không phải ngày tận thế, nhưng bạn cần phải xử lý nó nếu bạn đang sử dụng điều này và mong đợi các Datetrường hợp.
cayleyh

1
Và nếu bạn nghĩ rằng ai đó có thể thử và cung cấp thứ undefinednày thì bạn sẽ muốn, JSON.parse(JSON.stringify(obj) || null)nếu không nó sẽ báo lỗi.
Ian Mackinnon

1
Cùng với những gì @cayleyh đã đề cập, điều này sẽ giảm functionhoàn toàn.
Marko Grešak

13

FWIW, lodash có chức năng cloneDeep :

Phương thức này giống như _.clone ngoại trừ việc nó sao chép một cách đệ quy giá trị.


9

Tham chiếu API gạch dưới :

_.toArray(list)Tạo một Mảng thực từ danh sách (bất kỳ thứ gì có thể được lặp lại). Hữu ích để chuyển đổi đối tượng đối số.

... hoặc trong trường hợp này, sao chép một mảng. Thử đi:

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

Sau đây là một phụ lục tôi đã tạo ra sau bình luận của Steve bên dưới -thx

Một trình trợ giúp đệ quy nhân bản sâu Vanilla JS (hoặc sử dụng _.clonenếu muốn) :

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

*/

Đây là câu trả lời tốt nhất.
Pierre

_.toArray(list)không sao chép các đối tượng trong một mảng. 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 cảm ơn bạn đã chỉ ra điều đó. Giáo sư. Vì vậy, tôi đã mất một chút thời gian để tạo vani JS fn ở trên, mà nếu người dùng thực sự phải sử dụng nó _.clonetrong elseđiều kiện;)
gdibble
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.