9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-28

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なら nullundefinedなど)は予期せぬバグの元となります。

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の時に右側が評価されるので、もし hogenullundefined の場合は右側の "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の中身は次のようになります。

空オブジェクトの宣言.png

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

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

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

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

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

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

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

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

一方、ECMA2015以降ならばMapが利用できます。

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

ピュアなオブジェクトの生成.png

まとめ

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

9
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?