forEach()メソッド(配列)
はじめに
これは「フィヨルドブートキャンプ Part2 Advent Calendar 2020」11日目の記事です!
adventar.org フィヨルドブートキャンプ Part 1 Advent Calendar 2020はこちら adventar.org
きっかけ
配列に対してループ処理したいなって思ったとき、どうしますか?
私はfor文
かfor...of文
が浮かびます。
forEach
なんてのもJSPrimerにあったかも...と頭に浮かんでもコールバック関数何それ状態で避けていました。
実際、過去に書いたプログラムのFizzBuzz問題、カレンダー問題などを見返してもfor文
かfor...of文
で書いていました。
しかし、使わずとも調べるときはサンプルコードでforEach
に出くわすもので、理解しなければ前に進めない......
そんなこんなで、forEach
周りを調べたので書くことにしました。
forEachメソッドについて
forEachメソッドとは
Array#forEachは配列の要素を先頭から順番にコールバック関数へ渡し、反復処理を行うメソッドです。 次のようにコールバック関数には要素, インデックス, 配列が引数として渡され、配列要素の先頭から順番に反復処理します。
const array = [1, 2, 3] array.forEach((currentValue, index, array) => { console.log(currentValue, index, array) }) // コンソールの出力 // 1, 0, [1, 2, 3] // 2, 1, [1, 2, 3] // 3, 2, [1, 2, 3]
forEach
はRubyのeach
メソッド「配列の要素を先頭から順番に渡す」という意味で似ていてます。Rubyはそれぞれ渡された要素をブロック内で処理します。JSはコールバック関数に引数「要素, インデックス, 配列」を渡して処理します。
じゃあ、コールバック関数とは何?
コールバック関数とは
コールバック関数は他の関数に引数として渡される関数で、外側の関数で何らかの処理やアクションを実行します。
function greeting(name) { alert('Hello ' + name) } function processUserInput(callback) { var name = prompt('Please enter your name.') callback(name) } processUserInput(greeting)
関数を関数の引数にするということ....どゆこと?
関数はオブジェクト
JavaScriptでは、関数は関数オブジェクトとも呼ばれ、オブジェクトの一種です。 関数はただのオブジェクトとは異なり、関数名に()をつけることで、関数としてまとめた処理を呼び出すことができます。
一方で、()をつけて呼び出されなければ、関数をオブジェクトとして参照できます。 また、関数はほかの値と同じように変数へ代入したり、関数の引数として渡すことが可能です。
次のコードでは、定義したfn関数をmyFunc変数へ代入してから、呼び出しています。
このように関数が値として扱えることを、ファーストクラスファンクション(第一級関数)と呼びます。
function fn() { console.log("fnが呼び出されました") } // 関数`fn`を`myFunc`変数に代入している const myFunc = fn myFunc() // => fnが呼び出されました
関数は変数に格納できます。
だから関数を引数に取れことができます。
- オブジェクトって何?
「オブジェクトには、データを扱うためのプロパティとメソッドが含まれています。JavaやC++、C#などの言語では、これを『クラス』って呼んでますよね。プロパティやメソッドをまとめておいたもの、つまり他の言語のクラスに相当するものをJavaScriptではオブジェクトと呼んでいるのです」
上記コードのコールバック関数
もう一度、ここにコードを載せます。
const array = [1, 2, 3] array.forEach((currentValue, index, array) => { console.log(currentValue, index, array) }) // コンソールの出力 // 1, 0, [1, 2, 3] // 2, 1, [1, 2, 3] // 3, 2, [1, 2, 3]
では、このコードのどこがコールバック関数かというと、ここではforEachの引数がコールバック関数になっている。
(currentValue, index, array) => { console.log(currentValue, index, array) }
これはアロー関数という関数の書き方で書いています。
// Arrow Functionを使った関数定義 const 関数名 = () => { // 関数を呼び出したときの処理 // ... return 関数の返す値; };
この書き方は関数名と定義して関数名()
で呼び出せるようにしているが、上記の場合forEachの引数でしか使わないので=より後の() => {}
という形になっている。
引数の省略
コールバック関数は1 つから 3 つの引数を受け付けます。
1つの場合は以下のようになります。
const array = [1, 2, 3] array.forEach(currentValue => { console.log(currentValue) }) // コンソールの出力 // 1 // 2 // 3
コールバック関数が1つの場合は引数の()を省略できます。これはアロー関数の省略記法です。アロー関数について詳しくはJSPrimerを見てください。
また、コールバック関数には匿名関数(アロー関数も匿名関数)の関数式でも書けます。関数式について詳しくはJSPrimerを見てください。
const array = [1, 2, 3] array.forEach(function (currentValue) { console.log(currentValue) }) // コンソールの出力 // 1 // 2 // 3
forEachの仕様
具体的にforEachを使って動作をみていきます。
配列に存在しない要素があるとき(疎らな配列)
const alpha = [1, 2, , , 3] alpha.forEach(element => { console.log(element) }) // コンソールの出力 // 1 // 2 // 3 for (let i = 0; i < alpha.length; i++) { console.log(alpha[i]) } // コンソールの出力 //1 //2 //undefined //undefined //3 for (const element of alpha) { console.log(element) } // コンソールの出力 //1 //2 //undefined //undefined //3
for文
、for...of文
についてはループと反復処理 · JavaScript Primer #jsprimer
forEach メソッドは存在しない要素に対しては何もしません。 for 文では存在しない要素に対しても同じように処理を行います。
空文字列('')、0、false、 undefined、 NaN、 null、 未定義の変数(variable)の配列のとき
(空文字列、0、false、 undefined、 NaN、 nullはfalsyな値)
let variable const alpha = [1, 2, '', 0, false, undefined, NaN, null, variable, 3] alpha.forEach(element => { console.log(element) }) // コンソールの出力 //1 //2 // //0 //false //undefined //NaN //null //undefined //3 for (let i = 0; i < alpha.length; i++) { console.log(alpha[i]) } // コンソールの出力 //1 //2 // //0 //false //undefined //NaN //null //undefined //3 for (const element of alpha) { console.log(element) } // コンソールの出力 //1 //2 // //0 //false //undefined //NaN //null //undefined //3
出力は全て同じで、未定義の変数はundefinedになる。
配列のループ中に要素が追加されたとき
const array = [1, 2, 3] array.forEach((currentValue, index, array) => { array.push(currentValue + 3) console.log(currentValue, index, array) }) console.log(array) // コンソールの出力 // 1 0 [ 1, 2, 3, 4 ] // 2 1 [ 1, 2, 3, 4, 5 ] // 3 2 [ 1, 2, 3, 4, 5, 6 ] // [ 1, 2, 3, 4, 5, 6 ]
コールバックの最初の実行前に、forEachによって設定された要素の範囲が処理されます。 forEachが呼び出された後に追加された要素は、callbackの対象にはなりません。無限ループはしない。
配列のループ中に要素が削除されたとき
const array2 = [1, 2, 3, 4, 5] array2.forEach((currentValue, index, array) => { array2.pop() console.log(currentValue, index, array) }) console.log(array2) // コンソールの出力 // 1 0 [ 1, 2, 3, 4 ] // 2 1 [ 1, 2, 3 ] // 3 2 [ 1, 2 ] // [ 1, 2 ]
配列の要素が削除されたときは、その渡す対象から外されます。
配列のループ中に要素が削除されたとき2
const words = ['one', 'two', 'three', 'four'] words.forEach(word => { console.log(word) if (word === 'two') { words.shift() } }) console.log(words) // コンソールの出力 // one // two // four // [ 'two', 'three', 'four' ]
値 two を持つ項目に達した時、配列全体の最初の項目(one)は削除され、すべての残りの項目が 1 つ上の位置に繰り上がります。four が配列の以前の位置、元threeがあった場所に来るため、three が飛ばされます。
forEach() は繰り返しの前に配列のコピーを生成しません。
forEachの第二引数(this)
thisArg :callback 内で this として使用する値
ローカルオブジェクトのthisを参照するためには,forEachの第2引数にthisを指定する。
forEachのthisはコンストラクタ関数を使うときが多いかな??(まだ勉強不足でthisを把握しきれていないためわかり次第追記します。)
function Counter() { this.sum = 0 this.count = 0 } Counter.prototype.add = function(array) { array.forEach((entry) => { this.sum += entry ++this.count }, this) // ^---- Note } const obj = new Counter() obj.add([2, 5, 9]) obj.count // 3 obj.sum // 16
おわりに
これだけ調べたので、forEachも使える気がきます。 しかし、forEachはカバー範囲が広すぎるので、もっと特化したメソッドが使えるか考えてから使おうと思います。 qiita.com
自分メモように書いたので、読みづらいところもあったと思いますが、見てくださりありがとうございました。