使用jQuery进行(深层)复制数组

241

可能是重复问题:
什么是克隆JavaScript对象最有效的方法?

我需要复制一个(有序,非关联)的对象数组。我正在使用jQuery。我最初尝试了

jquery.extend({}, myArray)

但是,自然地,这会给我返回一个对象,而我需要一个数组(顺便说一句,我真的很喜欢jQuery.extend)。

那么,复制一个数组的最佳方式是什么?


如果您不想返回一个对象,则请使用[]代替{} - Ashwin
8个回答

293

由于Array.slice()不会进行深拷贝,因此它不适用于多维数组:

var a =[[1], [2], [3]];
var b = a.slice();

b.shift().shift();
// a is now [[], [2], [3]]
请注意,尽管我在上面使用了shift().shift(),但重点是b[0][0]包含对a[0][0]的指针,而不是值。
同样,delete(b[0][0])也会导致a[0][0]被删除,b[0][0]=99也会将a[0][0]的值更改为99。
当作为初始参数传递真值时,jQuery的extend方法执行深度复制:
var a =[[1], [2], [3]];
var b = $.extend(true, [], a);

b.shift().shift();
// a is still [[1], [2], [3]]

14
谢谢Noah。看起来我的最大问题是我把一个对象作为$.extend的第一个参数,而不是一个数组。 - morgancodes
2
你能解释一下 b.shift().shift() 在这里的作用是什么吗? - LCJ
1
b 只是被操纵来显示 a 和 b 不持有相同的值。a 和 b 不应该表示为 [[3]] 吗?Shift 会完全删除数组中的第一个值,无论它是什么类型。它不执行递归搜索以查找原始类型,然后将其删除。保存方法上下文的数组的长度始终减少 1,并在原地编辑。 - danronmoon
谢谢!你的代码:var b = $.extend(true, [], a); 救了我的命!我之前用了$.extend(true, {}, a)的花括号,而不是方括号! - Mixim
1
非常感谢,这很有帮助。通常情况下,当我们通过直接赋值的方式复制数组时,当一个数组发生变化时,另一个复制的数组也会发生变化,所以这种方法可以进行真正的复制。 - user889030

31

$.extend(true, [], [['a', ['c']], 'b'])

这应该适用于你的需求。


这对于多维数组不起作用,您必须像@Noah Sussman的答案中提到的那样使用“true”作为第一个参数。 - Akshay Vijay Jain

21

我知道您正在寻找数组的“深度”复制,但如果您只有一个单层数组,可以使用以下方法:

复制JS原生数组很简单。使用Array.slice()方法即可创建数组的全部或部分副本。

var foo = ['a','b','c','d','e'];
var bar = foo.slice();

现在,foo和bar是包含'a'、'b'、'c'、'd'、'e'这五个成员的数组。

当然,bar是一个副本而不是一个引用... 所以如果你接下来执行了这个操作...

bar.push('f');
alert('foo:' + foo.join(', '));
alert('bar:' + bar.join(', '));

你现在将会得到:

foo:a, b, c, d, e
bar:a, b, c, d, e, f

24
请注意,这并不是一份深拷贝。 - Yauhen Yakimovich
类似的:var a = [1,2,3]; var b = ([]).concat(a); b 是一个副本 - Yauhen Yakimovich
4
Array.slice 方法并不能提供深拷贝,这正是这里所问的问题。 - Ryley
1
作者在括号中放置了“deep”,因此任何关于复制的答案都可以接受。实际问题的文本根本不包含单词“deep”。 - F Lekschas

13

JavaScript 中的所有内容都是按引用传递的,因此如果您想要数组中对象的真正深层副本,我能想到的最好方法是将整个数组序列化为 JSON,然后再进行反序列化。


2
原始类型不是通过引用传递的。但对象(包括数组)则是。 - Kevin Beal

7

如果想使用纯JavaScript,请尝试以下方法:

 var arr=["apple","ball","cat","dog"];
 var narr=[];

 for(var i=0;i<arr.length;i++){
     narr.push(arr[i]);
 }
 alert(narr); //output: apple,ball,vat,dog
 narr.push("elephant");
 alert(arr); // output: apple,ball,vat,dog
 alert(narr); // apple,ball,vat,dog,elephant

3

关于复杂类型怎么处理呢?当数组包含对象或其他数据类型时...

我的建议:

Object.prototype.copy = function(){
    var v_newObj = {};
    for(v_i in this)
        v_newObj[v_i] = (typeof this[v_i]).contains(/^(array|object)$/) ? this[v_i].copy() : this[v_i];
    return v_newObj;
}

Array.prototype.copy = function(){
    var v_newArr = [];
    this.each(function(v_i){
        v_newArr.push((typeof v_i).contains(/^(array|object)$/) ? v_i.copy() : v_i);
    });
    return v_newArr;
}

这不是最终版本,只是一种想法。

PS:方法 each 和 contains 也是原型。


2

我计划在下一个版本的jPaq中发布这段代码,但在那之前,如果您的目标是对数组进行深度复制,可以使用以下代码:

Array.prototype.clone = function(doDeepCopy) {
    if(doDeepCopy) {
        var encountered = [{
            a : this,
            b : []
        }];

        var item,
            levels = [{a:this, b:encountered[0].b, i:0}],
            level = 0,
            i = 0,
            len = this.length;

        while(i < len) {
            item = levels[level].a[i];
            if(Object.prototype.toString.call(item) === "[object Array]") {
                for(var j = encountered.length - 1; j >= 0; j--) {
                    if(encountered[j].a === item) {
                        levels[level].b.push(encountered[j].b);
                        break;
                    }
                }
                if(j < 0) {
                    encountered.push(j = {
                        a : item,
                        b : []
                    });
                    levels[level].b.push(j.b);
                    levels[level].i = i + 1;
                    levels[++level] = {a:item, b:j.b, i:0};
                    i = -1;
                    len = item.length;
                }
            }
            else {
                levels[level].b.push(item);
            }

            if(++i == len && level > 0) {
                levels.pop();
                i = levels[--level].i;
                len = levels[level].a.length;
            }
        }

        return encountered[0].b;
    }
    else {
        return this.slice(0);
    }
};

以下是如何调用此函数以对递归数组进行深拷贝的示例:
// Create a recursive array to prove that the cloning function can handle it.
var arrOriginal = [1,2,3];
arrOriginal.push(arrOriginal);

// Make a shallow copy of the recursive array.
var arrShallowCopy = arrOriginal.clone();

// Prove that the shallow copy isn't the same as a deep copy by showing that
// arrShallowCopy contains arrOriginal.
alert("It is " + (arrShallowCopy[3] === arrOriginal)
    + " that arrShallowCopy contains arrOriginal.");

// Make a deep copy of the recursive array.
var arrDeepCopy = arrOriginal.clone(true);

// Prove that the deep copy really works by showing that the original array is
// not the fourth item in arrDeepCopy but that this new array is.
alert("It is "
    + (arrDeepCopy[3] !== arrOriginal && arrDeepCopy === arrDeepCopy[3])
    + " that arrDeepCopy contains itself and not arrOriginal.");

您可以在JS Bin中试验这段代码

1
有趣。@Chris West,您能说一下您的答案解决了哪些问题,而jQuery extend没有解决吗?非常感谢。 - iainH

2

谢谢,我正在寻找一个通用的克隆方法(有时是对象,有时是对象数组)。 - Apex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接