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]


forEachRubyeachメソッド「配列の要素を先頭から順番に渡す」という意味で似ていてます。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が呼び出されました


関数は変数に格納できます。
だから関数を引数に取れことができます。

  • オブジェクトって何?

    「オブジェクトには、データを扱うためのプロパティとメソッドが含まれています。JavaC++C#などの言語では、これを『クラス』って呼んでますよね。プロパティやメソッドをまとめておいたもの、つまり他の言語のクラスに相当するものをJavaScriptではオブジェクトと呼んでいるのです」

www.atmarkit.co.jp

上記コードのコールバック関数

もう一度、ここにコードを載せます。

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

jsprimer.net

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

自分メモように書いたので、読みづらいところもあったと思いますが、見てくださりありがとうございました。

参考

developer.mozilla.org

rara-world.com

www.dab.hi-ho.ne.jp