初めまして、Freedomingでフロントエンドエンジニアをしております、刀川です。現在はReactをメインに触っております。今回、会社の新たな取り組みとしてTech Blogの執筆を始めることとなり、私が第一弾を担当させていただきます。
Tech Blog1発目として、JavaScriptのArray.prototype.map()、Array.prototype.filter()を用いた配列処理についてお話します。
ざっくり説明すると、mapは「配列の全要素に対して指定の関数を適用した、新しい配列を返す」メソッドで、filterは「配列の全要素に対して指定の関数を適用し、結果がtrueの要素のみ残した新しい配列を返す」メソッドとなります。
どちらも指定の関数を全要素に適用するものですが、これらのメソッド自体は「新しい配列を返す(=元の配列を変更しない)」事も大きなポイントです。
javascriptの配列操作メソッドにはArray.prototype.reverse()など元の配列を変更するものがあって、そういったメソッドを利用するときには元の配列が変更されてもいいのか留意して扱わなければならないのですが、この2つに関してはそういった心配はないです。
map
[1, 2, 3, 4, 5].map(e => e + 10) // => [11, 12 13, 14, 15]
// こちらの例も同じ
const addTen = e => e + 10
[1, 2, 3, 4, 5].map(addTen)
上記は配列の全要素に10を足す関数を適用する例です。
function (現在の要素) {} のような、1要素受け取る関数を渡してやるのが基本的な使い方です。
また、mapは新しい配列を返却するので、mapをチェインして更に別の処理を加えてやることもできます。
type Obj = {
name: string,
postcode: string
}
const obj: Obj = {
name: 'Alice',
postcode: '1000000'
}
例えば、上記のような「XXXXXXX」形式の郵便番号を持つオブジェクトの配列があったとして、
そこから郵便番号だけを抜き取り、更に「〒XXX-XXXX」形式にフォーマットしたい場合は
const arr: Obj[] = [/* Objを要素に持つ配列 */]
const formatPostcode = e => /* 8桁の郵便番号を「〒XXX-XXXX」形式にフォーマットする関数 */
const postcodes = arr.map(e => e.postcode).map(formatPostcode)
のように書けます。
追加として、mapに渡す関数は省略可能な第2引数と第3引数を持てて、それぞれ「現在の配列の添字」「mapが適用されている配列そのもの」を受け取ります。
n番目の要素は違う処理をするだとか、配列の長さが見たい時だとかに使えます。
filter
[1, 2, 3, 4, 5, 6].filter(e => e % 2 === 0) // => [2, 4, 6]
上記はfilterを適用する配列から、偶数の要素のみを抽出する例です。
filterが引数として受け取る関数の引数の形式(第1引数が現在の要素、省略可能な第2, 第3引数)は同じですが、
受け取る関数が真偽値(true, false)を返す必要がある点が異なります。
mapと同様新しい配列を返却するので、更にmapやfilterをチェインできます。
// Objの型はmapの例と同様
const arr: Obj[] = [/* Objを要素に持つ配列 */]
const arr2 = arr.filter(e => e.postcode === '1020072').map(e => e.name)
上記は郵便番号が'1020072'の要素を抜き出し、さらにその要素の名前だけを残す処理の例です。
const isLessThanSix = e => /* 6字より少ないなら警告文を返却、そうでなければ''を返却する関数 */
const notContainsUpperCase = e => /* 大文字を含んでいなければ警告文を返却、そうでなければ''を返却する関数 */
const notContainsLowerCase = e => /* 小文字を含んでいなければ警告文を返却、そうでなければ''を返却する関数 */
const validation = text => {
return [isLessThanSix, notContainsUpperCase, notContainsLowerCase]
.map(f => f(text))
.filter(Boolean)
}
const invalidPassword = validation('aaaaa') // => ['6字より少ないときの警告文', '大文字を含んでいないときの警告文']
const validPassword = validation('aaaaaA') // => []
もう一例、map, filterを用いたバリデーションの例です。
エラー文を返す関数を配列にして、「関数 => エラー文(または'')」にmapし、''でない要素をfilterしています。
mapを使うことで「ここは配列を変更しています」、filterを使うことで「ここは配列をフィルタリングしています」というのが明確になるので、同様の配列処理をforで行った場合と比べ、コードが宣言的で簡潔に記述できるようになるのがポイント高いです。
今日、JavaScriptにおけるmap, filterと同様の機能は色々な言語に(名前は違えど)搭載されているので、是非使ってみてください。
参考
Array.prototype.map() - JavaScript | MDN - https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Array.prototype.filter() - JavaScript | MDN - https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter