【问题标题】:Copy array items into another array将数组项复制到另一个数组中
【发布时间】:2011-05-08 13:15:23
【问题描述】:

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

【问题讨论】:

标签: javascript arrays


【解决方案1】:

使用concat 函数,如下所示:

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

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

【讨论】:

  • 我同意高性能执行非常好。 BUT 不正是为了 that 目的而将 concat 连接到数组吗?所以它应该是标准的。还是与 concat 有其他更好的关系?它可能只是因为浏览器的 JS 引擎的错误实现或者你在哪里使用它而变慢?说不定哪天就修好了。我会选择代码可维护性而不是骇人听闻的速度优化。嗯....
  • 我也刚刚对情况进行了基准测试:concat 与 push.apply。 Google Chrome:快(concat = 获胜者),Opera:快(concat = 获胜者),IE:慢(concat = 获胜者),Firefox:慢(push.apply = 获胜者,但比慢 10 倍Chrome 的 concat) ...谈到糟糕的 JS 引擎实现。
  • 如何连接两个数组是如何push另一个数组的公认答案?!这是两种不同的操作。
  • @kaqqao 因为push 不会展平值数组。 concat 达到了问题的要求。
  • @WiseGuyEh 想象一下:function(stuff, toAdd) {stuff.push(toAdd);} 这里无法使用concat。这就是我所说的它们不可互换的意思。以及为什么我相信Array.prototype.push.apply(stuff, toAdd); 是正确答案。
【解决方案2】:

如果您的数组不是很大(请参阅下面的警告),您可以使用您希望附加值的数组的 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

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

【讨论】:

  • 注意:newArray.push.apply(newArray, dataArray1);Array.prototype.push.applay(newArray,dataArra1); 相同
  • 这与旧浏览器兼容吗?
  • @DamienÓCeallaigh:是的。这与可追溯到 IE 6 甚至更早版本的浏览器完全兼容。
  • 现在使用扩展运算符可以简化为array.push(...array2);
【解决方案3】:

在 ECMAScript 6 中,您可以使用 Spread syntax:

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

console.log(arr1)

所有主流浏览器(不包括 IE11)都可以使用扩展语法。有关当前兼容性,请参阅此(持续更新)compatibility table

但是,请参阅下面 Jack Giffin 的回复,了解更多关于性能的 cmets。看来concat 仍然比展开运算符更好更快。

【讨论】:

  • 如果你使用 TypeScript,你也可以使用扩展运算符。如果你以 ES5 为目标,它将编译为 newArray.apply(newArray, dataArray1)
  • 注意:如果您需要第三个数组中的结果(因此不修改 arr1,因为最初的问题似乎需要),您可以执行 newArray = [...arr1, ...arr2]
  • 类似于 concat 的功能,但具有持久性(改变原始数组)的好处。
  • @robertmylne 扩展运算符显然不会修改原始数组,而是创建所有数组内容的新副本。
  • 顺便说一句,与将一个数组的内容复制到另一个数组的其他方法相比,这也快得惊人。
【解决方案4】:

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

或者你可以使用 ES6 的spread operator 特性:

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

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

【讨论】:

  • 这不是问题所要求的。问题是每次都创建一个新数组,而不是修改旧数组。
【解决方案5】:

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

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

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

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

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

newArray.push(...dataArray2)

【讨论】:

  • 也提到here at MDN作为“合并两个数组”的例子
【解决方案6】:

????????????????????????????????????????????????(原始问题) h2>

事实上,执行performance test at jsperf 并检查控制台中的一些内容。对于研究,使用the website irt.org。下面是所有这些源代码的集合,以及底部的示例函数。

