Math.max 大陣列出現 RangeError?改用迴圈並定義資料契約
本文目錄
小陣列的 Math.max(...values) 正常,資料增加後卻出現 RangeError: Maximum call stack size exceeded,要先檢查函式呼叫的參數數量。展開語法會把每個元素變成一個參數;資料集取最大值不必走這條路,可以逐項比較。
MDN Math.max提醒,spread 和 apply() 都會受到大量函式參數影響。換成 Math.max.apply(null, values),仍有同一個問題。
先決定空資料與非法值要怎麼處理
內建 Math.max() 沒有參數時回傳 -Infinity,遇到無法轉換的值可能得到 NaN。業務資料若要求「非空、全部是有限 number」,可以明確拒絕空輸入及非法值,讓錯誤停在資料邊界。
以下函式採取這個契約,也接受可迭代的 typed array。它逐項走訪,不修改來源、沒有把所有值展開成參數:
import assert from 'node:assert/strict';
function maxFinite(values) { let found = false; let result = -Infinity; for (const value of values) { if (typeof value !== 'number' || !Number.isFinite(value)) { throw new TypeError('每個值都必須是有限 number'); } result = Math.max(result, value); found = true; } if (!found) throw new RangeError('至少需要一個值'); return result;}
assert.equal(maxFinite([-8, -3, -12]), -3);assert.equal(maxFinite([0]), 0);assert.equal(maxFinite(new Float64Array([1.5, 9.25, 3])), 9.25);assert.equal(Object.is(maxFinite([-0, 0]), 0), true);assert.equal(Object.is(maxFinite([-0]), -0), true);assert.throws(() => maxFinite([]), RangeError);for (const bad of [NaN, Infinity, -Infinity, '9', undefined, null]) { assert.throws(() => maxFinite([1, bad]), TypeError);}assert.throws(() => maxFinite(new Array(3)), TypeError);const source = [-2, 7, 4];assert.equal(maxFinite(source), 7);assert.deepEqual(source, [-2, 7, 4]);const large = Array.from({ length: 1_000_000 }, (_, index) => index);assert.equal(maxFinite(large), 999_999);console.log('最大值與資料契約檢查通過');存成 max-finite.mjs,執行 node max-finite.mjs。函式每次只傳兩個參數給 Math.max,額外空間為常數,時間隨輸入筆數線性增加。保留兩值版 Math.max,也保留了正零與負零的選擇語意。
把初值寫成 0 會讓全負數資料得到錯誤答案。使用 -Infinity 再以 found 判斷是否有資料,能把初始化與空集合政策分開;若你的產品允許空資料,則可另定義回傳 null,並讓呼叫端處理它。
reduce 可以用,但空槽的處理不同
下面這段適用於已驗證的 dense number 陣列:
const values = [-8, -3, -12];const max = values.reduce((current, value) => Math.max(current, value), -Infinity);console.log(max); // -3reduce() 會略過稀疏陣列的空槽;for...of 則會讀到 undefined,上面的嚴格函式因此拒絕它。這個差異會影響資料驗證。若空槽表示缺資料,別因為改寫迴圈就默默改成跳過。更多初始值行為可見 reduce 空陣列錯誤與資料契約。
如果 API 回傳數字字串,先在輸入層定義允許的格式與轉換,再取最大值。隨手加 Number() 會連空字串都轉成零,容易把資料缺漏藏起來。
不要把某台機器的失敗筆數寫成安全上限
不同引擎與執行環境的函式參數限制不宜用一個固定筆數保證。重現時可以用下面的小程式觀察當前環境,結果只代表這一次執行:
const values = Array.from({ length: 1_000_000 }, (_, index) => index);try { console.log(Math.max(...values));} catch (error) { console.log(error.name, error.message);}即使某個較小資料量能成功,對大小會持續增加的資料仍應改成逐項掃描。排序後取最後一項會做不需要的排序工作,sort() 還會修改原陣列;這個問題只需要找最大值。
2026-11-12 的本機驗證使用 macOS、Node.js 24.13.0:完整斷言程式通過,百萬筆展開範例出現 RangeError。這是正確性與錯誤重現檢查,沒有量測瀏覽器效能或尋找引擎參數上限。
參考資料: