JavaScript
js

switch文が実はもう少し使える子だった?

はじめに

JSに関する小ネタです。
以下のように感じる人はもしかしたらこの記事を読む価値があるかもしれません。

  • if-elseの羅列はやっぱ読みづらいし面倒だなー
  • switch文でパターンマッチみたいなの使いたいなー

ただしパターンマッチを実現するという内容の記事ではないので期待はしすぎないでください。

注意

  • サンプルコードはNode.jsの10.13.0で試してます
  • ほとんどのブラウザで使える内容だとは思いますが、検証しきれていません
  • 間違ってるとこがありましたら指摘お願いします

switch文が使いにくいと感じる場面

"/hoge/fuga"のような URL パスっぽいものがあったときに、その値に応じて処理や返り値を変えたいとする。
単純に完全一致だけで扱えるケースであればswitch文を使える。

function returnSomething(path) {
  switch (path) {
    case '/': {
      return 'top';
    }
    case '/hoge': {
      return 'hoge';
    }
    case '/fuga': {
      return 'fuga';
    }
    case '/hoge/fuga': {
      return 'hoga';
    }
    case '/hoge/piyo': {
      return 'hoyo';
    }
    default: {
      return 'anything else';
    }
  }
}

// 読み飛ばしてもよいコメント
// case にわざわざ "{}" をつけていますが、この例では必要ありません。
// ただし変数の名前空間を分けることができるので、つけとくと便利な場面もあります。

だけど完全一致だけじゃない場合はよくあると思う。(前方一致とかワイルドカードみたいなことをしたい)
そんなとき今までなら諦めてif-elseを使ってました。

function returnSomething(path) {
  if (path === '/') {
    return 'top';
  } else if (path === '/hoge') {
    return 'hoge';
  } else if (path === '/fuga') {
    return 'fuga';
  } else if (path.startsWith('/hoge/')) {
    return 'hogex';
  } else {
    return 'anything else';
  }
}

(String.prototype.startsWith()は IE11 では使えません。polyfill とかでなんとかすると良いと思います。)

ifelse ifでalignmentがずれたり、path ===と何回も書いたり。
個人的にはこういう読みづらくミスを誘発しやすいものを避けたい。

もちろんcaseを連続で並べる方法も数が少ない場合は良いですが、
数が多くなったりワイルドカードみたいなものを実現したいときには現実的な対応策になりません。

// (省略)
    case '/hoge/fuga':
    case '/hoge/piyo': {
      return 'hogex';
    }
// (省略)

そんなときに同僚のコードレビューしていたら、とても面白いコードができあがったのでそれを紹介します。

switch文をちょっと工夫して使う

上記のif-elseの例をswitchで実現したものを先に示しときます。

function returnSomething(path) {
  switch (path) {
    case '/': {
      return 'top';
    }
    case '/hoge': {
      return 'hoge';
    }
    case '/fuga': {
      return 'fuga';
    }
    case path.startsWith('/hoge/') && path: {
      return 'hogex';
    }
    default: {
      return 'anything else';
    }
  }
}

正直if-elseと大差ないとか思うかもしれないですが、まあマシになったということにしときます。
あとはcase path.startsWith('/hoge/') && path:の動きを理解するだけです。

ちょっとした解説

  1. path.startsWith('/hoge/')Booleanを返すので、truefalseという具体的な値で考えてみる
  2. path.startsWith('/hoge/') && pathという式は以下のようになる
    • true && path: pathを返す
    • false && path falseを返す
  3. switch (path)としているので当然比較は以下のようになる
    • path === path: これはNaN以外なら必ず成り立つ
    • path === false: これはpathfalseでない限りは成り立たない

つまり真偽値 && pathとすることで"自分自身"と、もしくは"真偽値の偽"との比較が最終的に行われるということでした。
なのでcase path.match(/^\/hoge\/.*/) && path:みたいなこともできます。
ただしswitchに渡す変数次第で、"真偽値の偽"との比較が真になってしまうこともあるため、
注意して使用したほうが良さそうです。

蛇足

letで変数をあらかじめ宣言しておいて、caseで代入も行うという荒業を使えば、
正規表現の結果を利用するといったことも可能です。
(が、個人的にはあんまり多用したくない印象)

function returnSomething(path) {
  let matched;
  switch (path) {
    case '/': {
      return 'top';
    }
    case '/hoge': {
      return 'hoge';
    }
    case (matched = path.match(/\/hoge\/(.*)/)) && path: {
      return `hoge + ${matched[1]}`;
    }
    default: {
      return 'anything else';
    }
  }
}

※ IE では let を使えないのでご注意ください。

さいごに

今回はswitch文の少し変わった使い方を紹介しました。
はやくパターンマッチが正式導入されるといいですね。

明日は @forl_head_officer さんの記事です!