色んなところでJavaScriptのソースを読んでいると、最近は関数を使いこなして、関数型プログラミング的な書き方をして分岐や繰り返し処理をあまり書かなくなっている傾向にあるように思います。
私も最近はJavaScriptのコードを書く時は、極力ifやforなどを使った命令型のプログラミングではなく、標準で用意されている高階関数(関数を引数に取る関数)を駆使してすっきり書くように心がけています。
ただ、初学者にとってはifやforを使って書くことと比べてどんなメリットがあるのかいまいち分かりにくいところがあるので、そんな方に向けてサンプルを使って少しでも理解を深めてもらえたらいいなと思います。
やりたいこと
まずは以下のような、顧客情報、配送先情報、請求先情報の3つのオブジェクトがあるとします。
// 顧客情報
const customer = {
customer_name : 'A商店',
customer_address : '東京都渋谷区',
customer_mail : 'ashop@sample.com',
}
// 配送先情報
const deliveryPartner = {
delivery_name : 'B株式会社',
delivery_address : '東京都新宿区',
delivery_mail : 'bcompany@sample.com',
}
// 請求先情報
const billingPartner = {
billing_name : 'C株式会社',
billing_address : '東京都品川区',
delivery_mail : 'ccpmpany@sample.com',
}
これらのオブジェクトのうち、どのオブジェクトが渡ってくるかは分かりませんが、とにかく、渡ってきたオブジェクトの住所の値が取得できる関数を作りたいとします。
顧客情報の場合は「customer_address」、配送先情報の場合は「delivery_address」、請求先情報の場合は「billing_address」の値をそれぞれ取得したいです。
キー名が「address」のように統一されていれば簡単な話でしたが、残念ながらオブジェクトによってキー名が異なるため、少し工夫が必要そうです。
方法1. 条件分岐を使ってプロパティをチェック
まず簡単に思いつきそうなのは、ifを使ってプロパティの存在チェックを行って、それぞれのプロパティの値を返すようなものでしょうか。例えば以下のようになります。
const getAddress = (obj) => {
if('customer_address' in obj) {
return obj.customer_address
} else if ('delivery_address' in obj) {
return obj.delivery_address
} else if ('billing_address') {
return obj.billing_address
} else {
return null
}
}
プロパティの存在チェックはいくつか方法がありますが、今回はinを使いました。
こうするとそれぞれのオブジェクトで住所が取得できます。
getAddress(customer) // ⇒'東京都渋谷区'
getAddress(deliveryPartner) // ⇒ '東京都新宿区'
getAddress(billingPartner) // ⇒ '東京都品川区'
実行結果は問題ないですが、この方法はいまいちイケてないですね。
何がイケてないかというと、条件分岐が複数あるので、そのパターン分動きを確認する必要があり、テストが面倒で、バグの温床にもになりやすいです。
また、新たに住所情報を持つ別のオブジェクトが増えてしまったとき、対応するにはこの関数を修正をする必要があります。
汎用性を高めるには、オブジェクトが増えた場合でもソースコードを修正せずに対応できることが理想です。
方法2. キーの配列をループして文字列チェック
では、オブジェクトが増えた場合でも対応できるようにするにはどうしたら良いでしょうか。
住所情報にはキーに「address」という文字列が含まれているので、その法則を利用することにします。
キー情報を配列で取得し、ループでキー名をチェックしながら、「address」という文字が含まれていた場合にそのプロパティを使って値を取得すればよさそうです。例えば以下のようになります。
const getAddress = (obj) => {
const keys = Object.keys(obj)
for (const key of keys) {
if(key.includes('address')) {
return obj[key]
}
}
return null
}
こうすれば、新しいオブジェクトが増えたとしても、住所を表すキー名に「address」という文字があれば対応できるようになりました。
ifで1つ1つ分岐していたものよりは、汎用性は高くなりました。
これでも悪くはないかもしれませんが、配列に用意されている関数を使うことでより簡潔に書くことができます。
方法3. 配列のfind関数を使用する
javaScriptの配列にはfindという関数が用意されており、関数を引数に取ります。
コールバック関数の中で条件を満たした要素が返るようになっており、その中で「address」という文字が含まれている要素を返すように作ります。
詳しくはMDNのサイトなどで確認してもらうとして、実際のコードは例えば以下のようになります。
const getAddress = (obj) => {
return obj[Object.keys(obj).find(e => e.includes('address'))]
}
やっていることは方法2と同じですが、1行で済むようになり、簡潔になりました。
オブジェクトが増えても対応できますし、条件分岐がないので確認作業も方法1に比べて楽になりそうですね。
方法4. オブジェクト自身のメソッドにする
先ほどまでは関数の引数としてオブジェクトを受け取る方法でしたが、オブジェクトを活用するという意味ではオブジェクト自身のメソッドにしてしまう方法も良いかと思います。
例えば以下のようになります。
// アロー関数ではなく関数式であることに注意
const getAddress = function() {
return this[Object.keys(this).find(e => e.includes('address'))]
}
customer.getAddress = getAddress // オブジェクトのプロパティとしてセット(メソッド)
// オブジェクトのメソッドとして呼び出す
customer.getAddress() // ⇒'東京都渋谷区'
注意点としては、関数を定義するときにアロー関数ではなく関数式を使う必要があることです。
これは、アロー関数と関数式では、関数内でのthisの挙動に違いがあるためです。
アロー関数ではthisは静的に決まる(定義された場所できまる)が、関数式ではthis動的に決まるとうい性質があります。
そのため、オブジェクトのメソッドとして使用したい場合は、関数式で定義することで、thisはメソッドを呼び出すオブジェクト自身を指すようになります。
JavaScriptでは標準で配列に対して多くの高階関数が用意されています。
うまく使いこなすことで条件分岐が減り、結果として確認作業が楽になりコードも簡潔になる場面が多くあるので、できる限り使いこなしていきたいところです。