【问题标题】:How do you clone an array of objects using underscore?如何使用下划线克隆一组对象?
【发布时间】:2014-01-26 23:46:49
【问题描述】:
#!/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 毫无用处。有没有办法实际复制对象数组?

【问题讨论】:

标签: javascript underscore.js


【解决方案1】:

嗯,有一个窍门!如果 clone 不“克隆”嵌套对象,您可以通过在 map 调用中显式克隆每个对象来强制它!像这样:

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

【讨论】:

  • 不过要小心。这当然只适用于两个层次。不适用于嵌套比此示例更多的数组或对象。
  • 按照设计,Underscore 也不会正确克隆 RegExp 或 Date 值
  • 任何看这里的人都应该在下面看到我的答案。
【解决方案2】:

提取from the issue on Github 的另一个解决方案适用于任何级别的嵌套数据,并且不需要下划线:

JSON.parse(JSON.stringify(obj))

【讨论】:

  • 这有效,除非对象有一个循环,在这种情况下JSON.stringify 会抛出一个错误。原来的情况并非如此,但仍然是一个有趣的事态。 a = {simple: 'thing'}; a.cycle = a ; JSON.stringify(a).
  • 另外值得注意的是,此解决方案仅适用于具有简单类型的对象。例如,如果您的对象有 DateRegex 实例,它们将被序列化为字符串。不是世界末日,但如果您正在使用它并期待 Date 实例,则需要处理它。
  • 如果您认为有人可能会尝试将undefined 提供给此内容,您将需要JSON.parse(JSON.stringify(obj) || null),否则会引发错误。
  • 连同@cayleyh 提到的,这将完全删除functions。
【解决方案3】:

FWIW,lodash 有一个cloneDeep 函数:

这个方法类似于_.clone,只是它递归地克隆值。

【讨论】:

  • 它的行为与具有未定义值的键不同。所以要注意一些事情。使用 lodash 克隆 {a: 1, b: undefined} 将保留未定义,但 parse/stringify 版本将删除它们。
【解决方案4】:

Underscore API reference:

_.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

Vanilla JS(或使用 _.clone,如果需要)deep cloning recursive helper

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]); --&gt; {a: 999}
  • @SteveLang 感谢您指出这一点。哎呀。因此我花了一些时间制作上面的香草JS fn,如果用户真的必须可以在else条件中使用_.clone;)
猜你喜欢
  • 1970-01-01
  • 2018-05-26
  • 1970-01-01
  • 2017-01-18
  • 2011-12-19
  • 2010-11-08
  • 2012-11-17
  • 2010-09-07
  • 2012-12-07
相关资源
最近更新 更多