╔═══════════════╦══════╦════════════════════════ ══════════╦═════════╦══════════╗
║ 方法 ║Concat║slice&push.apply ║ push.apply x2 ║ ForLoop ║Spread ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║ mOps/秒 ║179 ║104 ║ 76 ║ 81 ║28 ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║稀疏数组║YES! ║只有切片 ║ 没有 ║ Maybe2 ║no ║
║保持稀疏║║数组(第一个参数)║║║║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║ 支持 ║MSIE 4║MSIE 5.5 ║ MSIE 5.5 ║ MSIE 4 ║Edge 12 ║
║ (source) ║NNav 4║NNav 4.06 ║ NNav 4.06 ║ NNav 3 ║MSIE NNav ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║类似阵列的行为║no ║只有被推║ YES! ║ 是的! ║如果有║
║像一个数组 ║ ║array (第二个参数) ║ ║ ║iterator1║
╚═══════════════╩══════╩═════════════════╩════════ ═══════╩═════════╩══════════╝
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 循环,因为它既是性能第二高的方法,也是唯一保留稀疏数组的其他方法。下面,我们将快速回顾一下稀疏数组和类似数组的含义,以消除混淆。

??? ??????

起初,有些人可能认为这是侥幸,浏览器供应商最终会设法优化 Array.prototype.push,使其速度足以击败 Array.prototype.concat。错误的! Array.prototype.concat 总是会更快(至少在原则上),因为它是对数据的简单复制粘贴。下面是一个简化的 32 位数组实现可能是什么样子的直观图(请注意实际实现要复杂得多)

字节║数据在这里
═════╬═══════════
0x00 ║ int nonNumericPropertiesLength = 0x00000000
0x01 ║ 同上
0x02 ║ 同上
0x03 ║ 同上
0x00 ║ 整数长度 = 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 每次都必须额外分配更多内存,并且(对于某些浏览器实现)甚至可能重新计算一些位置查找数据以实现稀疏性。

另一种思考方式是这样。源阵列一是一大叠装订在一起的纸张。源阵二也是另外一大摞论文。你会不会更快

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

在上面的类比中,选项#1 代表 Array.prototype.concat 而#2 代表 Array.prototype.push.apply。让我们用一个类似的 JSperf 来测试它,不同之处仅在于它测试的是稀疏数组上的方法,而不是实心数组。可以找到right here

因此,我认为这个特定用例的性能未来不在于 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 等数组的方法会发生什么。

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}() ));
  • 注意:出于性能考虑,对函数参数调用 slice 是不好的做法。

观察使用将类似数组的对象强制转换为 concat 等数组的方法会发生什么。

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

