【问题标题】:Replace element at specific position in an array without mutating it替换数组中特定位置的元素而不改变它
【发布时间】:2016-10-29 20:46:43
【问题描述】:

如何在不改变数组的情况下完成以下操作:

let array = ['item1'];
console.log(array); // ['item1']
array[2] = 'item2'; // array is mutated
console.log(array); // ['item1', undefined, 'item2']

在上面的代码中,array 变量发生了变异。如何在不改变数组的情况下执行相同的操作?

【问题讨论】:

标签: javascript arrays ecmascript-6 immutability


【解决方案1】:

你可以使用Object.assign:

Object.assign([], array, {2: newItem});

【讨论】:

  • @DanPrince 不确定这些共享结构是如何实现的。但是 slice 复制了整个数组,所以这种情况也是 O(n)。
  • 也就是说,使用slice 复制数组可能会更快,因为首先复制length,因此可以进行预分配。 Object.assign(array.slice(), {2: newItem}); 是另一种可能。
  • 不确定这是否是原始发布者正在编写的程序中的一次性修复,或者最终结果将是跨多个设备和浏览器访问的应用程序但是...不支持@ 987654326@ 或android。您可能想在答案中添加免责声明并引用 polyfill。
  • @cemper93 它可以工作,你只需要使用这个特定的语法:Object.assign([], array, { [i]: newItem })
  • 不确定,但是当分配给 >= 比数组长度的索引时,这可能对引擎性能优化非常不利?
【解决方案2】:

快速方式

function replaceAt(array, index, value) {
  const ret = array.slice(0);
  ret[index] = value;
  return ret;
}

查看JSPerf(感谢@Bless

相关帖子:

【讨论】:

【解决方案3】:

您可以像这样简单地设置一个新数组:

const newItemArray = array.slice();

然后为您希望为其设置值的索引设置值。

newItemArray[position] = newItem

然后返回。中间索引下的值将具有undefined

或者明显的替代方案是:

Object.assign([], array, {<position_here>: newItem});

【讨论】:

    【解决方案4】:

    好吧,从技术上讲,这不会替换,因为您要更改的索引处没有项目。

    看看它是如何在 Clojure 中处理的——一种围绕不可变数据结构的规范实现而构建的语言。

    (assoc [1] 2 3)
    ;; IndexOutOfBoundsException
    

    它不仅会失败,还会崩溃。这些数据结构被设计为尽可能健壮,当您遇到此类错误时,通常不是因为您发现了边缘情况,而更有可能是您使用了错误的数据结构。

    如果您最终得到的是稀疏数组,那么请考虑使用对象或地图对其进行建模。

    let items = { 0: 1 };
    { ...items, 2: 3 };
    // => { 0: 1, 2: 3 }
    
    let items = new Map([ [0, 1] ]);
    items(2, 3);
    // => Map {0 => 1, 2 => 3}
    

    但是,Map 本质上是一种可变的数据结构,因此您需要将其替换为具有 Immutable.jsMori 之类的库的不可变变体。

    let items = Immutable.Map([ [0, 2] ]);
    items.set(2, 3);
    // => Immutable.Map {0 => 1, 2 => 3}
    
    let items = mori.hashMap();
    mori.assoc(items, 2, 3);
    // => mori.hashMap {0 => 1, 2 => 3}
    

    当然,想要使用 JavaScript 的数组可能有一个很好的理由,所以这里有一个很好的解决方案。

    function set(arr, index, val) {
      if(index < arr.length) {
        return [
          ...arr.slice(0, position),
          val,
          ...arr.slice(position + 1)
        ];
      } else {
        return [
          ...arr,
          ...Array(index - arr.length),
          val
        ];
      }
    }
    

    【讨论】:

      【解决方案5】:

      我想这样做:

      function update(array, newItem, atIndex) {
          return array.map((item, index) => index === atIndex ? newItem : item);
      }
      

      一般来说,Array-spread 操作会为您生成很少的临时数组,但 map 不会,因此它可以更快。也可以看看this discussion作为参考

      【讨论】:

      【解决方案6】:

      另一种方法是将扩展运算符与 slice as 一起使用

      let newVal = 33, position = 3;
      let arr = [1,2,3,4,5];
      let newArr = [...arr.slice(0,position - 1), newVal, ...arr.slice(position)];
      console.log(newArr); //logs [1, 2, 33, 4, 5]
      console.log(arr); //logs [1, 2, 3, 4, 5]

      【讨论】:

        【解决方案7】:

        这个怎么样:

        const newArray = [...array]; // make a copy of the original array
        newArray[2] = 'item2'; // mutate the copy
        

        我发现意图比这个单行更清楚:

        const newArray = Object.assign([...array], {2: 'item2'});
        

        【讨论】:

          【解决方案8】:

          var list1 = ['a','b','c'];
          var list2 = list1.slice();
          list2.splice(2, 0, "beta", "gamma");
          console.log(list1);
          console.log(list2);

          这是你想要的吗?

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-10-07
            • 2018-05-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多