【问题标题】:change values in array when doing foreach执行 foreach 时更改数组中的值
【发布时间】:2012-09-11 01:38:29
【问题描述】:

示例:

var arr = ["one","two","three"];

arr.forEach(function(part){
  part = "four";
  return "four";
})

alert(arr);

数组仍然是它的原始值,有什么方法可以从迭代函数中对数组元素进行写访问?

【问题讨论】:

标签: javascript arrays foreach pass-by-reference


【解决方案1】:

回调传递元素、索引和数组本身。

arr.forEach(function(part, index, theArray) {
  theArray[index] = "hello world";
});

edit — 如评论中所述,.forEach() 函数可以采用第二个参数,该参数将在每次调用回调时用作 this 的值:

arr.forEach(function(part, index) {
  this[index] = "hello world";
}, arr); // use arr as this

第二个示例显示arr 本身在回调中被设置为this。有人可能认为.forEach() 调用中涉及的数组可能是@987654329 的默认 值@,但无论出于何种原因,它都不是;如果没有提供第二个参数,this 将是 undefined

(注意:如果回调是 => 函数,则上述关于 this 的内容不适用,因为在调用此类函数时,this 永远不会绑定到任何东西。)

此外,重要的是要记住,Array 原型上提供了一整套类似的实用程序,并且 Stackoverflow 上会弹出许多关于一个或另一个函数的问题,因此最好的解决方案是简单地选择不同的工具。你有:

  • forEach 对数组中的每个条目进行操作;
  • filter 用于生成仅包含合格条目的新数组;
  • map 通过转换现有数组来创建一对一的新数组;
  • some 检查数组中的至少一个元素是否符合某个描述;
  • every 检查数组中的所有项是否与描述匹配;
  • find 在数组中查找值

等等。 MDN link

【讨论】:

  • 谢谢! ES6: arr.forEach((o, i, a) => a[i] = myNewVal)
  • 为什么 part(甚至 es6 中的 o)是未定义的?如何获得迭代值?
  • @DaniilMashkin part 将是 undefined 如果数组的元素已明确分配 undefined.forEach() 和大多数其他数组迭代方法会跳过数组的“空”槽(从未分配过任何值的数组条目)。
  • 为了完整起见,.forEach() 还接受第二个参数thisArg,您可以在回调中将其用作this。注意:这是.forEach 的参数,而不是回调的参数。
  • 要使用作为第二个参数传递给.forEach()this,您需要使用语法function() 传递回调函数,因为使用ES6 的箭头函数() => {} 不会绑定上下文。
【解决方案2】:

让我们尝试保持简单并讨论它的实际工作原理。它与变量类型和函数参数有关。

这是我们正在讨论的代码:

var arr = ["one","two","three"];

arr.forEach(function(part) {
  part = "four";
  return "four";
})

alert(arr);

首先,您应该在此处阅读有关 Array.prototype.forEach() 的内容:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

其次,我们简单谈谈 JavaScript 中的值类型。

基元(未定义、空、字符串、布尔值、数字)存储实际值。

例如:var x = 5;

引用类型(自定义对象)存储对象的内存位置。

例如:var xObj = { x : 5 };

第三,函数参数的工作原理。

在函数中,参数总是按值传递。

因为arr是一个字符串数组,所以它是一个原始对象的数组,也就是说它们是按值存储的。

所以对于上面的代码,这意味着每次 forEach() 迭代时,part 等于 arr[index] 的相同值,但不是同一个对象

part = "four"; 将更改 part 变量,但将保持 arr 不变。

以下代码将更改您想要的值:

var arr = ["one","two","three"];

arr.forEach(function(part, index) {
  arr[index] = "four";
});

alert(arr);

现在如果数组arr引用类型的数组,则以下代码将起作用,因为引用类型存储对象的内存位置而不是实际对象。

var arr = [{ num : "one" }, { num : "two"}, { num : "three"}];

arr.forEach(function(part, index) {
  // part and arr[index] point to the same object
  // so changing the object that part points to changes the object that arr[index] points to

  part.num = "four";
});

alert(arr[0].num);
alert(arr[1].num);
alert(arr[2].num);

以下说明您可以将part 更改为指向一个新对象,同时保留arr 中存储的对象:

var arr = [{ num : "one" }, { num : "two"}, { num : "three"}];

arr.forEach(function(part, index) {
  // the following will not change the object that arr[index] points to because part now points at a new object
  part = 5;
});

alert(arr[0].num);
alert(arr[1].num);
alert(arr[2].num);

