JavaScriptでforeachを行うときに配列の値を変更することは可能ですか?


300

例:

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

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

alert(arr);

配列はまだ元の値のままですが、反復関数から配列の要素に書き込みアクセスする方法はありますか?



map(developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…)を試してください:x=[2,3,4]; x=x.map(n=>n*2); // [4,6,8]
ジャスティン

回答:


468

コールバックには、要素、インデックス、および配列自体が渡されます。

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

編集 —コメントに記載されているように、.forEach()関数は2番目の引数を取ることができthis、コールバックへの各呼び出しでの値として使用されます。

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

この2番目の例は、コールバックのarrようthisに設定されていることを示しています。.forEach()呼び出しに含まれる配列はのデフォルト値であると考えるかもしれませんが、this何らかの理由でそうではありません。thisなりundefined、第2引数が提供されていない場合。

(注:thisコールバックが=>関数の場合、上記のものは適用されませんthis。そのような関数が呼び出されたときに何にもバインドされないためです。)

また、Arrayプロトタイプには同様のユーティリティのファミリー全体が提供されていることを覚えておくことも重要です。最適な解決策は単に別のツールを選択することであるかのように、Stackoverflowで1つの関数または別の関数について多くの質問が表示されることです。あなたが持っています:

  • forEach 配列のすべてのエントリで、またはすべてのエントリに対して処理を行うため。
  • filter 適格なエントリのみを含む新しい配列を生成するため。
  • map 既存の配列を変換して1対1の新しい配列を作成するため。
  • some 配列内の少なくとも1つの要素が何らかの説明に適合するかどうかを確認します。
  • every配列内のすべてのエントリが説明と一致するかどうかを確認ます。
  • find 配列の値を探す

等々。MDNリンク


34
ありがとう!ES6:arr.forEach((o、i、a)=> a [i] = myNewVal)
DiegoRBaquero 2017

なぜpart(またはoes6 でも)未定義ですか?反復値を取得する方法?
Daniil Mashkin、2018

@DaniilMashkin partundefined、配列の要素がundefined明示的に割り当てられている場合です。配列(値が割り当てられていない配列エントリ)の「空の」スロットは、.forEach()他のほとんどの配列反復メソッドによってスキップされます。
2018

2
完全を.forEach()期すために、2番目の引数も取ります。これは、コールバック内でthisArg使用できthisます。注:これ.forEachは、コールバックの引数ではなく、引数です。
Moha全能のラクダ

3
this渡された2番目の引数を.forEach()使用するには、構文を使用してコールバック関数を渡す必要がありますfunction()。これは、ES6の矢印関数を使用() => {}してもコンテキストがバインドされないためです。
jpenna

131

レッツトライそれをシンプルに保つ、それが実際に働いているか議論します。変数の型と関数のパラメーターに関係しています。

これが私たちが話しているあなたのコードです:

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の値型について簡単に説明します。

プリミティブ(未定義、null、文字列、ブール、数値)は実際の値を格納します。

例: var x = 5;

参照タイプ(カスタムオブジェクト)は、オブジェクトのメモリロケーションを格納します。

例: var xObj = { x : 5 };

3番目に、関数パラメーターのしくみ。

関数では、パラメーターは常に値で渡されます。

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

2
確かに、素晴らしい説明です!他の反復​​メソッドであるfor ... of、map、...の説明を拡張した方がよいでしょう。 「forEachのように」元の配列を最終的に変更するための「インデックス」がありません。また、ES5マップはforEachに似ているように見えますが、マップから値を返す可能性があるため、インデックスを使用するよりも構文の観点から物事が明確になります。
Christophe Vidal

1
素晴らしい説明。ありがとうございました。結局、私はこのステートメントを得ることができません:In functions, parameters are always passed by value. 2番目の例はどうですか?
アレックス・

@ 7sides、そのステートメントは、関数のパラメーターは常に値で渡されることを説明しています。したがって、プリミティブの場合、それはプリミティブが指す値になります。オブジェクトの場合は、オブジェクトが指している場所になります。このw3schoolsのページには、良い説明があります。「引数は値渡される」および「オブジェクトは参照で渡される」のセクションを参照してください。
Dave、

関数では、パラメーターは常に値で渡されます。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()元の配列を変更しないでください。
Anupam Maurya

@AnupamMauryaそれは全く真実ではありません。mapは間違いなくその配列を変更できますがforEach、一般的には変更しません。
user4642212

@SebastianSimon、返信ありがとうございます。追加したいのは、forEachはデータをオーバーライドしますが、マップはオーバーライドできません。新しいコピーが作成されます。
Anupam Maurya

14

ジャバスクリプトは、値によってパスされ、かつ本質的手段がpartあるコピー配列内の値の。

値を変更するには、ループで配列自体にアクセスします。

arr[index] = 'new value';


それpartはコピーされるかどうかのタイプに依存します-変数がポインターではなく値であることは正しいです
Bergi

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 14

2

Arrayオブジェクトのメソッドを使用すると、基本的なforループと比較してArrayコンテンツを変更できますが、これらのメソッドには1つの重要な機能がありません。実行時にインデックスを変更することはできません。

たとえば、現在の要素を削除して、同じ配列内の別のインデックス位置に配置する場合は、これを簡単に行うことができます。現在の要素を前の位置に移動すると、次の反復で問題がなく、何も実行しなかった場合と同じ次のアイテムが取得されます。

インデックスが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が残ります。

これを覚えておいてください。


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

注: 'before'と 'after'の両方の挿入を実装する場合、コードは最初に 'before'挿入を処理する必要があります。それ以外の方法は期待どおりに行われません。


0

オーバーライドしたい場合は、これを試すことができます

var newArray= [444,555,666];
var oldArray =[11,22,33];
oldArray.forEach((name, index) => oldArray [index] = newArray[index]);
console.log(newArray);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.