【讨论】:

    【解决方案7】:
    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)
    

    这能解决你的问题吗?

    【讨论】:

      【解决方案8】:

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

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

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

      【讨论】:

      • 如果你看一下how babel converts it,你会发现它不应该比使用Array.push.apply技术慢。
      • @JackGiffin 我只是指 Ryan 提到的,他不知道它在内部是如何工作的以及对性能有什么影响,我实际上并不是在建议这种方法。无论如何,你的回答做得很好,很好的研究,知道这些细节总是好的。
      【解决方案9】:

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

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

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

      【讨论】:

      • 这个问题是在寻找一种每次都创建一个新数组的方法,而不是修改一个现有的数组。
      【解决方案10】:

      这是 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)

      【讨论】:

        【解决方案11】:

        如果想修改原数组,可以传播推送:

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

        【讨论】:

          【解决方案12】:

          有很多关于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]

          【讨论】:

            【解决方案13】:

            我们有两个数组 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' ]
            

            【讨论】:

            • 请不要只发布代码作为答案。解释代码的作用以及它如何解决问题。
            【解决方案14】:

            性能

            我分析了当前的解决方案并提出了 2 个新的(F 和 G 在详细部分中介绍)一个对于中小型阵列来说非常快

            今天 2020.11.13 我在 Chrome v86、Safari v13.1.2 和 Firefox v82 上针对所选解决方案在 MacOs HighSierra 10.13.6 上执行测试

            结果

            适用于所有浏览器

            • 基于while-pop-unshift (F,G) 的解决方案在所有中小型数组的浏览器上(非常)最快。对于具有 50000 个元素的数组,此解决方案在 Chrome 上会减慢速度
            • 数组 500000 中断的解决方案 C、D:“RangeError:超出最大调用堆栈大小
            • 解决方案 (E) 最慢

            详情

            我执行 2 个测试用例:

            • 当数组有 10 个元素时 - 你可以运行它HERE
            • 当数组有 10k 个元素时 - 你可以运行它HERE

            下面的 sn-p 显示了解决方案之间的差异 A, B, C, D, E, F(我的), G(我的), H, I

            // https://stackoverflow.com/a/4156145/860099
            function A(a,b) {
              return a.concat(b);
            }
            
            // https://stackoverflow.com/a/38107399/860099 
            function B(a,b) {
              return [...a, ...b];
            }
            
            // https://stackoverflow.com/a/32511679/860099
            function C(a,b) {
              return (a.push(...b), a);
            }
            
            // https://stackoverflow.com/a/4156156/860099
            function D(a,b) {
                Array.prototype.push.apply(a, b);
                return a;
            }
            
            // https://stackoverflow.com/a/60276098/860099
            function E(a,b) {
                return b.reduce((pre, cur) => [...pre, cur], a);
            }
            
            // my
            function F(a,b) {
                while(b.length) a.push(b.shift());
                return a;
            }
            
            // my
            function G(a,b) {
                while(a.length) b.unshift(a.pop());
                return b;
            }
            
            // https://stackoverflow.com/a/44087401/860099
            function H(a, b) {
                var len = b.length;
                var start = a.length;
                a.length = start + len;
                for (var i = 0; i < len; i++ , start++) {
                    a[start] = b[i];
                }
                return a;
            }
            
            // https://stackoverflow.com/a/51860949/860099
            function I(a, b){
               var oneLen = a.length, twoLen = b.length;
               var newArr = [], newLen = newArr.length = oneLen + twoLen;
               for (var i=0, tmp=a[0]; i !== oneLen; ++i) {
                    tmp = a[i];
                    if (tmp !== undefined || a.hasOwnProperty(i)) newArr[i] = tmp;
                }
                for (var two=0; i !== newLen; ++i, ++two) {
                    tmp = b[two];
                    if (tmp !== undefined || b.hasOwnProperty(two)) newArr[i] = tmp;
                }
                return newArr;
            }
            
            
            
            
            
            
            // ---------
            // TEST
            // ---------
            
            let a1=[1,2,3];
            let a2=[4,5,6];
            
            [A,B,C,D,E,F,G,H,I].forEach(f=> {
                console.log(`${f.name}: ${f([...a1],[...a2])}`)
            })

            以下是 chrome 的示例结果

            【讨论】:

              【解决方案15】:

              试试这个:

              var arrayA = [1, 2];
              var arrayB = [3, 4];
              var newArray = arrayB.reduce((pre, cur) => [...pre, ...cur], arrayA);
              console.log(newArray)
              

              【讨论】:

                【解决方案16】:
                    public static void main(String[] args) {
                    // TODO Auto-generated method stub
                    //Scanner sc=new Scanner(System.in);
                    
                    int[] ij= {1,4,222,455,111};
                    int[] ijk=Arrays.copyOf(ij,ij.length);
                    for(int i=0;i<ij.length;i++) {
                        System.out.print(i);
                    }
                    System.out.println(" ");
                    for(int i=0;i<ijk.length;i++) {
                        System.out.print(i);
                    }
                    
                    
                }
                

                输出: 01234 01234

                【讨论】:

                • 这是一个 JavaScript 问题,当您使用 Java 回答时。
                【解决方案17】:

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

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

                【讨论】:

                • 两者都非常兼容 IE
                【解决方案18】:

                Т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))}
                

                【讨论】:

                  猜你喜欢
                  • 2017-09-13
                  • 1970-01-01
                  • 2022-07-05
                  • 1970-01-01
                  • 2018-02-16
                  • 2014-08-24
                  • 2022-11-25
                  • 1970-01-01
                  • 2013-07-09
                  相关资源
                  最近更新 更多