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