Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

ワンライナーでバージョン文字列を比較する

結論: localeCompare を使う

a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'})

参考: https://stackoverflow.com/a/65687141

localeCompare は IE11 でも使えるので、使えない心配はほぼありません。

しかし、locale とついているためブラウザ実装依存ではないか? と不安になりますよね。今回はそこを調べてみました。

まあ、結論から言うと 実装依存っぽいので自身のサポート環境でしっかり検証してから使う必要があります。

localeCompare は何をするメソッドなのか

まず localeCompare とはどういうメソッドなのか、ecmascript の仕様書 から引用しつつ説明します。

This method returns a Number other than NaN representing the result of an implementation-defined locale-sensitive String comparison of the this value (converted to a String S) with that (converted to a String thatValue).

つまり、localeCompare は呼び出し元(this)と引数(b)を比較して NaN 以外の数値を返すメソッドだそうです。

文字列の順序は実装に依存しています。また、返す値も negative か 0、plus という大雑把な具合にしか決まってなさそうです。仕様を読みましたが、それ以上の説明はありません。結構昔に導入されてブラウザ間の差異が激しくて仕様がフワッとしている…?

環境差異は大丈夫なのか

以下のスクリプトを Node v16.0.0、Chrome112.0.5615.137、Safari 15.6.1 で検証してみましたが全て true になりました。なので おそらく問題ないです (IE で確認したいけど手元の環境にない…)。false になる環境あったら教えてください。

console.log(
  '0.0.0'.localeCompare('0.0.1', undefined, { numeric: true, sensitivity: 'base' }) < 0 &&
  '0.0.1'.localeCompare('0.0.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('0.0.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.1'.localeCompare('1.0.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('0.1.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('0.2.3', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('1.2.3', undefined, { numeric: true, sensitivity: 'base' }) < 0 &&
  '10.0.0'.localeCompare('9.1.2', undefined, { numeric: true, sensitivity: 'base' }) >0
)

↑これくらい満たせていれば流石に大丈夫だと思います。

配列でも使える

this は string へ変換されるので、 apply を使えば配列に対しても使えます。

↓こんな感じ

''.localeCompare.apply([1, 0, 0], [[0, 0, 1], undefined, { numeric: true }])

// bind を使うとこんな感じ
''.localeCompare.bind([1, 0, 0])([0, 0, 1], undefined, { numeric: true })

// bind operator が使えるとこんな感じ
[1, 0, 0]::''.localeCompare([0, 0, 1], undefined, { numeric: true })