JavaScriptとちょっとしたリファクタリングにおすすめのテクニックを晒します。
- 俺的ラインナップ
- ① if文よりも&&演算子
- ② nilガード
- ③ reduce
- ④ enumもどき
- ⑤ ピュアなObject
①if文よりも&&演算子
よくあるif文。
const array = []
if (isHoge) {
array.push("hoge")
}
if (isFuga) {
array.push("fuga")
}
上記の文を &&
演算子を使うとすっきり書けます。
const array = []
isHoge && array.push("hoge")
isFuga && array.push("fuga")
&&
演算子は左側がtrueの場合は右側が評価されるので上記のように書くことができます。
なお、配列での処理を例に挙げましたが、オブジェクトでの処理でも活用できます。
const obj = {}
isHoge && Object.defineProperty(obj, "hoge", { value: hoge })
isFuga && Object.defineProperty(obj, "fuga", { value: fuga })
余談ですが、都度 Object.defineProperty
を使うのも冗長なので、関数化をすれば次のように更に簡潔に記述できます。
// カリー化のテクニックを使っています
const defineProp = (obj) => {
return (key, value) => {
return Object.defineProperty(obj, key, {
value,
writable: false, // readonly
enumerable: true, // in演算子などでプロパティを列挙させる
})
}
}
const obj = {}
const definePropObj = defineProp(obj)
isHoge && definePropObj("hoge", hoge)
isFuga && definePropObj("fuga", fuga)
&&
演算子で紹介していますが、もちろん ||
でも同様の小技テクが使えます。②のnilガードにも通じます。
②nilガード
nilガードをご存知でしょうか?Rubyを普段書いている方ならご存知かもしれません。
どの言語も同じだと思いますが、nullのようなもの(Rubyなら nil
, JavaScriptなら null
や undefined
など)は予期せぬバグの元となります。
Rubyでは意図せず nil
が変数に入らないように、nilガードというテクニックがあります。
hoge = nil
# hogeがnilなら"hoge"が代入される
hoge ||= "hoge"
p hoge #=> "hoge"
fuga = "fugafuga"
fuga ||= "fuga"
p hoge #=> "fugafuga"
JavaScriptでも ||
演算子を使えばnilガードと似たような文を再現することができます。
let hoge
hoge = hoge || "hoge"
console.log(hoge) // => "hoge"
let fuga = "fugafuga"
fuga = fuga || "fuga"
console.log(fuga) // => "fugafuga"
hoge = hoge || "hoge"
の文ではまず右辺が評価されます。
||
演算子は左側がfalsyの時に右側が評価されるので、もし hoge
が null
や undefined
の場合は右側の "hoge"
が評価され、変数 hoge
に代入されます。
また余談ですが、上記のような ||
を用いたテクニックはnilガードでなくとも役に立つので、覚えておくと便利です。
[2021/04/27 追記]
@github0013@github さんからご指摘があった通り、
nilガードの文脈においてはNull 合体 (??)の方が適切です!
const tests = [0, 1, 2, null, 3]
// 意図しない挙動になる可能性あり
console.log(tests.map(test => test || "値なし"))
// => ["値なし", 1, 2, "値なし", 3]
// 厳密に評価
console.log(tests.map(test => test ?? "値なし"))
// => [0, 1, 2, "値なし", 3]
③reduce
reduce
はJavaScriptのArrayに標準で組み込まれている関数です(MDN)。ぶっちゃけ、存在は知っていても「使い方ようわからん」「使わんでも生きていける」と思う方も多いかもしれません。しかし、reduce
ほど便利な関数はありません。
reduce
を使えば、例えば次のようなコードを書けます。
// 年齢ごとで何人いるかを算出します。
const kimetsu = [
{ name: '竈門 炭治郎', age: 15 },
{ name: '竈門 禰豆子', age: 14 },
{ name: '我妻 善逸', age: 16 },
{ name: '嘴平 伊之助', age: 15 },
]
// 1. ありがちな書き方
const output1 = {}
kimetsu.forEach(member => {
if (!(member.age in output1)) {
output1[member.age] = 1
return
}
output1[member.age]++
})
console.log(output1)
// => {14: 1, 15: 2, 16: 1}
// 2. reduceを使った書き方
const output2 = kimetsu.reduce((result, member) => {
if (!(member.age in result)) {
result[member.age] = 1
return result
}
result[member.age]++
return result
}, {})
console.log(output2)
// => {14: 1, 15: 2, 16: 1}
1. ありがちな書き方
では空のオブジェクトを宣言する必要がありますが、 2. reduceを使った書き方
をご覧いただければわかるように、空のオブジェクトを宣言しなくとも変数を初期化することができます。
他にも下記のように全員の年齢を足すことも reduce
でできます。
const sumAge = kimetsu.reduce((result, member) => {
result += member.age
return result
}, 0)
console.log(sumAge) // => 60
その他にもreduce
は覚えておくと使い所も多く応用も効くのでおすすめです。MDNのドキュメントに幾つか例があるので興味あればぜひ読んでみてください。
④enumもどき
「列挙型」としてJavaやPythonなど、様々な言語でサポートされているenumですが、JavaScriptでもenumに似たようなものを再現することができます。
// JavaScriptにおけるenumっぽいもの
const Animal = Object.freeze({
DOG: Symbol(),
CAT: Symbol(),
BIRD: Symbol(),
})
const pochi = {
type: Animal.DOG,
name: "ポチ"
age: 10,
}
console.log(pochi.type === Animal.CAT)
// => false
Object.freeze
を使うことで対象のオブジェクトを変更不可能にします。
さらに Symbol
を利用することでプログラム上で一意であることを担保します。
Animal
をexportすることによって pochi.type === "dog"
のような保守性の低いコードを散財させることを防ぐことができます。
⑤ピュアなObject
ピュアなObjectとは、純粋に key-valueのみで構成されるオブジェクトです(Javaで言うところMapのような。なお、「ピュアなObject」は私が勝手に呼んでいるだけで、世間一般的な用語じゃないです)。
オブジェクトを宣言する時には var hoge = {}
と宣言していますが、実はこれはピュアなオブジェクトではありません。どういうことでしょうか。
試しにvar hoge = {}
を宣言してみると、hoge
の中身は次のようになります。
hoge
の中によくわからないプロパティが紛れ込んでいます。
これは、var hoge = {}
で宣言すると、Objectのクラスを継承したオブジェクト(正確にはObjectをプロトタイプとしたオブジェクト)が生成されてしまうためです。
これは何が問題でしょうか?ずばりパフォーマンスに影響を与えます。
例えば下記のコード。
if ("xxx" in hoge) {
// 処理
}
この in
演算子はプロトタイプチェーンを遡って全てのオブジェクトのプロパティをチェックします。
先ほどのキャプチャの例で言うならば、constructor
や hasOwnProperty
までチェックしてしまいます。
純粋に key-value のオブジェクトを生成するには次のようにします。
// 1. ECMA2015の書き方
const hoge = Object.create(null)
// 2. ECMA2015以降の書き方
const hoge = new Map()
1. ECMA2015の書き方
ではnull
をプロトタイプとするオブジェクトを生成することでピュアなオブジェクトを生成できます。
一方、ECMA2015以降ならばMapが利用できます。
なお、Object.create(null)
で生成したオブジェクトは下記のキャプチャのようになります。
まとめ
リファクタに使えそうな5つの俺的JavaScript小技を紹介しました。
特に書籍を参考にしたわけではないオレオレなテクニックだったのですが、参考にしていただけると嬉しいです!
他にも小技テクニックあればコメントいただけると幸いです。