关于javascript:将数组项复制到另一个数组中

Copy array items into another array

我有一个javascript数组dataArray,我想把它推入一个新的数组newArray。但我不想让newArray[0]成为dataArray。我要将所有项推入新数组:

1
2
3
4
5
var newArray = [];

newArray.pushValues(dataArray1);
newArray.pushValues(dataArray2);
// ...

甚至更好:

1
2
3
4
5
var newArray = new Array (
   dataArray1.values(),
   dataArray2.values(),
   // ... where values() (or something equivalent) would push the individual values into the array, rather than the array itself
);

所以现在新数组包含了各个数据数组的所有值。是否有一些像pushValues这样的速记法可用,这样我就不必对每个单独的dataArray进行迭代,逐个添加项目?


使用concat函数,如下所示:

1
2
3
var arrayA = [1, 2];
var arrayB = [3, 4];
var newArray = arrayA.concat(arrayB);

newArray的值将为[1, 2, 3, 4](arrayAarrayB保持不变;concat为结果创建并返回一个新数组)。


如果数组不是很大(请参见下面的警告),则可以使用希望向其追加值的数组的push()方法。push()可以接受多个参数,因此可以使用其apply()方法传递要作为函数参数列表推送的值数组。与使用concat()相比,这有一个优势,即把元素添加到数组中,而不是创建一个新的数组。

然而,对于大型阵列(约100000个成员或更多),这个技巧可能会失败。对于这种数组,使用循环是一种更好的方法。有关详细信息,请参阅https://stackoverflow.com/a/17368101/96100。

1
2
3
var newArray = [];
newArray.push.apply(newArray, dataArray1);
newArray.push.apply(newArray, dataArray2);

您可能希望将其归纳为一个函数:

1
2
3
function pushArray(arr, arr2) {
    arr.push.apply(arr, arr2);
}

…或者添加到Array的原型中:

1
2
3
4
5
6
7
Array.prototype.pushArray = function(arr) {
    this.push.apply(this, arr);
};

var newArray = [];
newArray.pushArray(dataArray1);
newArray.pushArray(dataArray2);

…或者通过允许多个参数来模拟原始的push()方法,使用这样一个事实:concat()push()允许多个参数:

1
2
3
4
5
6
Array.prototype.pushArray = function() {
    this.push.apply(this, this.concat.apply([], arguments));
};

var newArray = [];
newArray.pushArray(dataArray1, dataArray2);

下面是上一个示例的基于循环的版本,适用于大型数组和所有主要浏览器,包括ie<=8:

1
2
3
4
5
6
Array.prototype.pushArray = function() {
    var toPush = this.concat.apply([], arguments);
    for (var i = 0, len = toPush.length; i < len; ++i) {
        this.push(toPush[i]);
    }
};


我再加一个"未来证明"回复

在ECMAScript 6中,可以使用排列运算符:

1
2
3
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

Spread运算符尚未包含在所有主要浏览器中。有关当前兼容性,请参阅此(持续更新的)兼容性表。

但是,可以将spread operator与babel.js一起使用。

编辑:

关于绩效的更多评论,请参阅下面的杰克·吉芬回复。看起来concat比spread操作符更好更快。


从MDN找到了一种优雅的方式

1
2
3
4
5
6
7
8
var vegetables = ['parsnip', 'potato'];
var moreVegs = ['celery', 'beetroot'];

// Merge the second array into the first one
// Equivalent to vegetables.push('celery', 'beetroot');
Array.prototype.push.apply(vegetables, moreVegs);

console.log(vegetables); // ['parsnip', 'potato', 'celery', 'beetroot']

也可以使用es6的spread operator功能:

1
2
3
4
let fruits = [ 'apple', 'banana'];
const moreFruits = [ 'orange', 'plum' ];

fruits.push(...moreFruits); // ["apple","banana","orange","plum"]


以下对我来说似乎最简单:

1
2
var newArray = dataArray1.slice();
newArray.push.apply(newArray, dataArray2);

