どこかで聞いたことあるようなタイトルで煽ってすいません^^;
でも誇張ではありません。
配列の中を繰り返すとき次のように長いfor文を書いて消耗していないでしょうか?
1 2 3 4 |
const items = [0, 1, 2, 3, 4]; for(let i = 0; i < items.length; i++) { console.log(items[i]); } |
配列の操作を行うコードはサービス開発でもよく出てきます。
長い「for」の1行目を毎回書いていると、次のようなデメリットがあります。
- タイピング数が多い
- let(var)のつけ忘れによるバグ
- 範囲指定のミスによるバグ(「<」or 「<=」)
今回の記事では上記3つを解消するために便利なArrayメソッドを3つ紹介とプロトタイプチェーンについて解説します。
- 「メソッド」は「関数」とほぼ同じ意味
厳密に言うと、メソッドはオブジェクトが持っている関数のことで、今回の場合は配列(Array)がオブジェクトとなります。
ここまでの説明を読んでいただいた方の中には次のように思われている方もいるのではないでしょうか?
新しいこと覚えなくても「for」でループ処理が出来るなら十分じゃない?
ただでさえ覚えること多いのに、「for」で出来ることを別のやり方で覚える必要あるの?
答えから言うと「forだけでも問題ない」です。
ただ、forEach、map、filterを使えるようになることで次のようなメリットがあります。
- タイピング量が減りバグの可能性を減らすことが出来る
- コールバック関数(高階関数)に慣れる
- プロトタイプチェーンを使ったプログラミングが出来るようになる
プログラミングの表現の幅が広がり、それがプログラミングスキル上達に繋がります。
「プロトタイプチェーン」については現段階では「プログラミングを楽にする書き方」と覚えていただければ大丈夫です。
今回のサンプルコードで、「プロトタイプチェーンってこんな便利なのか!」と思っていただけるはずです。
「コールバック関数(高階関数)」に関しては以前書いた以下の記事にまとめているので、そちらを参考にしていただけたらと思います。
- 開発が捗るArrayメソッド3選
- 必殺技「プロトタイプチェーン」
目次
開発が捗るArrayメソッド3選
今回は冒頭でも紹介した以下3つのArrayメソッドの使い方を解説します。
これら3つのメソッドは引数にコールバック関数を受け取るので、コールバック関数についてまだ理解していないという方は以下の記事を一度読んで頂くこのあとの解説の理解が深まるかと思います。
forEach
まずはforEachの説明からします。forEachがやることはforループがやることと基本同じですが、以下の点がforと異なります。
- forはループの開始の値の定義と終了の条件を記入する必要があるが、forEachは必要ない
- forはインクリメント(++)を使って開始時の値を増やしていくが、forEachは必要ない
上記2つの内容を読んでいただくとわかるかと思いますが、forEachはforでやらなければいけないことを色々と省略できるのです。
では実際にサンプルコードを使ってforを使ったコードとforEachを使ったコードの比較をしていきます。
forを使った場合
1 2 3 4 5 6 7 8 9 |
const items = ['item1', 'item2', 'item3']; const copy = []; // 開始時の値を定義(let i = 0) // 終了条件を定義(i < items.length) // 無限ループを起こさないために開始時の値を変更する(i++) for (let i = 0; i < items.length; i++) { copy.push(items[i]) } |
forEachを使った場合
1 2 3 4 5 6 7 8 9 |
const items = ['item1', 'item2', 'item3']; const copy = []; // コールバック関数を引数に受け取る // itemsの中の値をコールバック関数の第1引数であるitemにわたす // ループの条件を書かなくてもitem1~item3まで順番にループ処理される items.forEach(item => { copy.push(item) }); |
forEachでは開始時の値やループ条件を書かなくても良くなり、なおかつ配列のループを最初から最後まで必ず実行することが保証されます。
forを使って開始と終了の条件を自分で書くやり方の場合は、「開始時の値のミスやlet(var)の記述漏れ」、「終了条件の実装ミス」など入力ミスの可能性が出てきます。
そんな簡単なミスはしないと思われるかもしれませんが、人間はミスをする生き物であるため、こういった簡単なミスもすることもあります。
簡単なミスの可能性もなくすために「コード量」を少なくする意識が必要になってきます。
「forEach」や、このあと紹介する、「map」と「filter」はコード量を少なくするための手段の1つになります。
それでは残り2つのArrayメソッドも見ていきましょう。
map
「map」メソッドは配列の中身を加工して別の配列を作りたい時に便利です。
言葉で説明するだけだと理解が難しいので次のサンプルコードで、forとmapの比較をします。
forを使った場合
1 2 3 4 5 6 |
const numbers = [1, 2, 3]; const doubledNumbers = []; // numbersの中身を2倍にした値を格納する配列 for (let i = 0; i < numbers.length; i++) { doubledNumbers.push(numbers[i] * 2); } |
mapを使った場合
1 2 3 4 5 6 7 8 9 |
const numbers = [1, 2, 3]; // number(1, 2, 3)を2倍した値を新しい配列に格納して返す const doubledNumbers = numbers.map(number => { return number * 2; }); // 1行だけであればこのような書き方も出来る const doubledNumbers = numbers.map(number => number * 2); |
上に書いたforとmapを使ったサンプルコードを比較してどう思われたでしょうか?
特にmapの最後の1行を見て「たったの1行で書けるの?」って思われた方もいるかも知れません。
先程紹介した「forEach」ではforループと同じことをやるだけでしたが、「map」を使うと「forループ」+「新しい配列を作る」の2つの機能が1つのメソッドで実現できます。
filter
次に「filter」を見ていきましょう。
filterで行うことはエクセルのフィルタと同じように、条件にあったものだけを抽出する機能です。
では早速サンプルコードを使って比較します。
(「5より大きい値」だけを抽出してます。)
forを使った場合
1 2 3 4 5 6 7 8 9 10 |
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const numbersOfOver5 = []; // 5より大きい値を格納する配列 for (let i = 0; i < numbers.length; i++) { const number = numbers[i]; // numberが5より大きかったら配列(numbersOfOver5)に追加する if(number > 5) { numbersOfOver5.push(number) } } |
filterを使った場合
1 2 3 4 5 6 7 8 9 |
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // 「number > 5」の条件に当てはまる値だけ抽出した新しい配列を返す const numbersOfOver5 = numbers.filter(number => { return number > 5; }); // コールバック関数の中のコードが1行だけならこのような書き方も出来る const numbersOfOver5 = numbers.filter(number => number > 5); |
forを使ったコードを見るとループの中でifを使って条件分岐が必要となりコードがだいぶ長くなってしまいました。
それに比べてfilterを使ったコードを見ると「map」と同じようにコード量が一気に少なくなります。
必殺技「プロトタイプチェーン」
ここまで「forEach」「map」「filter」を説明してきましたが、実は「map」「filter」のように新しい配列を返すメソッドには「プロトタイプチェーン」と言われる必殺技が使えます。
(※forEachはループをするだけで配列を返さないので注意が必要です。)
プロトタイプチェーンを使ったサンプルコード
プロトタイプチェーンとは冒頭でも簡単に説明しましたが、「プログラミングを楽にする書き方」です。
「プロトタイプチェーンについてもっと詳しく知りたいよー!」という方は以下の記事が参考にすると概念が学べるかと思います。
次のサンプルコードで感覚的に「便利だ!」「使ってみたい!」と思っていただけるのと思うので、その状態で先程の記事を読むと理解が深まるかと思います。
では早速必殺技「プロトタイプチェーン」を使ったサンプルコードをforを使ったやり方と比較して見てみましょう。
forを使った場合
1 2 3 4 5 6 7 8 9 10 11 12 |
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // 配列numbersから5より大きい値だけを抽出して2倍にした値を格納する配列 const numbersOfOver5AndDoubled = []; for (let i = 0; i < numbers.length; i++) { const number = numbers[i]; // numberが5より大きかったら配列(numbersOfOver5)に追加する if(number > 5) { numbersOfOver5AndDoubled.push(number * 2); } } |
プロトタイプチェーンを使った場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // 「number > 5」の条件に当てはまる値だけ抽出して、 // 2倍にした値を新しい配列に格納して返す const numbersOfOver5 = numbers.filter(number => { return number > 5; }).map(number => { return number * 2; }); // filter, mapそれぞれのコールバック関数の中のコードが1行だけならこのような書き方も出来る const numbersOfOver5 = numbers .filter(number => number > 5) .map(number => number * 2); |
プロトタイプチェーンを使ったサンプルコード内では「filter(…).map(…)」のように、「filter」と「map」をつなげて書くことが出来ました。
この「つなげて書く」というのが「チェーンのようにつながる」という意味で「プロトタイプチェーン」といいます。
プロトタイプとは何かにつきましては先程の、「図で理解するJavaScriptのプロトタイプチェーン」を参考にすると良いかと思いますが簡単に説明しますと、
JavaScriptの全てオブジェクトは「prototype(プロトタイプ)」という値を所持していて、そのプロトタイプにメソッドがぶら下がっているイメージになります。
冒頭でメソッドについて説明しましたがさらに厳密にいうと次のようになります。
冒頭での説明:
厳密に言うと、メソッドはオブジェクトが持っている関数のことで、今回の場合は配列(Array)がオブジェクトとなります。
↓
厳密に言うと、メソッドはオブジェクトが持っている「プロトタイプ」にひもづく関数のことで、今回の場合は配列(Array)がオブジェクトとなります。
その証拠に「MDNのmapメソッド」のドキュメントのタイトルを見ると「Array.prototype.map()」と書かれています。
なぜつなげて書くことができるのか
まずはMDNの「map」と「filter」のドキュメントの戻り値(返り値)の説明を引用します。
map
与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列。
filter
テスト関数をパスした要素からなる新しい配列です。一つの要素もパスしなかった場合は、空の配列が返されます。
まとめると次のようになります。
- mapもfilterも「新しい配列」を戻り値として返す
- mapもfilterも配列(Array)のメソッドの1つ
つまりArray(配列)を戻り値と返すので、その戻り値(Array)に対してArrayメソッドを呼び出すことが可能なのです。
極端にいうと、次のように「map→map→map→map→…」と永遠につないで書くことも可能です。(厳密にはコンピューターの資源が許す限りなので永遠ではないのですが^^;)
1 2 3 4 5 6 7 8 9 10 11 12 |
const numbers = [1, 2, 3, 4, 5]; numbers .map(number => number * 2) // ①: numbersの中身を2倍にする .map(number => number * 3) // ②: ①の戻り値(Array)を3倍にする .map(number => number * 4) // ③: ②の戻り値(Array)を4倍にする .map(number => number * 5) // ④: ③の戻り値(Array)を5倍にする .map(number => number * 6) // ⑤: ④の戻り値(Array)を6倍にする .map(number => number * 7) // ⑥: ⑤の戻り値(Array)を7倍にする .map(number => number * 8) // ⑦: ⑥の戻り値(Array)を8倍にする .map(number => number * 9) // ⑧: ⑦の戻り値(Array)を9倍にする .map(number => number * 10) // ⑨: ⑧の戻り値(Array)を10倍にする |
しかし、このようにプロトタイプチェーンを使って書くとコード量が一気に減るというのが分かっていただけたかと思います。
まとめ
今回は「for」の代わりに使える便利なArrayメソッドについて説明しました。
そしてArrayメソッドの一例として「forEach」「map」「filter」の3つの使い方もサンプルコードを使って解説しました。
今回紹介した3つのメソッド以外にも便利なArrayメソッドがたくさんあるので、一度確認してみると「これは使えそう!」というメソッドに出会えるかもしれません。
コールバック関数を覚える練習にもなるので、配列を操作することがある際は積極的にArrayメソッドを使ってみてはいかがでしょうか^^