Intl.NumberFormat API
使用 JavaScript 内置的国际化 API 按照任意本地化格式化数字
简介
假设你需要在应用中显示数字 1234567.89。如果直接使用 toString(),会得到 "1234567.89",这种格式难以阅读,并且假定所有人都用句点作为小数点且从左到右读数字。美国用户期望 "1,234,567.89",德国用户期望 "1.234.567,89",而印度用户则期望 "12,34,567.89",每个地区的分组规则都不同。
Intl.NumberFormat API 通过根据本地化习惯格式化数字来解决这个问题。它会自动处理千位分隔符、小数点、数字分组、货币符号、百分号、计量单位和数字系统,无需手动字符串处理或第三方库。
本指南将介绍如何使用 Intl.NumberFormat,从基础用法到进阶功能(如货币格式、紧凑表示法和自定义舍入模式),帮助你为全球用户正确格式化数字。
使用默认设置格式化数字
通过调用 new Intl.NumberFormat() 并传入本地化字符串来创建格式化器,然后使用其 format() 方法格式化数字。
const formatter = new Intl.NumberFormat('en-US');
formatter.format(1234567.89);
// "1,234,567.89"
格式化器会根据本地化自动添加千位分隔符并格式化小数点。如果未指定本地化,格式化器会使用运行环境的默认本地化,通常基于用户的系统设置。
const formatter = new Intl.NumberFormat('de-DE');
formatter.format(1234567.89);
// "1.234.567,89"
德国的习惯是用句点作为千位分隔符,用逗号表示小数点,这与美国的习惯正好相反。格式化器会自动处理这些差异。
了解本地化代码
本地化代码用于标识一种语言及可选的地区,格式为 language-REGION。语言部分采用两位 ISO 639-1 代码,如 en 或 es。地区部分采用两位 ISO 3166-1 代码,如 US 或 MX。
new Intl.NumberFormat('en-US').format(1234.56);
// "1,234.56" (American English)
new Intl.NumberFormat('en-GB').format(1234.56);
// "1,234.56" (British English)
new Intl.NumberFormat('es-ES').format(1234.56);
// "1234,56" (European Spanish)
new Intl.NumberFormat('es-MX').format(1234.56);
// "1,234.56" (Mexican Spanish)
两种英语变体使用相同的格式,但西班牙语变体有所不同。欧洲西班牙语在四位数时不使用千位分隔符,小数用逗号分隔,而墨西哥西班牙语遵循美式格式。
根据用户的地理位置或语言偏好选择 locale。应用程序通常通过用户设置、浏览器语言或 IP 地理定位来确定 locale。
选择格式化样式
style 选项用于确定格式化类别。请将 options 对象作为构造函数的第二个参数传递。
new Intl.NumberFormat('en-US', {
style: 'decimal'
}).format(1234.56);
// "1,234.56"
decimal 样式为默认值。其他样式包括 currency、percent 和 unit。
使用符号和代码格式化货币
currency 样式需要一个带有 ISO 4217 货币代码的 currency 选项。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(1234.56);
// "$1,234.56"
格式化器默认会添加美元符号,并保留两位小数,这是大多数货币的标准。不同的 locale 会有不同的货币符号位置。
new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(1234.56);
// "1.234,56 €"
德语格式会在金额后加上欧元符号并留有空格。currency 选项决定显示哪种货币,而不是采用哪个 locale 的格式规范。locale 决定格式规范,currency 决定货币符号。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(1234.56);
// "€1,234.56"
美式格式规范下,欧元符号会出现在金额前,遵循美式而非欧洲的符号位置规则。
控制货币显示格式
currencyDisplay 选项可更改货币在格式化字符串中的显示方式。
const amount = 1234.56;
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'symbol'
}).format(amount);
// "$1,234.56"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'code'
}).format(amount);
// "USD 1,234.56"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'name'
}).format(amount);
// "1,234.56 US dollars"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'narrowSymbol'
}).format(amount);
// "$1,234.56"
symbol 选项为默认值,会显示货币符号,如 $ 或 €。code 选项会显示三字母货币代码。name 选项会拼写出货币名称。narrowSymbol 选项会使用 locale 的窄货币符号,虽然可能不唯一,但可以节省空间。
当涉及到具有相同货币符号的货币时,symbol 和 narrowSymbol 之间的区别就会变得明显。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'CAD',
currencyDisplay: 'symbol'
}).format(100);
// "CA$100.00"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'CAD',
currencyDisplay: 'narrowSymbol'
}).format(100);
// "$100.00"
加拿大元在使用 symbol 选项时会显示为 CA$,以便与美元区分;而使用 narrowSymbol 时则仅显示为 $。
使用会计记账法格式化负数货币金额
currencySign 选项用于控制负数金额的显示方式。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencySign: 'standard'
}).format(-1234.56);
// "-$1,234.56"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencySign: 'accounting'
}).format(-1234.56);
// "($1,234.56)"
standard 选项为默认值,使用负号表示负数金额。accounting 选项则将负数金额用括号括起来,符合会计惯例。这使得财务报表中的负数更加醒目。
百分比格式化
percent 样式会将数字乘以 100 并添加百分号。
new Intl.NumberFormat('en-US', {
style: 'percent'
}).format(0.1234);
// "12%"
new Intl.NumberFormat('en-US', {
style: 'percent'
}).format(0.1256);
// "13%"
格式化器默认会四舍五入到最接近的整数。可以通过数字选项控制小数位数。
new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2
}).format(0.1234);
// "12.34%"
请传入百分比的小数形式,而不是已乘以 100 的值。格式化器会自动处理乘法。
带单位的数值格式化
unit 样式需要一个带有单位标识符的 unit 选项。
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer-per-hour'
}).format(100);
// "100 km/h"
new Intl.NumberFormat('en-GB', {
style: 'unit',
unit: 'mile-per-hour'
}).format(100);
// "100 mph"
可用单位包括长度(meter、kilometer、mile)、时间(second、minute、hour)、数字存储(byte、kilobyte、megabyte)、温度(celsius、fahrenheit)等。复合单位如 kilometer-per-hour 则通过连字符组合简单单位。
unitDisplay 选项用于控制单位的格式。
const distance = 1234.5;
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer',
unitDisplay: 'long'
}).format(distance);
// "1,234.5 kilometers"
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer',
unitDisplay: 'short'
}).format(distance);
// "1,234.5 km"
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer',
unitDisplay: 'narrow'
}).format(distance);
// "1,234.5km"
long 选项会完整拼写单位名称。short 选项使用缩写。narrow 选项采用最简形式,可能存在歧义。
使用紧凑记数法显示大数字
notation 选项会改变数字的表达方式。compact 值会针对大数字使用特定语言环境的简写形式。
new Intl.NumberFormat('en-US', {
notation: 'compact'
}).format(1234567);
// "1.2M"
new Intl.NumberFormat('en-US', {
notation: 'compact'
}).format(987654321);
// "988M"
紧凑记数法默认保留一位小数,并为千位 K、百万 M 和十亿 B 添加后缀。这种格式常见于社交媒体粉丝数、视频播放量和分析仪表盘。
compactDisplay 选项用于控制后缀长度。
new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'short'
}).format(1234567);
// "1.2M"
new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'long'
}).format(1234567);
// "1.2 million"
short 选项为默认值,使用符号。long 选项会拼写数量级单词。不同语言环境会采用不同的后缀。
new Intl.NumberFormat('zh-CN', {
notation: 'compact'
}).format(123456789);
// "1.2亿"
中文使用 亿 表示亿,反映了中文的数字分组体系。
用科学计数法表示极大或极小的数字
scientific 记数法将数字表示为系数与 10 的幂的乘积。
new Intl.NumberFormat('en-US', {
notation: 'scientific'
}).format(123456789);
// "1.235E8"
new Intl.NumberFormat('en-US', {
notation: 'scientific'
}).format(0.00000123);
// "1.23E-6"
这种格式适用于极大的数字(如天文距离、分子数量)和极小的数字(如粒子质量、纳米级测量)。指数始终以 1 的倍数出现。
在技术应用中使用工程计数法
engineering 记数法类似于科学计数法,但指数限定为 3 的倍数。
new Intl.NumberFormat('en-US', {
notation: 'engineering'
}).format(123456789);
// "123.457E6"
new Intl.NumberFormat('en-US', {
notation: 'engineering'
}).format(1234);
// "1.234E3"
工程计数法与 SI 单位前缀(kilo、mega、giga)对齐,因此在工程和物理领域是标准做法。系数范围为 1 到 999。
使用小数位数控制小数精度
minimumFractionDigits 和 maximumFractionDigits 选项用于控制小数点后显示的位数。
new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(1234.5);
// "1,234.50"
new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(1234.567);
// "1,234.57"
最小值确保在需要时补零,最大值则会对较长的小数进行四舍五入。货币格式化默认保留两位小数,数字格式化默认最小为零位,最大为三位。
new Intl.NumberFormat('en-US', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(1234.567);
// "1,235"
将两者都设置为零时,会四舍五入到最接近的整数。
使用有效数字控制整体精度
minimumSignificantDigits 和 maximumSignificantDigits 选项用于控制总精度,与小数点位置无关。
new Intl.NumberFormat('en-US', {
minimumSignificantDigits: 3,
maximumSignificantDigits: 3
}).format(1234.567);
// "1,230"
new Intl.NumberFormat('en-US', {
minimumSignificantDigits: 3,
maximumSignificantDigits: 3
}).format(0.001234);
// "0.00123"
有效数字统计除前导零外的所有数字。第一个示例会四舍五入到三位有效数字,结果为 1230。第二个示例会保留前导零后的三位数字,结果为 0.00123。
当同时指定有效数字和小数位数选项时,有效数字选项优先生效。
通过舍入模式选择舍入策略
roundingMode 选项决定在需要截断时数字的舍入方式。
const value = 1.5;
new Intl.NumberFormat('en-US', {
maximumFractionDigits: 0,
roundingMode: 'halfExpand'
}).format(value);
// "2"
new Intl.NumberFormat('en-US', {
maximumFractionDigits: 0,
roundingMode: 'halfTrunc'
}).format(value);
// "1"
halfExpand 模式为默认值,会将 0.5 向零的远离方向舍入,这是学校常用的舍入策略。halfTrunc 模式则将 0.5 向零的方向舍入。
其他模式包括:
ceil:始终向正无穷舍入floor:始终向负无穷舍入expand:始终远离零舍入trunc:始终向零舍入halfCeil:0.5 向正无穷舍入halfFloor:0.5 向负无穷舍入halfEven:0.5 向最接近的偶数舍入
const prices = [1.5, 2.5, 3.5];
prices.map(price =>
new Intl.NumberFormat('en-US', {
maximumFractionDigits: 0,
roundingMode: 'halfEven'
}).format(price)
);
// ["2", "2", "4"]
halfEven 模式,也称为银行家舍入法,可减少重复计算中的舍入偏差。当对 0.5 进行舍入时,会选择最接近的偶数。这会使 2 同时适用于 1.5 和 2.5,但 4 适用于 3.5。
金融应用中,ceil 用于向上舍入费用,floor 用于向下舍入退款。统计应用中,halfEven 用于最小化累计舍入误差。
通过分组选项控制千位分隔符
useGrouping 选项用于控制是否显示千位分隔符。
new Intl.NumberFormat('en-US', {
useGrouping: true
}).format(123456);
// "123,456"
new Intl.NumberFormat('en-US', {
useGrouping: false
}).format(123456);
// "123456"
true 是默认值。false 值会移除所有分隔符。字符串值可实现更精细的控制。
new Intl.NumberFormat('en-US', {
useGrouping: 'always'
}).format(1234);
// "1,234"
new Intl.NumberFormat('en-US', {
useGrouping: 'min2'
}).format(1234);
// "1234"
always 值在所有情况下都使用分隔符。min2 值会在四位数字时省略分隔符。auto 值遵循本地化偏好,通常与 min2 行为一致。
紧凑表示法默认采用 min2,因为紧凑数字很少需要内部分隔符。
通过符号显示选项显式显示正负号
signDisplay 选项用于控制何时显示正负号。
new Intl.NumberFormat('en-US', {
signDisplay: 'auto'
}).format(100);
// "100"
new Intl.NumberFormat('en-US', {
signDisplay: 'always'
}).format(100);
// "+100"
auto 是默认值,负数显示负号,正数不显示正号。always 值则两者都显示。
new Intl.NumberFormat('en-US', {
signDisplay: 'exceptZero'
}).format(0);
// "0"
new Intl.NumberFormat('en-US', {
signDisplay: 'always'
}).format(0);
// "+0"
exceptZero 值即使在 always 行为下也会对零值省略符号。这可以避免 +0 和 -0 的混淆显示。
new Intl.NumberFormat('en-US', {
signDisplay: 'never'
}).format(-100);
// "100"
never 值会完全移除符号,仅显示绝对值。
金融应用使用 always 通过加号突出显示收益。温度显示使用 exceptZero 避免显示 +0° 或 -0°。
将格式化输出拆分为多个部分
formatToParts() 方法返回一个对象数组,每个对象代表格式化字符串的一个组成部分。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatToParts(1234.56);
返回结果为:
[
{ type: 'currency', value: '$' },
{ type: 'integer', value: '1' },
{ type: 'group', value: ',' },
{ type: 'integer', value: '234' },
{ type: 'decimal', value: '.' },
{ type: 'fraction', value: '56' }
]
每个对象包含一个 type 用于标识组件,以及一个 value 存储字符串。type 可以是 currency、integer、group、decimal、fraction、literal、minusSign、plusSign、percentSign 或其他。
这样可以对各个组件进行自定义样式。
const parts = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatToParts(1234.56);
const formatted = parts.map(part => {
if (part.type === 'currency') {
return `<span class="currency">${part.value}</span>`;
}
if (part.type === 'integer') {
return `<span class="integer">${part.value}</span>`;
}
return part.value;
}).join('');
// <span class="currency">$</span><span class="integer">1</span>,<span class="integer">234</span>.56
这会生成带有样式组件的 HTML。相同方法也适用于为负数金额设置不同颜色、放大货币符号或为单个数字添加动画。
格式化数字区间
formatRange() 方法可将两个数字格式化为区间。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatRange(100, 200);
// "$100.00 – $200.00"
格式化器会使用特定于本地的区间分隔符(英文中为短横线),并包含两个货币符号。如果两个值格式化后字符串相同,则格式化器返回带有波浪号的单个值。
new Intl.NumberFormat('en-US', {
notation: 'compact'
}).formatRange(1200, 1800);
// "~1K"
1200 和 1800 在紧凑表示法下都格式化为 1K,因此格式化器会显示大约 1K。
formatRangeToParts() 方法返回区间的各个部分。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatRangeToParts(100, 200);
返回结果为:
[
{ type: 'currency', value: '$', source: 'startRange' },
{ type: 'integer', value: '100', source: 'startRange' },
{ type: 'decimal', value: '.', source: 'startRange' },
{ type: 'fraction', value: '00', source: 'startRange' },
{ type: 'literal', value: ' – ', source: 'shared' },
{ type: 'currency', value: '$', source: 'endRange' },
{ type: 'integer', value: '200', source: 'endRange' },
{ type: 'decimal', value: '.', source: 'endRange' },
{ type: 'fraction', value: '00', source: 'endRange' }
]
source 属性用于标识该部分是来自起始值、结束值还是区间分隔符。
价格区间、日期区间和测量区间都会使用此方法,以实现符合本地化的格式化。
检查已解析的选项
resolvedOptions() 方法会返回一个对象,显示格式化器实际使用的选项。
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
formatter.resolvedOptions();
返回结果为:
{
locale: 'en-US',
numberingSystem: 'latn',
style: 'currency',
currency: 'USD',
currencyDisplay: 'symbol',
currencySign: 'standard',
minimumIntegerDigits: 1,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
useGrouping: 'auto',
notation: 'standard',
signDisplay: 'auto',
roundingMode: 'halfExpand'
}
该对象包含显式设置的选项和默认值。如果系统解析为不同但兼容的区域设置,locale 可能与请求的区域设置不同。numberingSystem 显示格式化器使用的数字字符。
此方法通过展示所有激活的设置,有助于调试意外的格式化结果。
复用格式化器以提升性能
创建 NumberFormat 实例时会加载本地化数据并处理选项。当需要用相同设置格式化多个值时,建议复用实例。
// Inefficient: creates new formatter for each value
prices.map(price =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(price)
);
// Efficient: creates formatter once
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
prices.map(price => formatter.format(price));
在格式化大量数据时,第二种方式明显更快。请在循环和组件渲染函数外部创建格式化器。
现代 JavaScript 引擎会在内部缓存 NumberFormat 实例,但显式复用能带来更好的性能和更清晰的代码。
检查浏览器支持情况
Intl.NumberFormat API 已在所有现代浏览器中获得支持。Chrome、Firefox、Safari 和 Edge 自 2016 年起支持基础 API。像 formatRange()、formatRangeToParts() 以及扩展的 roundingMode 选项等高级功能,近年也已在当前浏览器版本中支持。
可通过以下方式检查支持情况:
if (typeof Intl !== 'undefined' && Intl.NumberFormat) {
// NumberFormat is supported
const formatter = new Intl.NumberFormat('en-US');
}
检查特定功能支持:
const formatter = new Intl.NumberFormat('en-US');
if (typeof formatter.formatRange === 'function') {
// formatRange is supported
}
需要支持旧版浏览器的应用可以使用如 @formatjs/intl-numberformat 这样的 polyfill,但大多数现代应用可以直接使用原生 API,无需回退方案。
何时使用 NumberFormat
在以下场景使用 Intl.NumberFormat:
- 在任何 UI 场景下向用户展示数字
- 在电商应用中格式化货币金额
- 在分析仪表盘中显示百分比
- 展示粉丝数、浏览量等社交指标
- 格式化度量值和科学数值
- 构建支持多语言环境的国际化应用
不要在以下场景使用 Intl.NumberFormat:
- 内部计算或数据处理
- 在数据库中存储数值
- 为 API 序列化数据
- 将用户输入解析回数字
NumberFormat 是一个展示层工具。请在计算和存储时保留原始数值,仅在向用户展示时进行格式化。