由于"push"采用的参数数量可变,因此可以使用push函数的apply方法来推送另一个数组的所有元素。它构建使用其第一个参数("newarray"此处)作为"this"和数组的元素作为其余参数。

第一条语句中的slice获取第一个数组的副本,因此您不修改它。

更新如果使用的是具有可用切片的javascript版本,则可以将push表达式简化为:

1
newArray.push(...dataArray2)


1
2
3
4
var a=new Array('a','b','c');
var b=new Array('d','e','f');
var d=new Array('x','y','z');
var c=a.concat(b,d)

那能解决你的问题吗?


关于array.prototype.push.apply有很多答案。下面是一个明确的例子:

1
2
3
4
5
6
var dataArray1 = [1, 2];
var dataArray2 = [3, 4, 5];
var newArray = [ ];
Array.prototype.push.apply(newArray, dataArray1); // newArray = [1, 2]
Array.prototype.push.apply(newArray, dataArray2); // newArray = [1, 2, 3, 4, 5]
console.log(JSON.stringify(newArray)); // Outputs: [1, 2, 3, 4, 5]

如果您有ES6语法:

1
2
3
4
5
6
var dataArray1 = [1, 2];
var dataArray2 = [3, 4, 5];
var newArray = [ ];
newArray.push(...dataArray1); // newArray = [1, 2]
newArray.push(...dataArray2); // newArray = [1, 2, 3, 4, 5]
console.log(JSON.stringify(newArray)); // Outputs: [1, 2, 3, 4, 5]


下面的函数不存在数组长度问题,并且性能优于所有建议的解决方案:

1
2
3
4
5
6
7
8
function pushArray(list, other) {
    var len = other.length;
    var start = list.length;
    list.length = start + len;
    for (var i = 0; i < len; i++ , start++) {
        list[start] = other[i];
    }
}

不幸的是,jspref拒绝接受我的提交,所以这里是使用benchmark.js的结果。

1
2
3
4
5
        Name            |   ops/sec   |  ± %  | runs sampled
for loop and push       |      177506 |  0.92 | 63
Push Apply              |      234280 |  0.77 | 66
spread operator         |      259725 |  0.40 | 67
set length and for loop |      284223 |  0.41 | 66

在哪里?

对于循环和推送:

1
2
3
    for (var i = 0, l = source.length; i < l; i++) {
        target.push(source[i]);
    }

推应用:

1
target.push.apply(target, source);

排列运算符:

1
    target.push(...source);

最后,"设置长度和for循环"是上面的函数


使用JavaScriptES6,您可以使用…运算符作为一个扩散运算符,它基本上将数组转换为值。然后,您可以这样做:

1
2
3
4
5
6
7
const myArray = [1,2,3,4,5];
const moreData = [6,7,8,9,10];

const newArray = [
  ...myArray,
  ...moreData,
];

虽然语法很简洁,但我不知道这在内部是如何工作的,也不知道大型数组的性能影响是什么。


研究和结果

事实上,在JSPerf执行性能测试,并在控制台中检查一些东西。研究使用网站irt.org。下面是所有这些源的集合,加上底部的示例函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
╔═══════════════╦══════╦═════════════════╦═══════════════╦═════════╦══════════╗
║ Method        ║Concat║slice&push.apply ║ push.apply x2 ║ ForLoop ║Spread    ║
╠═══════════════╬══════╬═════════════════╬═══════════════╬═════════╬══════════╣
║ mOps/Sec      ║179   ║104              ║ 76            ║ 81      ║28        ║
╠═══════════════╬══════╬═════════════════╬═══════════════╬═════════╬══════════╣
║ Sparse arrays ║YES!  ║Only the sliced  ║ no            ║ Maybe2<sub>  </sub> ║no        ║
║ kept sparse   ║      ║array (1st arg)  ║               ║         ║          ║
╠═══════════════╬══════╬═════════════════╬═══════════════╬═════════╬══════════╣
║ Support       ║MSIE 4║MSIE 5.5         ║ MSIE 5.5      ║ MSIE 4  ║Edge 12   ║
(source)      ║NNav 4║NNav 4.06        ║ NNav 4.06     ║ NNav 3  ║<strike>MSIE</strike> <strike>NNav</strike>
╠═══════════════╬══════╬═════════════════╬═══════════════╬═════════╬══════════╣
║Array-like acts║no    ║Only the pushed  ║ YES!          ║ YES!    ║If have   ║
║like an array  ║      ║array (2nd arg)  ║               ║         ║iterator1<sub>  </sub>
╚═══════════════╩══════╩═════════════════╩═══════════════╩═════════╩══════════╝
1 If the array-like object does not have a Symbol.iterator property, then trying
  to spread it will throw an exception.