【讨论】:

  • 确实,很好的解释!在其他迭代方法中扩展解释会更好,for...of, map, ... 对于这种情况,新的 for...of 的工作方式与 forEach 非常相似,除了“ index" 最终改变原始数组(如 forEach 中)而丢失。 ES5 map 似乎也类似于 forEach,也许只是从 map 的返回值的可能性使得从语法的角度来看比使用索引更清晰。
  • 很好的解释。谢谢你。毕竟我不能得到这样的说法:In functions, parameters are always passed by value.第二个例子呢?
  • @7sides,该语句描述了函数参数将始终按值传递。所以对于原语,它将是原语指向的值。对于对象,它将是对象指向的位置。 this w3schools page 有一个很好的解释。请参阅参数通过值传递对象通过引用传递部分。
  • 在函数中,参数总是按值传递。 BAM。谢谢。 +1
【解决方案3】:

数组:[1, 2, 3, 4]
结果:["foo1", "foo2", "foo3", "foo4"]

Array.prototype.map()保留原数组

const originalArr = ["Iron", "Super", "Ant", "Aqua"];
const modifiedArr = originalArr.map(name => `${name}man`);

console.log( "Original: %s", originalArr );
console.log( "Modified: %s", modifiedArr );

Array.prototype.forEach()覆盖原始数组

const originalArr = ["Iron", "Super", "Ant", "Aqua"];
originalArr.forEach((name, index) => originalArr[index] = `${name}man`);

console.log( "Overridden: %s", originalArr );

【讨论】:

  • "Array.prototype.map() 修改原始数组,通过重新分配 arr 变量实现" .map() 永远不会修改原始数组,它会创建一个新数组。变量的重新赋值不会改变原来的对象。
  • let arr1 = ["1", 2, 3, 4]; arr1.map(function(v) { return "foo"+ v; }); console.log( arr ); Array.prototype.map() 从不修改原始数组,forEach 变异。
  • @AnupamMaurya 这根本不是真的。 map 绝对可以改变它的数组,而forEach 通常不会。
  • @SebastianSimon,感谢您的回复。我要添加的所有内容,forEach 都会覆盖数据,但地图不能,它会创建一个新副本。
  • 比别人清楚
【解决方案4】:

Javascript 是按值传递的,这实质上意味着part 是数组中值的副本

要更改值,请在循环中访问数组本身。

arr[index] = 'new value';

【讨论】:

  • 是否被复制取决于part的类型——虽然你说得对,变量不是指针,而是值
  • 说“JavaScript 是按值传递”是粗略的概括。 JavaScript 中有引用类型。值类型是按值传递的。
  • 不是粗略的概括。完全正确和绝对的陈述。 Javascript 按值传递引用。 Javascript 总是按值传递。
  • @Bergi:不,类型无关紧要。所有值都在赋值时被复制——JavaScript 中唯一的值是原语和引用。原语和引用都在赋值时被复制。
  • @Bergi 仅仅因为一种语言有一个叫做引用的东西,并不意味着它使用引用传递。引用可以(并且是)按值传递,即复制引用的值并将该副本用作参数。
【解决方案5】:

这是使用=> 样式函数的类似答案:

var data = [1,2,3,4];
data.forEach( (item, i, self) => self[i] = item + 10 );

给出结果:

[11,12,13,14]

self 参数对于箭头样式函数并不是绝对必要的,所以

data.forEach( (item,i) => data[i] = item + 10);

也可以。

