紀錄工作經驗、相關知識,解決技術相關問題。

Javascript, React, 新手 Jamp Holes, 網站相關

JS 數字加逗號 / 轉換千分位顯示(toLocaleString 與正規表達式)

JS 數字加逗號 / 轉換千分位顯示(toLocaleString 與正規表達式) 特色圖片

在前端處理金額或統計報表時,最常遇到的需求便是將數字加逗號與轉換千分位顯示(每三位數加上逗號 ,)。若數值在顯示前需要先經過折扣或無條件進位計算,可先透過 JavaScript Math.ceil 無條件進位 處理數值,再選用以下 3 種方法轉換為千分位字串。


一、 3 種 JS 數字轉千分位方法比較表

在選擇千分位轉換方式時,可依照「是否需要傳入字串」、「是否需要指定幣別與小數位數」以及「大量資料渲染效能」來決定:

轉換方法支援直接傳入數字字串支援幣別與小數位數控制適用情境
Number.prototype.toLocaleString()否(需先轉為 Number)是(透過 options 參數)單筆數值快速轉千分位顯示
Intl.NumberFormat是(自動轉型數值字串)是(內建 currency 與位數控制)大量列表渲染、多國幣別與小數補零
正規表達式(RegExp + replace)是(直接處理字串)否(需額外拆分整數與小數)純字串處理或特殊格式自訂替換

二、 分項語法拆解與程式碼範本

1. 使用 toLocaleString() 轉換千分位/加逗號

toLocaleString() 是最直觀的內建方法,但需特別注意它只對 Number 型別有效。若直接對字串 '9999999' 呼叫 String.prototype.toLocaleString(),並不會加上千分位逗號:

// 1. 傳入 Number 型別(正常加上千分位逗號)
const num = 9999999;
console.log(num.toLocaleString());
// 輸出: "9,999,999"

// 2. 傳入 String 型別(不會轉換千分位!)
const numStr = '9999999';
console.log(numStr.toLocaleString());
// 輸出: "9999999"

為了避免後端 API 傳回數字字串導致千分位失效,建議封裝一個包含 Number() 轉型與 Number.isNaN() 防呆的函式(使用 Number() 而非 parseInt(),才不會把小數點後的金額截斷):

const thousandthsFormat = (value) => {
  if (value === null || value === undefined || String(value).trim() === '') {
    return '';
  }
  const num = Number(value);
  if (Number.isNaN(num)) return '';
  return num.toLocaleString('en-US');
};

console.log(thousandthsFormat(9999999));     // 輸出: "9,999,999"
console.log(thousandthsFormat('9999999.5')); // 輸出: "9,999,999.5"
console.log(thousandthsFormat('abcd'));      // 輸出: ""

2. 使用 Intl.NumberFormat 轉換千分位與指定幣別

Intl.NumberFormat 除了可以直接將「數字」或「純數字字串」轉換為千分位顯示外,在迴圈渲染上千筆資料時,重複使用同一個 Intl.NumberFormat 實例的效能也顯著優於重複呼叫 toLocaleString():

const formatter = new Intl.NumberFormat('en-US');

const number1 = 123456.789;
console.log(formatter.format(number1));
// 輸出: "123,456.789"

const number2 = '123456.789';
console.log(formatter.format(number2));
// 輸出: "123,456.789"

使用 Intl.NumberFormat 的另一個優勢,是能直接透過參數指定幣別符號(style: 'currency')或固定保留小數點後補零位數(minimumFractionDigits,亦可參考 JavaScript 數字後面補 0(Trailing Zero)):

const number = 123456.789;

// 1. 新臺幣 (TWD) 顯示(預設保留 2 位小數並四捨五入)
const twd = new Intl.NumberFormat('zh-TW', {
  style: 'currency',
  currency: 'TWD',
}).format(number);
console.log(twd); // 輸出: "$123,456.79" 或 "NT$123,456.79"

// 2. 日圓 (JPY) 顯示(日圓無小數分位,自動取整至整數)
const jpy = new Intl.NumberFormat('ja-JP', {
  style: 'currency',
  currency: 'JPY',
}).format(number);
console.log(jpy); // 輸出: "¥123,457"

// 3. 純千分位且強制保留小數點後 2 位(不足補 0)
const fixed2 = new Intl.NumberFormat('en-US', {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
}).format(1234.5);
console.log(fixed2); // 輸出: "1,234.50"

更多國際化參數設定可參照 MDN Web Docs: Intl.NumberFormat。

3. 使用正規表達式(RegExp)轉換千分位/加逗號

若需要跨環境處理純字串,或避免超長數字(超過 16 位數 Number.MAX_SAFE_INTEGER)在轉為 Number 時發生尾數精度遺失,可採用正規表達式結合 String.prototype.replace()。

需要注意的是,若數字包含小數點超過 3 位(如 1234567.8912),直接對整串字元套用 /\B(?=(\d{3})+(?!\d))/g 會連小數點後方的數字也加上逗號。因此建議先以 .split('.') 拆開整數與小數部分,僅針對整數部分加上逗號:

const thousandthsRegexFormat = (value) => {
  if (value === null || value === undefined) return '';
  const parts = String(value).trim().split('.');
  // 僅針對小數點前的整數部分,每 3 位數插入逗號
  parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  return parts.join('.');
};

console.log(thousandthsRegexFormat(1234567.89));   // 輸出: "1,234,567.89"
console.log(thousandthsRegexFormat(1234567.8912)); // 輸出: "1,234,567.8912"(小數點後不會誤加逗號)

三、 常見問題與排錯 Q&A

Q1:使用者在 input 輸入框填入千分位金額後,要如何還原回純數字送給後端?

  • 問題原因:帶有逗號的字串(如 "1,234,567")若直接傳給 Number("1,234,567") 會回傳 NaN;若傳給 parseInt("1,234,567") 則只會讀到第一個逗號前的 1。
  • 解決思路:在進行數學計算或送出表單前,先使用 str.replace(/,/g, '') 移除所有逗號,再轉為 Number(表單數字驗證可搭配 Input RegExp 表單驗證 — 只輸入數字)。

Q2:為什麼 toLocaleString() 顯示的小數位數最多只有 3 位?

  • 問題原因:根據 ECMA-402 國際化規範,toLocaleString() 與 Intl.NumberFormat 的 maximumFractionDigits 預設值為 3,超過第 3 位的小數會自動被四捨五入。
  • 解決思路:若需保留更多位小數,可在第二個參數傳入 { maximumFractionDigits: 6 } 明確指定上限。

參考資料與延伸閱讀

發佈留言