2 Depends on the code. The following example code"YES" preserves sparseness.
1
2
3
4
5
6
7
8
9
10
11
12
13
function mergeCopyTogether(inputOne, inputTwo){
   var oneLen = inputOne.length, twoLen = inputTwo.length;
   var newArr = [], newLen = newArr.length = oneLen + twoLen;
   for (var i=0, tmp=inputOne[0]; i !== oneLen; ++i) {
        tmp = inputOne[i];
        if (tmp !== undefined || inputOne.hasOwnProperty(i)) newArr[i] = tmp;
    }
    for (var two=0; i !== newLen; ++i, ++two) {
        tmp = inputTwo[two];
        if (tmp !== undefined || inputTwo.hasOwnProperty(two)) newArr[i] = tmp;
    }
    return newArr;
}

如上所述,我认为concat几乎总是性能和保留备用阵列稀疏性的方法。然后,对于类似数组(如domnodelist,如document.body.children),我建议使用for循环,因为它既是性能第二高的方法,也是保留稀疏数组的唯一其他方法。下面,我们将快速介绍稀疏数组的含义,数组喜欢清除混乱。

未来

起初,有些人可能认为这是一次侥幸,浏览器供应商最终将着手优化array.prototype.push,使其速度足以击败array.prototype.concat。错了!array.prototype.concat总是更快(原则上至少更快),因为它是一个简单的复制-粘贴数据。下面是一个简化的关于32位数组实现的Persuado可视图(请注意,实际的实现要复杂得多)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Byte ║ Data here
═════╬═══════════
0x00 ║ int nonNumericPropertiesLength = 0x00000000
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ int length = 0x00000001
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ int valueIndex = 0x00000000
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ int valueType = JS_PRIMITIVE_NUMBER
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ uintptr_t valuePointer = 0x38d9eb60 (or whereever it is in memory)
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid

如上所述,复制类似的东西所需要做的一切几乎和逐字节复制一样简单。对于array.prototype.push.apply,它远不止是在数据上简单地复制粘贴。".apply"必须检查数组中的每个索引,并在将其传递给array.prototype.push之前将其转换为一组参数。然后,array.prototype.push每次都必须额外分配更多的内存,并且(对于某些浏览器实现)甚至可能重新计算一些位置查找数据的稀疏性。

