【问题标题】:How to duplicate elements in a js array without creating "dependent elements"?如何在不创建“依赖元素”的情况下复制 js 数组中的元素?
【发布时间】:2019-05-27 17:36:45
【问题描述】:

我正在尝试修改数组中的单个元素,该数组的元素先前已重复 n 次。为了执行数组复制,我只依赖于duplicateElements(array, times)from this 帖子的自定义函数(请参阅@Bamieh 答案)。如下例所示,问题是我无法在不修改其他元素的情况下修改数组中的单个元素:

function duplicateElements(array, times) {
  return array.reduce((res, current) => {
    return res.concat(Array(times).fill(current));
  }, []);
}

var myvar = duplicateElements([{ a: 1 }, { a: 2 }], 2);
myvar[0].a = 3;

console.log(myvar);
// (4) [{…}, {…}, {…}, {…}]
// 0: {a: 3}
// 1: {a: 3}
// 2: {a: 2}
// 3: {a: 2}
// length: 4

如您所见,myvar[1].a 也被修改了,尽管这不是故意的。我怎样才能避免这个问题?

【问题讨论】:

  • 如果您的对象可以有多个嵌套级别,您将不得不搜索对象的深度克隆,因此您可以执行以下操作:res.concat(Array(times).fill(clone(current)));。否则,您可以使用spread syntax,例如:res.concat(Array(times).fill({...current}))
  • @Shidersz 我从 lodash 尝试了_.cloneDeep(current),但没有成功。
  • 它怎么没用,用你试过的代码更新,我刚刚从Lodash读到了这个方法,只是出于好奇,它在我做的一些测试中运行良好。如果你想使用Lodash,我可以给你举个例子。

标签: javascript arrays


【解决方案1】:

问题是您将引用传递给 Array(times).fill(current) 中的原始对象。

在这种情况下,第一个 {a:2} 的两个副本是原始副本的相同副本(它们引用内存中的相同空间),因此如果您更改一个,它们两个将随着它们引用内存中的相同对象。

您必须创建一个深度克隆函数,或者将对象传播到一个新函数中。您可以更改原始函数以使用如下对象和原语:

function duplicateElements(elementsArray, times) {
  //Make a new placeholder array
  var newArray = [];
  //Loop the array of elements you want to duplicate
  for (let index = 0; index < elementsArray.length; index++) {
    //Current element of the array of element
    var currentElement = elementsArray[index];
    //Current type of the element to check if it is an object or not
    var currentType = typeof currentElement
    //Loop over the times you want to copy the element
    for (let index = 0; index < times; index++) {
      //If the new element is not an object
      if (currentType !== "object" && currentType){
        //append the element
        newArray.push(currentElement)
        //if it is an Object
      } else if (currentType === "object" && currentType){
        //append an spreaded new Object https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
        newArray.push({...currentElement})
      }
    }
  }
  return newArray;
}

这不是执行此操作的最佳方法,但我认为您可能是 javascript 新手,最好在使用更多 Array 功能之前学习循环的旧方法(作为 Jonas Wilms 的回答,这也是一个很好的答案)。
我会推荐 javascript.info 和 eloquent javascript 来了解更多关于语言的信息

【讨论】:

    【解决方案2】:

    Array.fill 文档中指定的主要原因是在处理对象时它将通过引用复制:

    当 fill 被传递一个 object 时,它会复制 reference 并填充 对该对象的引用的数组。

    使用 lodash(和 _.cloneDeep)是这样的一行:

    let dubFn = (arr, t=1) => 
      _.concat(arr, _.flatMap(_.times(t, 0), x => _.cloneDeep(arr)))
    
    let r1 = dubFn([{a:1},{b:3}])            // no parameter would mean just 1 dub
    let r2 = dubFn([{a:1},{b:3},5,[1]], 2)  // 2 dublicates
    
    r1[0].a = 3
    r2[0].a = 3
    
    console.log(r1)
    console.log(r2)
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"&gt;&lt;/script&gt;

    请注意,这现在适用于 数组/对象和基元

    这个想法是使用_.concat 返回输入数组的新连接版本,其中包含几个函数的组合,这些函数最终返回一个克隆对象数组。在这种情况下,我们使用_.times 返回一个包含t 元素的数组,然后对于每个元素,我们用数组的deep clone 替换。需要_.flatMap 来展平最终结果,因为在_.times 调用之后我们最终得到了数组数组。

    使用 ES6,您可以执行以下操作:

    let dubElements = (arr, t) => 
      [...arr, ...new Array(t).fill().flatMap(x => arr.map(y => ({...y})))]
    
    let r1 = dubElements([{a:1},{b:3}])
    let r2 = dubElements([{a:1},{b:3}],2)
    
    r1[0].a = 3
    r2[0].a = 3
    
    console.log(r1)
    console.log(r2)

    我们通过扩展运算符连接数组,并使用new Array(t) 创建新的重复数组,并确保我们在这种情况下将fillundefined 结合起来,然后flatMap 得到结果(我们映射再次通过扩展运算符通过clone

    请注意,这适用于for your use case specifically。如果你想让它更通用,你必须在最后一个 map 函数等中扩展更多。

    如果你想保持元素的顺序,你可以这样做:

    let dubElements = (arr, t=1) => {
      let _result = []	
      arr.forEach(x => {
        for(let i=0; i<t+1; i++) {
          _result.push({...x})
        }
      })
      return _result
    }
    
    let result = dubElements([{a:1},{b:3}],2)
    
    result[0].a = 3
    
    console.log(result)

    【讨论】:

    • 有没有办法保存元素的顺序(例如,[“a”,“a”,“b”,“b”],而不是[“a”,“b”, "a", "b"])
    • 用保留顺序的方法更新了答案。
    【解决方案3】:

    替换

     Array(times).fill(current)
    

    这会将一个对current 的引用多次添加到数组中:

      Array.from({ length: times }, () => ({...current }))
    

    这将浅层克隆current。请注意,代码将仅适用于对象,而不适用于原语。


    我愿意:

     const duplicateElements = (array, length)  => 
        array.flatMap(current => Array.from({ length }, () => ({ ...current }));
    

    【讨论】:

    • 您的自定义函数 (const...) 与我的示例完美配合,但不适用于 duplicateElements(["aa", "bb"], 2),预期结果应为 ["aa", "aa", "bb", "bb"]。有没有办法让它在这两种情况下都起作用?
    • @mat 你需要一些方法来深度克隆,如果你搜索它,你会找到一些实现。
    猜你喜欢
    • 2019-08-13
    • 2020-01-05
    • 2019-03-03
    • 2021-09-28
    • 1970-01-01
    • 2016-01-23
    • 1970-01-01
    • 2021-12-03
    • 1970-01-01
    相关资源
    最近更新 更多