在javascript中进行foreach时是否可以更改数组的值?


300

例:

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

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

alert(arr);

数组仍保留其原始值,是否可以通过迭代函数对数组元素进行写访问?



尝试地图(developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...):x=[2,3,4]; x=x.map(n=>n*2); // [4,6,8]
贾斯汀

Answers:


468

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

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

编辑 -如注释中所述,该.forEach()函数可以采用第二个参数,该参数将用作this每次对回调的调用中的值:

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

第二个例子说明了arr自己是this在回调中设置的。有人可能认为.forEach()调用中涉及的数组可能是的默认this,但无论出于何种原因,它都不是。thisundefined如果没有提供第二个参数。

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

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

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

等等。MDN链接


34
谢谢!ES6:arr.forEach((o,i,a)=> a [i] = myNewVal)
DiegoRBaquero

为什么part(甚至o在es6中)未定义?如何获得迭代值?
丹尼尔·马什金

@DaniilMashkin part将是undefined如果已undefined明确分配数组的元素。数组的“空”插槽(从未分配任何值的数组条目)被.forEach()大多数其他数组迭代方法跳过。
尖尖的

2
为了完整起见,.forEach()还需要第二个参数thisArg,您可以this在回调内部将其用作。注意:这是一个参数,而.forEach不是回调的参数。
全能骆驼Moha

3
要将this用作第二个参数传递.forEach(),您需要使用语法传递回调函数function(),因为使用ES6的arrow函数() => {}不会绑定上下文。
jpenna

131

让我们尝试使其简单并讨论其实际工作方式。它与变量类型和函数参数有关。

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

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

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

alert(arr);

首先,这里是您应该阅读Array.prototype.forEach()的地方:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

其次,让我们简要地谈谈JavaScript中的值类型。

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

例如: 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);

现在,如果array 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);

2
确实,很好的解释!对于... of,map,... for ... of的新方法,在这种情况下,与forEach的工作方式非常相似,否则扩展解释会更好。最终更改了原始数组(如forEach)缺少“索引”。ES5映射似乎也类似于forEach,也许只是从映射返回值的可能性从语法的角度来看比使用索引更清楚。
Christophe Vidal

1
很好的解释。谢谢。毕竟我听不清这句话:In functions, parameters are always passed by value. 第二个例子呢?
亚历克斯

@ 7sides,该语句描述函数参数将始终按值传递。因此,对于基元,它将是基元指向的值。对于对象,它将是对象指向的位置。这个w3schools页面有很好的解释。请参见“ 参数通过值传递对象通过引用传递”部分
戴夫

在函数中,参数始终按值传递。BAM。谢谢。+1
Radarbob

92

数组:[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 );


3
“ Array.prototype.map()修改原始数组,通过重新分配arr变量来实现。”。map()从不修改原始数组,它会创建一个新数组。重新分配变量不会更改原始对象。
vadkou

1
let arr1 = ["1", 2, 3, 4]; arr1.map(function(v) { return "foo"+ v; }); console.log( arr ); Array.prototype.map()永不修改原始数组,每次更改。
阿努帕姆·毛里雅

@AnupamMaurya根本不是真的。map可以肯定地改变其数组forEach,通常不会。
user4642212 '19

@SebastianSimon,感谢您的回复。我要添加的所有内容,forEach会覆盖数据,但是映射不能,它会创建一个新副本。
阿努帕姆·毛里雅

14

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

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

arr[index] = 'new value';


这取决于part是否复制它的类型-尽管您说对了,变量不是指针,而是值,这是正确的
Bergi 2012年

8
说“ JavaScript是按价值传递”是一种概括。JavaScript中有引用类型。值类型按值传递。
Alex Turpin 2012年

9
不能一概而论。完全正确和绝对的陈述。Javascript按值传递引用。Javascript总是按值传递。
hvgotcodes 2012年

1
@Bergi:不,类型无关紧要。所有值都在赋值时复制-JavaScript中唯一的值是基元和引用。基本体和引用都在赋值时被复制。
newacct 2012年

6
@Bergi仅仅因为一种语言有一个称为引用的东西,并不意味着它使用逐个引用。引用可以通过值传递,也可以通过值传递,即,将引用的值复制,并将该副本用作参数。
hvgotcodes 2012年


4

这是使用=>样式功能的类似答案:

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

也可以。


3

.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
如果要复制数组,请使用map()代替forEach()map()遍历源数组,并返回一个包含原始数组的[修改]副本的新数组:源数组保持不变。
Nicholas Carey 2014年

2

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

例如,如果要删除当前元素并将其放置在同一数组中的另一个索引位置,则可以轻松地做到这一点。如果将当前元素移动到上一个位置,则在下一次迭代中没有问题,您将获得与未执行任何操作相同的下一项。

考虑一下此代码,一旦索引计数到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继续,而是从它离开的地方继续。

因此,请记住这一点。


1

要完全添加或删除会改变索引的元素,通过扩展zhujy_8833建议slice()遍历一个副本,只需计算已删除或添加的元素数并相应地改变索引。例如,要删除元素:

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

注意:如果同时实现“之前”和“之后”插入,则代码应首先处理“之前”插入,否则其他方法将与预期不符


0

如果要覆盖,可以尝试一下

var newArray= [444,555,666];
var oldArray =[11,22,33];
oldArray.forEach((name, index) => oldArray [index] = newArray[index]);
console.log(newArray);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.