JavaScriptのMath.min/maxの挙動の気をつけどころ

2021/03/20に公開1

記事作成日: 2019/05

Math.min / Math.maxを使っていたら思ったよりハマりそうな挙動があったのでまとめておきたい。
今回書いてる事はMDNにも記載されている。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/max

Math.min / Math.maxは数値に変換出来ないものが一つでもあるとNaNを返す

まず、これらの関数は受け取る引数の中にNaNが混じっている場合NaNを返す。

Math.min(1, 2, 3, NaN)
// => NaN

そして、受け取る引数はすべてNumberにキャストされる。

Math.min(10, undefined)
// => NaN
Math.min(10, "foo")
// => NaN

Numberでキャストされて数値になるようなものはそれによって扱われる

Math.min(10, null)
// => 0 ( NUmber(null)が0として評価される)
Math.min(10, true)
// => 1 ( Number(true)が1として評価される)

数値に変換された時にどうなるかは結構覚えゲーな所があるので、そもそもこのような値が入らないようにするのが望ましいだろう。

Math.min / Math.maxは空の引数の場合Infinityを返す

ざっくり書くとこんな感じだ

Math.min()
// => Infinity
Math.max()
// => -Infinity

minやmaxのような関数に何も入れなかった場合の挙動は各言語で変わるだろうが、JavaScriptはこうなるらしい。

なぜこうなるかは下記に有益な記事があったのでそちらに任せたい。

(reduce関数の初期値としてそれぞれ利用されていると捉えると個人的には腑に落ちた)

どういう時に気をつけるのか?

「いやいや、普通そんな書き方しないでしょう」というのもあると思うので、そもそも自分がこの挙動を知るに至った経緯を書いてみる。

確かに普通に起きないが、例えばこんな具合にfilterと組み合わせて書きたい場合、ちょっと気をつけたほうが良い

const items = [item1, item2]
  .map(Number) // ここでNaNが混じりうる

Math.min(...items)
// => NaNになりうる
const items = [item1, item2]
  .filter(item => item > 0) // ここでitemsが空配列になりうる

Math.min(...items)
// => Infinityになりうる

ちなみに、上記コードでMath.min(...items)のように配列をspreadして渡しているがこれは普通に便利。
昔はMath.min.apply(null, items)のような形で利用していたものを非常に簡素に書けている。

Discussion

standard softwarestandard software

間違った値が入ってもそのままエラー発生せずに動いてしまって、結果、あとあとから何か変な値が別のところで発見されて困って、その別の所で分岐コードを書かなければいけない、

ということが起きやすいのが、JavaScriptを難しくしている問題のひとつと思います。

自作ライブラリのParts.js を作っているのですが、そこのmaxでは、数値以外の値を評価しようとするとエラーになるようにしていました。配列以外を渡したらエラーとか、配列の要素なしを渡したらnullを返すとか、maxみたいな基本的なものでもいろいろ考えることがいくつもあるので、JSって面白いような難しいような感じです。

https://github.com/standard-software/partsjs/blob/v10.4.1/source/array/_max.js#L28