リファクタリングに使える俺的JavaScript小技テクニック集5選

JavaScriptとちょっとしたリファクタリングにおすすめのテクニックを晒します。

* 俺的ラインナップ
    * ① if文よりも&&演算子
    * ② nilガード
    * ③ reduce
    * ④ enumもどき
    * ⑤ ピュアなObject

## ①if文よりも&&演算子
よくあるif文。

```javascript
const array = []
if (isHoge) {
  array.push("hoge")
}
if (isFuga) {
  array.push("fuga")
}
```

上記の文を `&&` 演算子を使うとすっきり書けます。

```javascript
const array = []
isHoge && array.push("hoge")
isFuga && array.push("fuga")
```

`&&` 演算子は左側がtrueの場合は右側が評価されるので上記のように書くことができます。
なお、配列での処理を例に挙げましたが、オブジェクトでの処理でも活用できます。

```javascript
const obj = {}
isHoge && Object.defineProperty(obj, "hoge", { value: hoge })
isFuga && Object.defineProperty(obj, "fuga", { value: fuga })
```

余談ですが、都度 `Object.defineProperty` を使うのも冗長なので、関数化をすれば次のように更に簡潔に記述できます。

```javascript
// カリー化のテクニックを使っています
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ガードというテクニックがあります。

```ruby
hoge = nil
# hogeがnilなら"hoge"が代入される
hoge ||= "hoge"
p hoge #=> "hoge"

fuga = "fugafuga"
fuga ||= "fuga"
p hoge #=> "fugafuga"
```

JavaScriptでも `||` 演算子を使えばnilガードと似たような文を再現することができます。

```javascript
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 合体 (??)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator)の方が適切です！

```javascript
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](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)）。ぶっちゃけ、存在は知っていても「使い方ようわからん」「使わんでも生きていける」と思う方も多いかもしれません。しかし、`reduce` ほど便利な関数はありません。

`reduce` を使えば、例えば次のようなコードを書けます。

```javascript
// 年齢ごとで何人いるかを算出します。
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` でできます。

```javascript
const sumAge = kimetsu.reduce((result, member) => {
  result += member.age
  return result
}, 0)

console.log(sumAge) // => 60
```

その他にも`reduce` は覚えておくと使い所も多く応用も効くのでおすすめです。[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)のドキュメントに幾つか例があるので興味あればぜひ読んでみてください。

## ④enumもどき
「列挙型」としてJavaやPythonなど、様々な言語でサポートされているenumですが、JavaScriptでもenumに似たようなものを再現することができます。

```javascript
// 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`の中身は次のようになります。

![空オブジェクトの宣言.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/b56ed43a-f266-9fce-2e7a-917bd7d1914c.png)

`hoge` の中によくわからないプロパティが紛れ込んでいます。
これは、`var hoge = {}` で宣言すると、Objectのクラスを継承したオブジェクト（正確にはObjectをプロトタイプとしたオブジェクト）が生成されてしまうためです。

これは何が問題でしょうか？ずばりパフォーマンスに影響を与えます。
例えば下記のコード。

```javascript
if ("xxx" in hoge) {
  // 処理
}
```

この `in` 演算子はプロトタイプチェーンを遡って全てのオブジェクトのプロパティをチェックします。
先ほどのキャプチャの例で言うならば、`constructor` や `hasOwnProperty` までチェックしてしまいます。

純粋に key-value のオブジェクトを生成するには次のようにします。

```javascript
// 1. ECMA2015の書き方
const hoge = Object.create(null)

// 2. ECMA2015以降の書き方
const hoge = new Map()
```

`1. ECMA2015の書き方`では`null`をプロトタイプとするオブジェクトを生成することでピュアなオブジェクトを生成できます。

一方、ECMA2015以降ならば[Map](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map)が利用できます。

なお、`Object.create(null)` で生成したオブジェクトは下記のキャプチャのようになります。

![ピュアなオブジェクトの生成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/4d9cf607-789b-43c9-f387-9741cb20c9eb.png)

## まとめ
リファクタに使えそうな5つの俺的JavaScript小技を紹介しました。
特に書籍を参考にしたわけではないオレオレなテクニックだったのですが、参考にしていただけると嬉しいです！
他にも小技テクニックあればコメントいただけると幸いです。