先记住这个答案
货币格式不只是在数字前面加个符号。千分位用逗号还是点、小数保留几位、符号放前面还是后面、负数怎么写,每个国家、每种货币都有自己的习惯。Intl.NumberFormat 的 style: 'currency' 模式会根据 locale 和 currency 两个参数一起处理这些细节。currency 必须传 ISO 货币码,比如 USD、CNY 或 JPY,它不仅决定符号,还决定默认的小数位数,日元默认就是 0 位。手动 '$' + amount 既处理不了这些,也区分不了美元和加元这种都用 $ 的货币。
- 同时传
locale、style和 ISO 货币码 - 小数位跟着币种走,日元默认 0 位
- 同一个
$可能是美元也可能是加元,靠currency区分 - 格式化只管显示,不解决
0.1 + 0.2的精度问题
同一个数字,不同币种和地区
一个跨境电商页面,同样的价格数字要按用户的地区和结算币种显示。
const amount = 1234.5
const money = (locale, currency, extra = {}) =>
new Intl.NumberFormat(locale, { style: 'currency', currency, ...extra }).format(amount)
console.log(money('en-US', 'USD')) // 美元,补齐两位小数
console.log(money('zh-CN', 'CNY')) // 人民币
console.log(money('ja-JP', 'JPY')) // 日元没有小数,四舍五入
console.log(money('en-US', 'CAD')) // 加元,自动加上 CA 前缀区分
console.log(money('en-US', 'CAD', { currencyDisplay: 'narrowSymbol' }))查看输出与解释
$1,234.50
¥1,234.50
¥1,235
CA$1,234.50
$1,234.50日元那行值得注意:1234.5 被四舍五入成了 1235,因为日元的最小单位就是 1 元,格式化器默认按 0 位小数显示。如果你手写 toFixed(2),日本用户会看到奇怪的 1234.50。
加元那行,美国用户看到的是 CA$,因为在美国语境里单写 $ 就是美元。currencyDisplay: 'narrowSymbol' 强制只显示 $,适合空间紧凑、币种已经在别处写明的界面。另外两个取值里,code 显示成 CAD 1,234.50,而 name 显示成 1,234.50 Canadian dollars。
- 符号位置和分隔符也会变
- 换成德国用户看欧元,
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' })输出的是1.234,50 €。千分位变成了点,小数点变成了逗号,符号跑到了后面,中间还是一个不换行空格。这些规则如果自己写,每加一个市场就要改一次代码。
格式化只管显示,不管计算
Intl.NumberFormat 输出的是给人看的字符串。它解决不了浮点数计算误差,也不该拿去做后续计算。
const fmt = new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' })
console.log(0.1 + 0.2) // 浮点误差还在
console.log(fmt.format(0.1 + 0.2)) // 显示时被舍入掩盖了
// 推荐:金额用「分」存整数,显示前再除以 100
const priceInCents = 1999 + 2999
console.log(fmt.format(priceInCents / 100))查看输出与解释
0.30000000000000004
¥0.30
¥49.98第二行看起来没问题,是因为格式化器舍入到了两位小数,误差被藏起来了。但如果你拿格式化前的值继续算,累加几百次、再和后端对账,就可能差出一分钱。账务计算要么用整数的分,要么用 decimal.js 这类十进制库,格式化只在最后一步显示时做。 同样的道理,格式化后的字符串不要拿去排序、提交或者反向解析成数字。数据层始终保留原始数值和币种,视图层最后才格式化。
容易答错的地方
- 以为
locale能决定币种 zh-CN不代表人民币。style: 'currency'时currency是必填的,不传直接抛TypeError。一个中国用户看美元订单,就是locale为zh-CN、currency为 USD,两者是独立的。- 用
toFixed(2)统一处理小数 - 不是所有货币都有两位小数。日元、韩元是 0 位,科威特第纳尔是 3 位。让格式化器按币种的默认规则走;产品确实要求固定位数时,再显式设置
minimumFractionDigits和maximumFractionDigits。
面试官还会怎么问?
财务报表里负数要显示成括号,怎么做?
加 currencySign: 'accounting'。在 en-US 下,-5 美元会显示成 ($5.00),而不是默认的 -$5.00。是否支持括号写法取决于语言,有的语言会继续用负号。
怎么把货币符号单独加样式,比如写小一号?
用 formatToParts()。它返回 currency、integer、group、decimal、fraction 这些片段,给 type 为 currency 的那段套上样式,其余按原顺序输出。不要自己拆字符串,因为符号可能在前也可能在后。
列表里几百个价格,性能怎么优化?
把 formatter 实例建一次,重复用它的 format()。每次 new 都要解析语言数据和货币数据,放在渲染循环里开销很大。可以按 locale 加 currency 做一个简单的缓存 Map。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。