PHP の max() / min() に NAN が混ざると、引数の順番や PHP のバージョンで答えが変わる、という記事が話題になっていました。では JavaScript の Math.max() はどうなのか。空の配列、NaN、文字列、配列そのものなどを渡して、macOS 27.0.1 上の Node.js v24.14.1・Google Chrome 155・WebKit(Safari と同じ系統のブラウザエンジン。Mac の cmux というアプリに入っているもの)の3つで確かめました。
結果は、6つの疑問のうち5つは3つの環境で同じで、違ったのは「何個まで渡せるか」だけでした。その1つ以外はエラーにならず、そのまま値が返ってくるので、おかしな値に気づきにくいのが厄介なところです。
Q1. 空の配列を ... でばらして渡すと、エラーになる?
なりません。-Infinity(マイナスの無限大)が返ります(... を付けずに空の配列をそのまま渡したときの話は Q4 です)。
console.log(Math.max(...[])); // -Infinity
console.log(Math.min(...[])); // Infinity
console.log(Math.max()); // -Infinity
... は配列の中身をばらして引数として渡す書き方(スプレッド構文)です。中身が空だと引数が0個になり、Math.max() は比べる相手がいないときの出発点(どんな数よりも小さい値)として -Infinity を返します。MDN の Math.max() のページにも、引数がないときは -Infinity を返すと書かれています。
困るのは、一覧の ID を「いちばん大きい番号 + 1」で作るときです。
const todos = [];
const nextId = Math.max(...todos.map((t) => t.id)) + 1;
console.log(nextId); // -Infinity
一覧が空のときだけ、次の ID が -Infinity になります。エラーは出ません。
const safeNextId = Math.max(0, ...todos.map((t) => t.id)) + 1;
console.log(safeNextId); // 1
最初に 0 を1つ入れておくと、空のときは 0 + 1 で 1 から始まります(ID が 0 以下にならない前提の書き方です)。
Q2. NaN が混ざると、順番で結果が変わる?
JavaScript では変わりませんでした。NaN(数ではない、という意味の値)がどこにあっても、結果は NaN です。MDN にも、引数のどれかが NaN、または変換すると NaN になるなら NaN を返す、と書かれています。
console.log(Math.max(NaN, 1, 3)); // NaN
console.log(Math.max(1, NaN, 3)); // NaN
console.log(Math.max(1, 3, NaN)); // NaN
console.log(Math.min(1, NaN, 3)); // NaN
console.log(Math.max(1, undefined)); // NaN
undefined も数にすると NaN になるので、同じく結果が NaN になります。よく起きるのは、一部のデータに項目がないときです。
const items = [{ price: 300 }, { price: 1200 }, { name: "送料" }];
console.log(Math.max(...items.map((i) => i.price))); // NaN
3つ目に price がないので undefined が混ざり、最大値が NaN になりました。数だけを残してから渡します。
const prices = items.map((i) => i.price).filter((p) => Number.isFinite(p));
console.log(Math.max(...prices)); // 1200
Number.isFinite() は、ふつうの数(NaN や無限大ではない数)のときだけ true を返します。?? 0 で 0 に置きかえる方法もありますが、それは「ない」を「0円」として扱う、という決めごとになるので、どちらにするかは場面で決めます。
この書き方には2つ気をつける点があります。全部のデータに price がないと prices が空になり、Q1 の -Infinity に戻ります。また、Number.isFinite("1200") は false なので、API やフォームから "1200" のような文字で届く値は、黙って外されます。文字で届くなら、先に数に変換してから絞り込みます(文字から数への変換のずれは「文字列を数値に変換する4つの方法、挙動の違い5選」にまとめています)。
Q3. 文字列や null を渡すと?
数に変換してから比べます。返ってくるのは数です。
console.log(Math.max("9", "10")); // 10
console.log(Math.max(1, "10px")); // NaN
console.log(Math.max(-1, null)); // 0
console.log(Math.max(-1, "")); // 0
"9" と "10" は、文字として並べると "9" のほうが後ですが、Math.max は数にしてから比べるので 10 が返ります(返り値は文字ではなく数の 10)。"10px" は数にできないので NaN です。
気をつけたいのは null と空の文字 "" で、どちらも 0 として扱われます。全部マイナスの値の中に null が1つ混ざると、存在しないはずの 0 が最大値になります。
Q4. 配列をそのまま渡すと?
console.log(Math.max([1, 2, 3])); // NaN
console.log(Math.max([5])); // 5
console.log(Math.max([])); // 0
... を付け忘れて配列をそのまま渡すと、配列が1つの引数として数に変換されます。[1, 2, 3] は文字にすると "1,2,3" なので数にできず NaN、[5] は "5" なので 5、[] は "" なので 0 です。
厄介なのは、要素が1つの配列だと正しそうな値が返ることです。テストのデータが1件だけだと気づけず、2件以上になってから NaN になります。
Q5. 要素が多いと、何個まで渡せる?
スプレッド構文で渡せる個数には上限があり、ここだけ環境で違いました。同じ値を並べた配列を Math.max(...配列) に渡して、エラーにならない最大の個数を探しました(3回ずつ測って同じ値)。
| 環境 | エラーにならなかった最大の個数 | 1つ増やしたとき |
|---|---|---|
| Node.js v24.14.1 | 124,159 | RangeError: Maximum call stack size exceeded |
| Google Chrome 155 | 123,587 | RangeError: Maximum call stack size exceeded |
| WebKit(cmux 内蔵) | 638,616 | RangeError: Maximum call stack size exceeded. |
10万個まではどの環境でも通り、100万個はどの環境でもエラーでした。この上限は、関数を呼び出すときに引数を積んでおく場所(コールスタック)の大きさに左右されるので、呼び出し方や環境で変わります。上の数字は「この測り方ではこうだった」という目安です。
実際、関数の呼び出しを深く重ねた中(関数の中で関数を呼ぶのを何千回も重ねた、いちばん奥)で同じように測ると、上限は下がりました。2000回重ねた中では Node.js 100,137・Chrome 99,567・WebKit 610,586、5000回重ねた中では Node.js 64,137・Chrome 83,571・WebKit 568,586 個でした。アプリの中のどこで呼ぶかによっては、10万個より少なくても止まることがあります。
件数が多くなりうるなら、スプレッドを使わずに、reduce で1つずつ Math.max に通します(MDN の Math.max() のページにも同じ書き方が載っています)。
const big = Array.from({ length: 1_000_000 }, (_, i) => i);
const max = big.reduce((a, b) => Math.max(a, b), -Infinity);
console.log(max); // 999999
100万個でも、3つの環境とも 999999 が返りました。1つずつ Math.max に通すので、NaN が混ざれば NaN、"9" と "10" なら 10、空の配列なら -Infinity と、Math.max(...配列) と同じ結果になり、個数の上限だけがなくなります。
同じ reduce でも、Math.max の代わりに > で比べる書き方には注意が要ります。
const bigger = (arr) => arr.reduce((a, b) => (a > b ? a : b), -Infinity);
console.log(bigger([NaN, 1, 3])); // 3
console.log(bigger([1, 3, NaN])); // NaN
console.log(bigger([undefined, 300, 1200])); // 1200
console.log(bigger([300, 1200, undefined])); // undefined
console.log(bigger(["9", "10"])); // 9
> は相手が NaN や undefined だと必ず false になるので、混ざっている位置で結果が変わりました。冒頭の PHP と同じく「順番で答えが変わる」状態です。また、文字の "9" と "10" は数にせず文字として比べるので、"9" が返ります(console.log の表示では引用符が付かず 9 と出ます)。この書き方を使うなら、Q2 のように数だけに絞った配列に使います。
Q6. 日付を渡すと、日付が返ってくる?
返ってくるのは日付ではなく、数(1970年1月1日 0時(世界標準時)からのミリ秒)です。
const a = new Date(2026, 9, 1);
const b = new Date(2026, 9, 16);
const latest = Math.max(a, b);
console.log(latest); // 1792076400000
console.log(new Date(latest)); // 2026-10-15T15:00:00.000Z
日付を数に変換して比べるので、いちばん新しい日付を求めること自体はできます。日付として使うなら new Date() で戻します。最後の表示は世界標準時なので、日本時間の 10月16日 0:00 が 15T15:00 と出ています(Node.js の表示。日本時間の設定で実行)。
AI が書いたコードを見るときは
-
Math.max(...配列)の配列が空になることはないか(空なら-Infinity) - 配列の中に
undefined・null・空の文字が混ざる可能性はないか(NaNや0になる) -
...を付け忘れてMath.max(配列)になっていないか(要素が1つのときだけ正しく見える) - 件数が数万〜10万を超えることはないか(深い呼び出しの中では上限が下がるので、多くなりうるなら
reduceで1つずつMath.maxに通す) -
reduceを(a, b) => (a > b ? a : b)で書いていないか(NaNやundefinedの位置で結果が変わる)
未経験者向けの講座を運営しています
未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全24セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md と学習モード(learner / developer)の設計までセットで含みます。
- ▶ ライブデモ(インストール不要・無料で今すぐ見られます)→ https://trial-web-ayies128s-projects.vercel.app/?ref=qiita
- 教材完全版+月5,500円のメンタリング(全24セッション+チャット質問し放題)→ https://menta.work/plan/20251?ref=qiita
- 無料体験版(git clone して自分の手元で動かす・最初の数セッション分・⭐ Star もよろしくお願いします)→ https://github.com/ayies128/next-ai-camp-trial
- YouTube『AIエンジニア情報局』(AI×開発ニュースを1本5分でキャッチアップできる別運営チャンネル・無料)→ https://www.youtube.com/channel/UC1rXVD9WYsQPQEWZyd-A1KA/?ref=qiita
※ 中上級者には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。