高階関数(Higher Order Functions)を使いこなしてこそのjsだと偉い人が言っていました。
ここではその代表格である、filter, reject, map, reduceの基本的な使い方を纏めておきます。
ちなみに、これはとあるYoutubeの講座を見て、自分用にまとめておこうと思い立っただけですので悪しからず。
##高階関数とは
高階関数とは、引数に値の替わりに関数を受け取る関数のことです。
JavaやPHPなどと違い、jsは関数も値であると考え、変数に代入できたりするのが特徴ですよね。
そうした特徴をふんだんに使っていこうぜってことみたいです。
(高階関数はjsに限った話ではありませんが)
##filterとrejectの使い方
filterとrejectは配列に条件を渡して、
その条件を通った要素だけを纏めた新しい配列を作る関数です。
当然要素数は元の配列より少なくなります。
早速中身を見てみましょう。
まずは、比較したいので高階関数を使わない例から見てみます。
//動物の名前と種類を格納したデータを用意
let animals = [
{name: 'Tom', species: 'rabbit'},
{name: 'Caro', species: 'dog'},
{name: 'Bob', species: 'dog'},
{name: 'Ken', species: 'cat'},
{name: 'Jimmy', species: 'fish'}
]
// forを使ってdogだけを取り出す
let dogs = []
for (let i = 0; i < animals.length; i++) {
if (animals[i].species === 'dog') {
dogs.push(animals[i])
}
}
こんな感じでforのループを使って、
配列から欲しい情報(犬だけに絞った配列)を取得しました。
では、高階関数のfilterを使ってみます。
//動物の名前と種類を格納したデータを用意
let animals = [
{name: 'Tom', species: 'rabbit'},
{name: 'Caro', species: 'dog'},
{name: 'Bob', species: 'dog'},
{name: 'Ken', species: 'cat'},
{name: 'Jimmy', species: 'fish'}
]
// filterでdogを取り出す
const dogs = animals.filter((animal) => {
return animal.species === 'dog'
})
filterは引数にbooleanを返す関数を渡して、trueになった場合のみ
新しい配列に追加していきます。なにこれ便利!!
記述量や読みやすさもforより分かりやすいですね。
でも良さはそこだけじゃないんですよ。
真のすばらしさは機能を分離できるところにあります。
つまり、引数に渡した関数は完全に独立した機能として動くことができます。
例を見たほうが早いですね
//動物の名前と種類を格納したデータを用意
const animals = [
{name: 'Tom', species: 'rabbit'},
{name: 'Caro', species: 'dog'},
{name: 'Bob', species: 'dog'},
{name: 'Ken', species: 'cat'},
{name: 'Jimmy', species: 'fish'}
]
// dogであるか判定する関数を切り出す
const isDog = (animal) => {
return animal.species === 'dog'
}
// dogだけを取得する
const dogs = animals.filter(isDog)
// dog以外だけを取得する
const otherAnimals = animals.reject(isDog)
こんな感じで、渡す関数をdogであるか判定する独立した関数にしました。
それを使いまわしてさらっとrejectも使ってみました。
rejectはfilterと反対の動きをする関数で、falseになった値のみを配列に詰めます。
これにより、犬だけの配列と、犬以外だけの配列を簡単に作ることが出来ました。
なるほど、確かにこりゃ便利だ!!
##mapの使い方
mapも配列の一つ一つの要素を見ていきながら、
引数で渡した関数を実行するという点では先ほどと同様です。
ただ、filterやrejectと違って、新しい配列の要素数は変わらず、
元の配列の要素を"変形"させて新しく詰めなおすようなイメージです。
では先ほどと同じく、普通の例から見てみます。
//動物の名前と種類を格納したデータを用意
const animals = [
{name: 'Tom', species: 'rabbit'},
{name: 'Caro', species: 'dog'},
{name: 'Bob', species: 'dog'},
{name: 'Ken', species: 'cat'},
{name: 'Jimmy', species: 'fish'}
]
// for文を使って、'Tomはrabbitです'のような文章を詰める
let names = []
for (let i = 0; i < animals.length; i++) {
names.push(animals[i].name + 'は' + animals[i].species + 'です')
}
まあ普通ですね笑
mapを使うとこうなります
const animals = [
{name: 'Tom', species: 'rabbit'},
{name: 'Caro', species: 'dog'},
{name: 'Bob', species: 'dog'},
{name: 'Ken', species: 'cat'},
{name: 'Jimmy', species: 'fish'}
]
// mapは引数の関数で変形させた値を新しい配列に詰める
let names = animals.map((animal) => {
return animal.name + 'は' + animal.species + 'です'
})
forを使った例より記述量が減りました。
個人的にはmapの方が断然読みやすいですね。
reactとかやるときは、コンポーネントやJSXをmapの中でreturnしたり超頻出の関数です
では、最後にreduce
##reduceの使い方
配列内の値を全部合計した値を取る例で考えます。
まずはforの書き方で。
let orders = [
{amount: 250},
{amount: 500},
{amount: 750},
{amount: 900},
]
let totalAmount = 0
for (let i = 0; i < orders.length; i++){
totalAmount += orders[i].amount
}
て感じになりますね。
ではreduceを使ってみましょう。
reduceは色々と書き方あって大変なので、一番基本的なものだけ紹介します。
let orders = [
{amount: 250},
{amount: 500},
{amount: 750},
{amount: 900},
]
let totalAmount = orders.reduce((sum, order) => {
return sum + order.amount
}, 0)
reduce第一引数には関数、第二引数には初期値を渡します。
今回は、
第一引数:(sum, order) => { return sum + order.amount }
第二引数:0
となっていますね!
さらに第一引数に渡した関数が、sumとorderという2つの引数を持っています。
orderは言わずもがなorders配列のそれぞれの要素が入ってきます。
試しにconsole.logして値の遷移を見てみましょう
let totalAmount = orders.reduce((sum, order) => {
//途中経過のlogを出してみる
console.log('sum:' , sum)
console.log('order:' , order)
return sum + order.amount
}, 0)
/** console.logの結果 **/
sum: 0
order: { amount: 250 }
sum: 250
order: { amount: 500 }
sum: 750
order: { amount: 750 }
sum: 1500
order: { amount: 900 }
sumの最初は、第二引数で渡した初期値である0が入っていますね。
そこからsumに対してorder.amountを足し合わせているので
sumがどんどん増加して合計値になっています。
##終わりに
特にmapやreduceは利用できる幅がもっと広いはずです。
他にも便利なものがあったら教えてください!