将数组项目复制到另一个数组中


916

我有一个JavaScript数组dataArray,我想将其推送到一个新数组中newArray。除了我不想newArray[0]这样dataArray。我想将所有项目推入新数组:

var newArray = [];

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

甚至更好:

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,逐个添加项目?




Answers:


1246

使用concat函数,如下所示:

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

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



15
我同意执行者执行非常好。不CONCAT正是为了那个目的CONCAT到数组?所以应该是标准的。还是concat还有其他更好的方法?而且它可能仅由于浏览器的JS引擎执行不当或您在任何地方使用它而变慢了?可能会固定一天。我会选择代码可维护性而不是hacky速度优化。嗯..
Bitterblue

3
我也只是基准测试了这种情况:concat vs. push.apply。Google Chrome:快速(concat =获胜者),:Opera快速(concat =获胜IE者)Firefox,:较慢(concat =获胜者),:慢速(push.apply =获胜者,但比Chrome的concat慢10倍)...谈到不良的JS引擎实现。
Bitterblue

15
如何将两个数组连接为如何一个数组入另一个数组的公认答案?那是两个不同的操作。
kaqqao

@kaqqao,因为push它不会展平值数组。concat达到问题的要求。
WiseGuyEh

644

如果数组不是很大(请参见下面的注意事项),则可以使用push()要向其附加值的数组方法。push()可以采用多个参数,因此您可以使用其apply()方法将要推送的值数组作为函数参数列表传递。与使用concat()将元素添加到数组中而不是创建新数组相比,这具有优势。

但是,似乎对于大型数组(成员数为100,000或更多),此技巧可能会失败。对于此类阵列,使用循环是一种更好的方法。有关详细信息,请参见https://stackoverflow.com/a/17368101/96100

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

您可能需要将此概括为一个函数:

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

...或将其添加到Array的原型中:

Array.prototype.pushArray = function(arr) {
    this.push.apply(this, arr);
};

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

...或模拟原始push()通过允许多个参数使用的事实,方法concat(),如push(),允许多个参数:

Array.prototype.pushArray = function() {
    this.push.apply(this, this.concat.apply([], arguments));
};

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

这是最后一个示例的基于循环的版本,适用于大型阵列和所有主流浏览器,包括IE <= 8:

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

9
注意:newArray.push.apply(newArray, dataArray1);Array.prototype.push.applay(newArray,dataArra1);

这与旧版浏览器兼容吗?
DamienÓCeallaigh


1
@DamienÓCeallaigh:是的。所有这些都与回溯至IE 6或更早版本的浏览器兼容。
Tim Down

这是不良编程实践的缩影。Array.prototype.concat不错,只是被误解,有时被滥用。在这种情况下,它只会被误解,而不会被滥用。
杰克·吉芬

444

我将再添加一个“面向未来”的回复

在ECMAScript 6中,您可以使用Spread语法

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

console.log(arr1)

传播语法尚未在所有主流浏览器中提供。有关当前兼容性,请参见此(连续更新)兼容性表

但是,您可以将扩展语法与Babel.js一起使用

编辑:

有关性能的更多评论,请参见下面的Jack Giffin的回复。似乎concat仍然比散布运算符更好和更快。


7
如果您使用的是TypeScript,也可以使用传播运算符。如果您以ES5为目标,它将编译为newArray.apply(newArray, dataArray1)
AJ理查森

5
注意:如果需要将结果放在第三个数组中(因此不修改arr1,因为最初的问题似乎要求如此),则可以执行newArray = [... arr1,... arr2]
昏暗

哦,我不知道。谢谢。我添加了一个编辑评论。
卡雷尔·比列克(KarelBílek)

然后类似于concat的功能,但具有持久性(改变原始数组)的好处。
抢夺

@robertmylne传播运算符显然不会修改原始数组,而是为所有被剥夺的数组内容创建一个新副本。
杰克·吉芬

145

MDN找到了一种优雅的方式

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']

或者,您可以使用spread operatorES6 的功能:

let fruits = [ 'apple', 'banana'];
const moreFruits = [ 'orange', 'plum' ];

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

1
这不是问题所要的。问题是每次都需要一种创建新数组的方法,而不是修改旧数组。
杰克·吉芬

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

这样可以解决您的问题吗?


13

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

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

由于“ push”具有可变数量的参数,因此您可以使用函数的apply方法push来推送另一个数组的所有元素。它使用其第一个参数(此处为“ newArray”)作为“ this”,并使用数组的元素作为其余参数构造一个推入调用。

slice在第一个语句获取第一个数组的副本,所以您不要修改它。

更新如果您正在使用带有可用片段的javascript版本,则可以将push表达式简化为:

newArray.push(...dataArray2)

1
在MDN上也作为“合并两个数组”的示例在此处提到
Wilt

12

下面的函数对数组的长度没有问题,并且比所有建议的解决方案都有更好的表现:

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的结果

        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

哪里

for循环和推送是:

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

推送应用:

target.push.apply(target, source);

点差运算符:

    target.push(...source);

最后,“设置长度和循环”是上面的功能


这个问题正在寻找一种每次都创建一个新数组而不修改现有数组的方法。
杰克·吉芬

10

𝗥𝗲𝘀𝘂𝗹𝘁𝘀

事实是,在jsperf上进行性能测试并检查了控制台中的某些内容。为了进行研究,使用了网站irt.org。下面是所有这些来源的集合,底部是示例函数。

╔═══════════════╦══════╦═════════════════╦════════ ═══════╦═════════╦══════════╗
║方法║Concat║slice&push.apply║push.applyx2║ForLoop║Spread║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║mOps /秒║179║104║76║81║28║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
rs稀疏数组║是!║Only切片║║没有可能2   ║no║
║保持稀疏║array(1st arg)║║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║支持║MSIE4║MSIE5.5║MSIE 5.5║MSIE 4║Edge12║
║()║NNav4║NNav4.06║NNAV 4.06║NNAV 3║ MSIE  NNAV ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║类似数组​​的行为║否║仅按pushed是!║是的!have如果有║
║like阵列║║array(第二ARG)║║║iterator 1   ║
╚═══════════════牛皮══════牛皮═════════════════牛皮════════ ═══════牛皮═════════牛皮══════════╝
1如果类数组对象没有Symbol.iterator属性,则尝试
  传播它会引发异常。
2取决于代码。以下示例代码“ YES”保留了稀疏性。
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几乎始终是提高性能和保留备用阵列稀疏性的方法。然后,对于类似数组的对象(例如DOMNodeLists之类的document.body.children),我建议使用for循环,因为它既是性能第二高的方法,又是保留稀疏数组的唯一其他方法。下面,我们将快速浏览稀疏数组和类似数组的含义,以消除混乱。

𝗙𝘂𝘁𝘂𝗿𝗲

最初,有些人可能会认为这是fl幸,浏览器供应商最终将绕过优化Array.prototype.push的速度,使其足以击败Array.prototype.concat。错误!Array.prototype.concat总是更快(至少在原则上来说如此),因为它是对数据的简单复制-n-粘贴。以下是32位阵列实现的外观的简化的persuado-visual图表(请注意,实际实现要复杂得多)

字节║数据在这里
═════╬═══════════
0x00║int nonNumericPropertiesLength = 0x00000000
0x01║同上
0x02║同上
0x03║同上
0x00║int长度= 0x00000001
0x01║同上
0x02║同上
0x03║同上
0x00║int valueIndex = 0x00000000
0x01║同上
0x02║同上
0x03║同上
0x00║int valueType = JS_PRIMITIVE_NUMBER
0x01║同上
0x02║同上
0x03║同上
0x00║uintptr_t valuePointer = 0x38d9eb60(或内存中的任何位置)
0x01║同上
0x02║同上
0x03║同上