另一种思考方法是这样。源数组1是一大叠装订在一起的纸。源数组2也是另一大叠文件。你能快点吗

  • 到商店去,买足够的纸张来复制每个源阵列。然后,将每一个源阵列堆叠的纸张通过复印机,并将生成的两个副本装订在一起。
  • 去商店,买足够的纸,只需要一份第一个源数组的副本。然后,手工将源数组复制到新的纸张上,确保填充任何空白的稀疏点。然后,回到商店,为第二个源阵列购买足够的纸张。然后,检查第二个源数组并复制它,同时确保副本中没有空白空白。然后,把所有复印的纸钉在一起。
  • 在上面的类比中,选项1表示array.prototype.concat,而2表示array.prototype.push.apply。让我们用一个类似的JSPERF来测试这一点,只是这个JSPERF的不同之处在于它测试的是稀疏数组而不是实体数组上的方法。你可以在这里找到它。

    因此,我认为这个特定用例的未来性能不在array.prototype.push中,而在array.prototype.concat中。

    澄清备用阵列

    当数组的某些成员完全丢失时。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // This is just as an example. In actual code,
    // do not mix different types like this.
    var mySparseArray = [];
    mySparseArray[0] ="foo";
    mySparseArray[10] = undefined;
    mySparseArray[11] = {};
    mySparseArray[12] =  10;
    mySparseArray[17] ="bar";
    console.log("Length:  ", mySparseArray.length);
    console.log("0 in it: ", 0 in mySparseArray);
    console.log("arr[0]:  ", mySparseArray[0]);
    console.log("10 in it:", 10 in mySparseArray);
    console.log("arr[10]  ", mySparseArray[10]);
    console.log("20 in it:", 20 in mySparseArray);
    console.log("arr[20]: ", mySparseArray[20]);

    或者,JavaScript允许您轻松初始化备用阵列。

    1
    var mySparseArray = ["foo",,,,,,,,,,undefined,{},10,,,,,"bar"];

    数组喜欢

    类数组是至少具有length属性,但未用new Array[]初始化的对象;例如,以下对象被分类为类数组。

    1
    {0:"foo", 1:"bar", length:2}
    1
    document.body.children
    1
    new Uint8Array(3)
    • 这类似于数组,因为尽管它是(n)(类型化)数组,但将其强制为数组会更改构造函数。
    1
    (function(){return arguments})()

    使用一种方法来观察会发生什么,这种方法确实会将数组like强制到类似slice的数组中。

    1
    2
    3
    4
    5
    6
    7
    8
    var slice = Array.prototype.slice;
    // For arrays:
    console.log(slice.call(["not an array-like, rather a real array"]));
    // For array-likes:
    console.log(slice.call({0:"foo", 1:"bar", length:2}));
    console.log(slice.call(document.body.children));
    console.log(slice.call(new Uint8Array(3)));
    console.log(slice.call( function(){return arguments}() ));

    • 注意:由于性能原因,对函数参数进行切片是不好的做法。

    使用不将array like强制为concat之类的数组的方法来观察会发生什么。

    1
    2
    3
    4
    5
    6
    7
    8
    var empty = [];
    // For arrays:
    console.log(empty.concat(["not an array-like, rather a real array"]));
    // For array-likes:
    console.log(empty.concat({0:"foo", 1:"bar", length:2}));
    console.log(empty.concat(document.body.children));
    console.log(empty.concat(new Uint8Array(3)));
    console.log(empty.concat( function(){return arguments}() ));


    这是ES6路

    1
    2
    3
    4
    5
    var newArray = [];
    let dataArray1 = [1,2,3,4]
    let dataArray2 = [5,6,7,8]
    newArray = [...dataArray1, ...dataArray2]
    console.log(newArray)

    The above method is good to go for most of the cases and the cases it is not please consider concat, like you have hundred thousands of items in arrays.

    1
    2
    3
    4
        let dataArray1 = [1,2,3,4]
        let dataArray2 = [5,6,7,8]
        let newArray = dataArray1.concat(dataArray2);
        console.log(newArray)


    我们有两个数组A和B。这里所做的代码是将数组A的值推入数组B。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let a = [2, 4, 6, 8, 9, 15]

    function transform(a) {
        let b = ['4', '16', '64']
        a.forEach(function(e) {
            b.push(e.toString());
        });
        return b;
    }

    transform(a)

    [ '4', '16', '64', '2', '4', '6', '8', '9', '15' ]


    使用concat函数代替push()函数。例如,

    1
    var a=a.concat(a,new Array('amin'));


    他是一个有效的代码,工作正常:

    1
    2
    3
    4
    var els = document.getElementsByTagName('input'), i;
    var invnum = new Array();
    var k = els.length;
    for(i = 0; i < k; i++){invnum.push(new Array(els[i].id,els[i].value))}