执行 foreach 时更改数组中的值

change values in array when doing foreach

提问人:rsk82 提问时间:9/19/2012 最后编辑:Roko C. Buljanrsk82 更新时间:1/7/2023 访问量:674845

问:

例:

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

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

alert(arr);

数组仍然具有其原始值,有没有办法从迭代函数中写入数组的元素?

JavaScript 数组 foreach 传递引用

评论

1赞 Pacerier 1/18/2016
相关新闻: stackoverflow.com/q/6081868/632951
5赞 Justin 5/25/2020
尝试 map(developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...):x=[2,3,4]; x=x.map(n=>n*2); // [4,6,8]

答:

17赞 hvgotcodes 9/19/2012 #1

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

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

arr[index] = 'new value';

评论

1赞 Bergi 9/19/2012
这取决于它是否被复制的类型 - 尽管你是对的,变量不是指针,而是值part
8赞 Alex Turpin 9/19/2012
说“JavaScript 是按值传递的”是一种粗略的概括。JavaScript 中有引用类型。值类型按值传递。
12赞 hvgotcodes 9/19/2012
不是粗略的概括。一个完全正确和绝对的陈述。Javascript 按值传递引用。Javascript 总是按值传递的。
1赞 newacct 9/19/2012
@Bergi:不,类型无关紧要。所有值都是在赋值时复制的——JavaScript 中唯一的值是基元和引用。基元和参照都是在赋值时复制的。
7赞 hvgotcodes 9/19/2012
@Bergi仅仅因为一种语言有一种叫做引用的东西,并不意味着它使用逐个引用。引用可以(并且是)按值传递,即引用的值被复制,并且该副本被用作参数。
721赞 Pointy 9/19/2012 #2

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

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

第二个示例显示自己被设置为在回调中。有人可能会认为调用中涉及的数组可能是 的默认值,但无论出于何种原因,它都不是; 如果未提供第二个参数,则为。arrthis.forEach()thisthisundefined

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

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

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

等等。MDN 链接

评论

69赞 DiegoRBaquero 7/13/2017
谢谢!ES6: arr.forEach((o, i, a) => a[i] = myNewVal)
6赞 Moha the almighty camel 11/13/2018
为了完整起见,还需要第二个参数,您可以将其用作回调。注意:这是一个参数,而不是回调的参数。.forEach()thisArgthis.forEach
4赞 jpenna 1/30/2019
要使用 pass as second 参数 ,您需要使用语法传递回调函数,因为使用 ES6 的箭头函数不会绑定上下文。this.forEach()function()() => {}
1赞 Dennis 10/19/2021
包含第三个参数有什么意义,;你不能这样做吗theArrayarr[index] = "hello world";
1赞 Pointy 10/19/2021
@Dennis 是的,如果涉及的数组位于本地范围内。在某些情况下,显式传入引用很有用。诚然,我很少发现有必要这样做。然而,这就是 API 的工作方式。
3赞 Asif Alamgir 9/19/2012 #3

将其替换为数组的索引。

array[index] = new_value;
5赞 zhujy_8833 9/19/2012 #4

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

评论

3赞 Nicholas Carey 12/3/2014
如果要创建数组的副本,请使用 代替 。 遍历源数组并返回一个新数组,其中包含原始数组的 [修改] 副本:源数组保持不变。map()forEach()map()
161赞 Dave 7/9/2015 #5

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

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

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 中的值类型。

基元(undefined、null、String、Boolean、Number)存储实际值。

前任:var x = 5;

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

前任:var xObj = { x : 5 };

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

在函数中,参数始终按值传递。

因为是一个字符串数组,所以它是一个原始对象的数组,这意味着它们是按值存储的。arr

因此,对于上面的代码,这意味着每次 forEach() 迭代时,都等于与 相同的值,但不是同一个对象partarr[index]

part = "four";将更改变量,但将单独保留。partarr

以下代码将更改所需的值:

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

下面演示了可以更改为指向新对象,同时将对象单独存储在:partarr

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

评论

2赞 Christophe Vidal 11/15/2015
确实,很好的解释!在其他迭代方法中扩展解释会更好,因为......的,地图,...新的...在这种情况下,的工作方式与 forEach 非常相似,只是缺少“索引”以最终更改原始数组(如 forEach 中)。此外,ES5 映射似乎与 forEach 类似,也许只是从映射返回值的可能性从语法角度来看比使用索引更清晰。
1赞 Alex 5/20/2016
很好的解释。谢谢。毕竟我无法得到这个陈述:第二个例子呢?In functions, parameters are always passed by value.
0赞 Dave 5/21/2016
@7sides,该语句描述了函数参数将始终按值传递。因此,对于基元来说,它将是基元指向的值。对于对象,它将是对象指向的位置。这个 W3schools 页面有一个很好的解释。请参阅“参数按值传递”和“对象按引用传递”部分。
0赞 radarbob 11/24/2017
在函数中,参数始终按值传递。砰。感谢。+1
2赞 Redu 5/16/2016 #6

使用 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

正如你所看到的,当我们递减时,它不会从 5 继续,而是从它离开的地方继续 6。i

所以请记住这一点。

154赞 Roko C. Buljan 7/8/2016 #7

数组: 结果:
[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.map()覆盖原始数组

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

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

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

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

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

评论

5赞 vadkou 2/19/2019
“Array.prototype.map() 修改原始数组,通过重新分配 arr 变量来实现” .map() 从不修改原始数组,它会创建一个新数组。变量的重新赋值不会更改原始对象。
1赞 Anupam Maurya 4/17/2019
let arr1 = ["1", 2, 3, 4]; arr1.map(function(v) { return "foo"+ v; }); console.log( arr );Array.prototype.map() 从不修改原始数组,forEach mutate。
0赞 Sebastian Simon 10/15/2019
@AnupamMaurya 这根本不是真的。 绝对可以改变它的数组,而且一般来说,不会。mapforEach
2赞 Anupam Maurya 10/16/2019
@SebastianSimon,感谢您的回复。我只想补充一下,forEach 覆盖了数据,但地图不能,它会创建一个新副本。
1赞 Yang Wang 7/9/2020
比别人清楚
3赞 Leigh Mathieson 11/19/2018 #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”插入,否则将不符合预期

12赞 J. Peterson 2/28/2019 #9

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

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

也有效。

1赞 Shuhad zaman 5/12/2020 #10

如果要覆盖,可以尝试此操作

var newArray= [444,555,666];
var oldArray =[11,22,33];
oldArray.forEach((name, index) => oldArray [index] = newArray[index]);
console.log(newArray);
2赞 Simon_Weaver 8/27/2021 #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 的解决方案,但重要的是要记住,“老派”方法通常是最好的。如果出于某种原因你需要从循环中(并且有不止几个项目),那么 for 循环更有效,因为你不能跳出 for 循环。break;