如上所示,复制类似内容所需要做的几乎就像逐字节复制一样简单。使用Array.prototype.push.apply,它不只是对数据进行简单的复制粘贴。“ .apply”必须检查数组中的每个索引,并将其转换为一组参数,然后再将其传递给Array.prototype.push。然后,Array.prototype.push每次必须另外分配更多的内存,并且(对于某些浏览器实现)甚至可能出于稀疏性而重新计算一些位置查找数据。

另一种思考的方式是这样。源阵列之一是装订在一起的一大堆纸。源阵列2也是另一叠大文件。你会更快吗

  1. 去商店,购买足够的纸张来复制每个源阵列。然后,将每个源阵列纸叠通过复印机,将装订后的两个副本装订在一起。
  2. 去商店,购买足够的纸张以获取第一个源阵列的单个副本。然后,手动将源阵列复制到新纸张上,确保填充任何空白的稀疏斑点。然后,回到商店,为第二个来源阵列购买足够的纸张。然后,检查第二个源数组并复制它,同时确保副本中没有空白。然后,将所有复印的纸张装订在一起。

在上面的类比中,选项#1表示Array.prototype.concat,而#2表示Array.prototype.push.apply。让我们用类似的JSperf进行测试,不同之处仅在于此代码测试的是基于稀疏数组而非实体数组的方法。人们可以在这里找到它。

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

𝗖𝗹𝗮𝗿𝗶𝗳𝗶𝗰𝗮𝘁𝗶𝗼𝗻𝘀

𝗔𝗿𝗿𝗮𝘆𝘀

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

// 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允许您轻松初始化备用阵列。

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

𝗔𝗿𝗿𝗮𝘆-𝗟𝗶𝗸𝗲𝘀

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

{0:“ foo”,1:“ bar”,长度:2}
document.body.children
新的Uint8Array(3)
  • 这类似于数组,因为尽管它是一个(n)(类型化)数组,但将其强制为数组会更改构造函数。
(function(){返回参数})()

观察使用将像数组一样强制转换为像slice这样的数组的方法会发生什么。

  • 注意:由于性能,在函数参数上调用slice是一种不好的做法。

观察使用不会像数组一样强制转换为像concat这样的数组的方法会发生什么。


9

使用JavaScript ES6,您可以将...运算符用作扩展运算符,这实际上会将数组转换为值。然后,您可以执行以下操作:

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

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

虽然语法很简洁,但我不知道它在内部如何工作以及对大型数组的性能影响如何。


2
如果您查看babel的转换方式,将会发现它不应该比使用Array.push.apply技术慢。
emil.c

1
@JackGiffin我只是指Ryan提到他不知道它在内部如何工作以及对性能有何影响,我实际上并不是在建议这种方法。无论如何,您在答案上做得非常出色,做得很好,对这些细节的了解总是很好。
emil.c

9

这是ES6的方式

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

上面的方法适用于大多数情况,不建议考虑的情况concat,例如数组中有成千上万的项目。

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


8

关于Array.prototype.push.apply有很多答案。这是一个清晰的示例:

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语法:

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]


4

如果要修改原始数组,可以展开并推送:

var source = [1, 2, 3];
var range = [5, 6, 7];
var length = source.push(...range);

console.log(source); // [ 1, 2, 3, 5, 6, 7 ]
console.log(length); // 6

如果要确保只有相同类型的项进入source数组(例如,不混合数字和字符串),请使用TypeScript。

/**
 * Adds the items of the specified range array to the end of the source array.
 * Use this function to make sure only items of the same type go in the source array.
 */
function addRange<T>(source: T[], range: T[]) {
    source.push(...range);
}

2

我们有两个数组a和b。该代码在这里所做的是将数组a的值压入数组b中。

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' ]

1
请不要仅仅将代码作为答案。解释代码的作用以及如何解决问题。
Patrick Hund


-2

代替push()函数,请对IE使用concat函数。例,

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

1
两者都非常兼容IE
Jack Giffin

-3

Тhis是一个有效的代码,可以正常工作:

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))}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.