4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【JavaScript初心者向け】forループを配列メソッド(map等)に置き換える

Last updated at Posted at 2023-03-11

概要

JavaScriptの配列操作処理を、for文、for...of文、配列メソッドのそれぞれで紹介します。

詳細

配列の要素に対して処理を行う際、一般的には以下のいずれかで処理することが多いです。

  1. indexを使用したfor文
  2. for...of文(C#で言うところのforeach)
  3. 配列メソッド(mapやforEachなど)

1が一番古典的な記載方法で、2、3と記述がシンプルになっていきます。
1から2はわりとすんなり受け入れてくれる人が多いのですが、3に移行しようとすると拒否反応を起こす人が周りに多いので、布教用にまとめておきます。

今回紹介するのは一部のメソッドに限ります。
他にも便利なメソッドは沢山あるため、気になる方はMDNを覗いてみてください。

配列を他の配列に変換する(map)

要素数を変更せずに配列の各要素を別の形に変更する場合。
例では、配列の各要素を3倍にしていきます。(2倍にするサンプルをよく見るので、今回は3倍にしてみました。)

for文

const befourArr = [1, 2, 3, 4, 5]
const afterArr = []
for(let i = 0; i < befourArr.length; i++) {
    const current = befourArr[i]
    const after = current * 3
    afterArr.push(after)
}
console.log(afterArr) // => (5) [3, 6, 9, 12, 15]

古典的な書き方です。

for...of文

const befourArr = [1, 2, 3, 4, 5]
const afterArr = []
for(const current of befourArr) {
    const after = current * 3
    afterArr.push(after)
}
console.log(afterArr) // => (5) [3, 6, 9, 12, 15]

処理がそこそこ減り、バグの原因になりやすいlengthやindexに対する処理がなくなりました。

配列メソッド

mapメソッドを使用します。(比較しやすさのために変数名は合わせています。)

const befourArr = [1, 2, 3, 4, 5]
const afterArr = befourArr.map(current => current * 3)
console.log(afterArr) // => (5) [3, 6, 9, 12, 15]

記述がめっちゃ減りました。
短さこそ正義。

何が何だかわからない人に

慣れていない人はおそらく、「current => current * 3って何?」ってなっているのではないかと思います。
この書き方はアロー関数といい、関数の宣言をシンプルにおこなったものになります。

この「current => current * 3」をfunction宣言の形で書くと、こうなります。

function triple(current) { //関数名を書かないとエラーになる為関数名を記載します。
    return current * 3
}

アロー関数に書き換えます。

(current) => {
    return current * 3
}

処理が1つの場合はreturnと中括弧{}が省略できます。

(current) => current * 3

引数が1つの場合は、()の省略ができます。

current => current * 3

こうして出来上がったのがcurrent => current * 3です。

基本的に、配列メソッドには「その配列の各要素に対して行いたい処理」を関数化し、引数として渡していくことになります。

配列を条件で絞り込みたい場合(filter)

要素数を変更せずに配列の各要素を別の形に変更する場合。
例では、偶数要素のみを抽出します。

for文

const befourArr = [1, 2, 3, 4, 5]
const afterArr = []
for(let i = 0; i < befourArr.length; i++) {
    const current = befourArr[i]
    if(current % 2 === 0) {
        afterArr.push(current)
    }
}
console.log(afterArr) // => (2) [2, 4]

for...of文

const befourArr = [1, 2, 3, 4, 5]
const afterArr = []
for(const current of befourArr) {
    if(current % 2 === 0) {
        afterArr.push(current)
    }
}
console.log(afterArr) // => (2) [2, 4]

配列メソッド

filterメソッドを使用します。

const befourArr = [1, 2, 3, 4, 5]
const afterArr = befourArr.filter(current => current % 2 === 0)
console.log(afterArr) // => (2) [2, 4]

配列内の集計を行いたい場合(reduce)

number型やstring型の変数一つに結果をまとめたい場合。
例では、配列の要素の合計を算出します。

for文

const arr = [1, 2, 3, 4, 5]
let summary = 0
for(let i = 0; i < arr.length; i++) {
    const current = befourArr[i]
    summary += current
}
console.log(summary) // => 15

for...of文

const arr = [1, 2, 3, 4, 5]
const summary = []
for(const current of arr) {
    summary += current
}
console.log(summary) // => 15

配列メソッド

reduceメソッドを使用します。

const arr = [1, 2, 3, 4, 5]
const summary = arr.reduce((sum, current) => sum + current)
console.log(summary) // => 15

配列内に特定条件の要素が存在するかチェック(some)

配列内の要素に1つでも条件を満たす要素が存在するかをチェックします。
例では、偶数要素が存在するかチェックします。

for文

const arr = [1, 2, 3, 4, 5]
let exists = false
for(let i = 0; i < arr.length; i++) {
    const current = arr[i]
    if(current % 2 === 0) {
        exists = true
        break
    }
}
console.log(exists) // => true

for...of文

const arr = [1, 2, 3, 4, 5]
let exists = false
for(const current of arr) {
    if(current % 2 === 0) {
        exists = true
        break
    }
}
console.log(exists) // => true

配列メソッド

everyメソッドを使用します。

const befourArr = [1, 2, 3, 4, 5]
const exists = befourArr.some(current => current % 2 === 0)
console.log(exists) // => true

配列内の要素が全て特定条件を満たすかチェック(every)

配列内の要素に1つでも条件を満たさない要素が存在するかをチェックします。(入力チェックなど)
例では、全ての要素が偶数かチェックします。

for文

const arr = [1, 2, 3, 4, 5]
let allEven = true
for(let i = 0; i < arr.length; i++) {
    const current = arr[i]
    if(current % 2 !== 0) {
        allEven = false
        break
    }
}
console.log(allEven) // => false

for...of文

const arr = [1, 2, 3, 4, 5]
let allEven = true
for(const current of arr) {
    if(current % 2 !== 0) {
        allEven = false
        break
    }
}
console.log(allEven) // => false

配列メソッド

someメソッドを使用します。

const befourArr = [1, 2, 3, 4, 5]
const allEven = befourArr.every(current => current % 2 === 0)
console.log(allEven) // => false

複合パターン

今まではシンプルな処理を紹介してきましたが、配列メソッドの本領が発揮されるのはこれらの条件が複合的になった場合です。

例として以下の処理を行います。

  • 配列に数値以外の要素がある場合は集計処理を行わない
  • 偶数のみで絞り込み
  • 配列の各要素を3倍に
  • 3倍した結果、下1桁が2の数値は除外
  • 合計を算出

for文

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let summary = 0
for(let i = 0; i < arr.length; i++) {
    const current = arr[i]
    //配列に数値以外の要素がある場合は集計処理を行わない
    if (typeof current !== "number" || Number.isNaN(current)) {
        return
    }
    //偶数のみで絞り込み
    if (current % 2 === 0) {
        //配列の各要素を3倍に
        const triple = current * 3
        //3倍した結果、下1桁が2の数値は除外
        if (triple % 10 !== 2) {
            //合計を算出
            summary += triple
        }
    }
}

console.log(summary) // => 78

もう少しわかりやすく書くこともできますが、素直に書けばこんな感じになると思います。
まだシンプルな仕様だとは思いますが、ちょっとごちゃごちゃしてますね。

ループ内の処理が複雑化すると、こんな問題が発生したりします。

  • 処理を追っていかないと、結局何をやりたいのかが分からない
  • 仕様追加があるたび、ループがもっと巨大化する。
  • 仕様が減ると、関係ない処理の書き換えが発生する
  • バグがあった場合にどこが原因なのか調査し辛い
  • 処理が増えるたびにスコープ内の変数が増える

for...of文

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let summary = 0
for(const current of arr) {
    //配列に数値以外の要素がある場合は集計処理を行わない
    if (typeof current !== "number" || Number.isNaN(current)) {
        return
    }
    //偶数のみで絞り込み
    if (current % 2 === 0) {
        //配列の各要素を3倍に
        const triple = current * 3
        //3倍した結果、下1桁が2の数値は除外
        if (triple % 10 !== 2) {
            //合計を算出
            summary += triple
        }
    }
}

console.log(summary) // => 78

ループの先頭2行が変わっただけで、他は同じです。

配列メソッド

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
//配列に数値以外の要素がある場合は集計処理を行わない
if (arr.some(n => n !== "number" || Number.isNaN(n))) {
	return
}

const summary = arr
    .filter(num => num % 2 === 0)    //偶数のみで絞り込み
	.map(num => num * 3)             //配列の各要素を3倍に
	.filter(num => num % 10 !== 2)   //3倍した結果、下1桁が2の数値は除外
	.reduce((sum, num) => sum + num) //合計を算出

console.log(summary) // => 78

ネストが無くなり、スッキリしました。
for、for...of文が抱えていた問題も、かなり解決したのではないでしょうか。

  • 処理を追っていかないと、結局何をやりたいのかが分からない

メソッドチェーンの戻り値をsummaryに代入しているので、集計処理ということが読み取れます。
関数名に各処理の目的が反映されているため、処理の流れ(絞り込み⇨変換⇨絞り込み⇨集計)もわかりやすいです。

  • 仕様追加があるたび、ループがもっと巨大化する。

シンプルな仕様であれば、チェーンが増えていくだけです。

  • 仕様が減ると、関係ない処理の書き換えが発生する

対象の行が減るだけです。

  • バグがあった場合にどこが原因なのか調査し辛い

reduce前の配列を確認し、要素の数が少なければfilterを確認、要素の中身がおかしければmapを確認すると、ある程度最初から原因を絞ることができます。
メソッドごとに都度配列が生成されるため、各処理ごとに実行結果の配列を確認することもできます。

  • 処理が増えるたびにスコープ内の変数が増える

各メソッド内だけで変数スコープが完結しているため、複数の変数を同時に意識する必要がないです。

まとめ

for文、for...of文、配列メソッドの比較を行いました。
かなり記述がスッキリするということがわかっていただけたのではないでしょうか。
メソッド1つ挟むだけで変換結果がスパーンと取れるようになるので、楽しく書けるのも配列メソッドの魅力の1つだと思います。
この記事で配列メソッドの魅力が少しでも伝わると嬉しいです。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?