【讨论】:

    【解决方案6】:

    用数组的索引替换它。

    array[index] = new_value;
    

    【讨论】:

      【解决方案7】:

      .forEach 函数可以有一个回调函数(eachelement, elementIndex) 所以基本上你需要做的是:

      arr.forEach(function(element,index){
          arr[index] = "four";   //set the value  
      });
      console.log(arr); //the array has been overwritten.
      

      或者如果您想保留原始数组,您可以在执行上述过程之前对其进行复制。 要制作副本,您可以使用:

      var copy = arr.slice();
      

      【讨论】:

      • 如果要复制数组,请使用map() 而不是forEach()map() 遍历源数组并返回一个新数组,其中包含原始的 [修改] 副本:源数组保持不变。
      【解决方案8】:

      要完全添加或删除会改变索引的元素,通过扩展 slice() 的 zhujy_8833 建议来迭代副本,只需计算您已经删除或添加的元素的数量并相应地更改索引。例如,要删除元素:

      let values = ["A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8"];
      let count = 0;
      values.slice().forEach((value, index) => {
          if (value === "A2" || value === "A5") {
              values.splice(index - count++, 1);
          };
      });
      console.log(values);
      
      // Expected: [ 'A0', 'A1', 'A3', 'A4', 'A6', 'A7', 'A8' ]
      

      在之前插入元素:

      if (value === "A0" || value === "A6" || value === "A8") {
          values.splice(index - count--, 0, 'newVal');
      };
      
      // Expected: ['newVal', A0, 'A1', 'A2', 'A3', 'A4', 'A5', 'newVal', 'A6', 'A7', 'newVal', 'A8' ]
      

      在之后插入元素:

      if (value === "A0" || value === "A6" || value === "A8") {
          values.splice(index - --count, 0, 'newVal');
      };
      
      // Expected: ['A0', 'newVal', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'newVal', 'A7', 'A8', 'newVal']
      

      替换元素:

      if (value === "A3" || value === "A4" || value === "A7") {
          values.splice(index, 1, 'newVal');
      };
      
      // Expected: [ 'A0', 'A1', 'A2', 'newVal', 'newVal', 'A5', 'A6', 'newVal', 'A8' ]
      

      注意:如果同时实现 'before' 和 'after' 插入,代码应该首先处理 'before' 插入,否则将不会像预期的那样

      【讨论】:

        【解决方案9】:

        使用 Array 对象方法可以修改 Array 内容,但与基本的 for 循环相比,这些方法缺少一项重要功能。运行时不能修改索引。

        例如,如果您将删除当前元素并将其放置到同一数组中的另一个索引位置,您可以轻松地做到这一点。如果您将当前元素移动到之前的位置,那么在下一次迭代中您将获得相同的下一项,就好像您什么都没做一样。

        考虑一下这段代码,一旦索引计数达到 5,我们将索引位置 5 处的项目移动到索引位置 2。

        var ar = [0,1,2,3,4,5,6,7,8,9];
        ar.forEach((e,i,a) => {
        i == 5 && a.splice(2,0,a.splice(i,1)[0])
        console.log(i,e);
        }); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 5 5 - 6 6 - 7 7 - 8 8 - 9 9
        

        但是,如果我们将当前元素移动到当前索引位置之外的某个位置,事情就会变得有些混乱。然后下一个项目将移动到已移动项目的位置,在下一次迭代中我们将无法看到或评估它。

        考虑一下这段代码,一旦索引计数达到 5,我们将索引位置 5 处的项目移动到索引位置 7。

        var a = [0,1,2,3,4,5,6,7,8,9];
        a.forEach((e,i,a) => {
        i == 5 && a.splice(7,0,a.splice(i,1)[0])
        console.log(i,e);
        }); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 5 5 - 6 7 - 7 5 - 8 8 - 9 9
        

        所以我们从未在循环中遇到过 6。通常在 for 循环中,当您将数组项向前移动时,您应该减少索引值,以便您的索引在下一次运行中保持在相同的位置,并且您仍然可以评估移动到已删除项的位置的项。这对于数组方法是不可能的。您不能更改索引。检查以下代码

        var a = [0,1,2,3,4,5,6,7,8,9];
        a.forEach((e,i,a) => {
        i == 5 && (a.splice(7,0,a.splice(i,1)[0]), i--);
        console.log(i,e);
        }); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 4 5 - 6 7 - 7 5 - 8 8 - 9 9
        

        正如你所见,当我们减少 i 时,它不会从 5 继续,而是从 6 开始。

        所以请记住这一点。

        【讨论】:

          【解决方案10】:

          如果你想覆盖,你可以试试这个

          var newArray= [444,555,666];
          var oldArray =[11,22,33];
          oldArray.forEach((name, index) => oldArray [index] = newArray[index]);
          console.log(newArray);
          

          【讨论】:

            【解决方案11】:

            如果你想删除项目

            如果您需要进行的更改是从列表中完全删除一个或多个项目,则使用for 循环并向后循环通过循环会更安全。

                for (let i = myArray.length - 1; i >= 0; i--)
                {
                    const item = myArray[i];
            
                    if (...) 
                    {
                       // remove item
                       // https://stackoverflow.com/questions/5767325/how-can-i-remove-a-specific-item-from-an-array?rq=1
                    }
                };
            

            倒退意味着每个项目的数组永远不会改变。如果您要通过循环前进并删除item[3],那么item[4] 现在是新的item[3],这不会让任何事情变得更容易。你不会有这个问题倒退。

            这当然是一个不使用 foreach 的解决方案,但重要的是要记住,“老派”方法通常是最好的。如果出于某种原因您需要从循环中break;(并且有很多项),那么 for 循环会更有效,因为您无法跳出 for 循环。

            【讨论】:

              猜你喜欢
              • 2014-11-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-02-08
              相关资源
              最近更新 更多