Skip to content

在JavaScript中格式化数字值

数字格式化是在编写网页代码时最常见的展示任务之一。虽然有一些内置的方法,但通常需要为特定的用例自己编写解决方案。让我们探讨一些常见的场景以及如何处理它们。

[!NOTE]

你可能不熟悉JavaScript的数字分隔符,它们在下面的示例中使用。它们是使大型数字值更易读的语法糖

不带尾随零的定点表示法

定点表示法中,一个数字用小数点后的固定位数表示。然而,我们经常希望从结果中去除尾随零

为了做到这一点,我们可以使用Number.prototype.toFixed()将数字转换为定点表示法字符串。然后,使用Number.parseFloat()将定点表示法字符串转换为数字,去除尾随零。最后,我们可以使用模板字面量将数字转换为字符串。

const toOptionalFixed = (num, digits) =>
  `${Number.parseFloat(num.toFixed(digits))}`;

toOptionalFixed(1, 2); // '1'
toOptionalFixed(1.001, 2); // '1'
toOptionalFixed(1.500, 2); // '1.5'

将数字四舍五入到指定精度

将数字四舍五入到特定的小数位数是非常常见的。我们可以使用Math.round()和模板字面量将数字四舍五入到指定的位数。省略第二个参数decimals将四舍五入到整数。

const round = (n, decimals = 0) =>
  Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`);

round(1.005, 2); // 1.01

[!NOTE]

此函数返回一个数字,而不是字符串。这是有意的,因为我们可能希望在进一步的计算中使用四舍五入后的数字。

格式化持续时间

将一定数量的毫秒转换为可读格式只需要将该数字除以适当的值,并为每个值创建一个字符串。在下面的代码片段中,我们将使用一个包含dayhourminutesecondmillisecond适当值的对象,但您可以根据不同的需求进行适当的调整。

我们可以使用Object.entries()Array.prototype.filter()来保留非零值。然后,我们可以使用Array.prototype.map()为每个值创建字符串,适当地进行复数化。最后,我们可以使用Array.prototype.join()将这些值组合成一个字符串。

const formatDuration = ms => {
  if (ms < 0) ms = -ms;
  const time = {
    day: Math.floor(ms / 86_400_000),
    hour: Math.floor(ms / 3_600_000) % 24,
    minute: Math.floor(ms / 60_000) % 60,
    second: Math.floor(ms / 1_000) % 60,
    millisecond: Math.floor(ms) % 1_000
  };
  return Object.entries(time)
    .filter(val => val[1] !== 0)
    .map(([key, val]) => `${val} ${key}${val !== 1 ? 's' : ''}`)
    .join(', ');
};

formatDuration(1_001);
// '1 second, 1 millisecond'
formatDuration(34_325_055_574);
// '397 days, 6 hours, 44 minutes, 15 seconds, 574 milliseconds'

秒数转ISO格式

同样,将秒数格式化为ISO格式也只需要几次除法运算。然而,我们需要单独处理符号

我们可以使用Array.prototype.map()Math.floor()String.prototype.padStart()结合使用,将每个段转换为字符串。最后,我们可以使用Array.prototype.join()将值组合成一个字符串。

const formatSeconds = s => {
  const [hour, minute, second, sign] =
    s > 0
      ? [s / 3_600, (s / 60) % 60, s % 60, '']
      : [-s / 3_600, (-s / 60) % 60, -s % 60, '-'];

  return (
    sign +
    [hour, minute, second]
      .map(v => `${Math.floor(v)}`.padStart(2, '0'))
      .join(':')
  );
};

formatSeconds(200); // '00:03:20'
formatSeconds(-200); // '-00:03:20'
formatSeconds(99_999); // '27:46:39'

区域敏感的数字格式化

使用Number.prototype.toLocaleString()也可以轻松地将数字格式化为区域敏感的字符串。该方法允许我们使用本地数字格式分隔符格式化数字。

const formatNumber = num => num.toLocaleString();

formatNumber(123_456); // 在`en-US`中为'123,456'
formatNumber(15_675_436_903); // 在`de-DE`中为'15.675.436.903'

数字到小数点

如果我们想将数字格式化为特定的区域设置,可以将区域设置作为第一个参数传递给Number.prototype.toLocaleString()。例如,以下是如何使用en-US区域设置将数字格式化为使用小数点的方式。

const toDecimalMark = num => num.toLocaleString('en-US');

toDecimalMark(12_305_030_388.9087); // '12,305,030,388.909'

数字转换为货币字符串

在处理货币时,使用适当的格式非常重要。幸运的是,JavaScript的Intl.NumberFormat使得这一点变得很容易,它允许我们将数字格式化为货币字符串。我们只需要指定货币和语言格式,以及style: 'currency'

const toCurrency = (n, curr, LanguageFormat = undefined) =>
  Intl.NumberFormat(LanguageFormat, {
    style: 'currency',
    currency: curr,
  }).format(n);

toCurrency(123_456.789, 'EUR');
// €123,456.79  | 货币:欧元 | 货币语言格式:本地
toCurrency(123_456.789, 'USD', 'en-us');
// $123,456.79  | 货币:美元 | 货币语言格式:英语(美国)
toCurrency(123_456.789, 'USD', 'fa');
// ۱۲۳٬۴۵۶٫۷۹ ؜$ | 货币:美元 | 货币语言格式:波斯语
toCurrency(322_342_436_423.2435, 'JPY');
// ¥322,342,436,423 | 货币:日元 | 货币语言格式:本地
toCurrency(322_342_436_423.2435, 'JPY', 'fi');
// 322 342 436 423 ¥ | 货币:日元 | 货币语言格式:芬兰语

数字转换为序数后缀

将数字转换为其序数形式(例如,1转换为1st2转换为2nd3转换为3rd等)也相当简单,使用Intl.PluralRules。我们可以使用Intl.PluralRules.prototype.select()来获取数字的正确复数形式,然后使用一个查找字典来获取正确的后缀。为了产生正确的结果,我们需要在构造对象时指定正确的区域设置和type: 'ordinal'选项。

const ordinalsEnUS = {
  one: 'st',
  two: 'nd',
  few: 'rd',
  many: 'th',
  zero: 'th',
  other: 'th',
};

const toOrdinalSuffix = (num, locale = 'en-US', ordinals = ordinalsEnUS) => {
  const pluralRules = new Intl.PluralRules(locale, { type: 'ordinal' });
  return `${num}${ordinals[pluralRules.select(num)]}`;
};

## 添加序数后缀

有时候,我们需要将数字转换为带有序数后缀的字符串。我们可以使用以下函数来实现:

```js
const toOrdinalSuffix = (n) => {
  const suffixes = ['th', 'st', 'nd', 'rd'];
  const v = n % 100;
  return `${n}${suffixes[(v - 20) % 10] || suffixes[v] || suffixes[0]}`;
};

toOrdinalSuffix(1); // '1st'
toOrdinalSuffix(2); // '2nd'
toOrdinalSuffix(3); // '3rd'
toOrdinalSuffix(4); // '4th'
toOrdinalSuffix(123); // '123rd'

使用前导零填充数字

最后,有时候我们需要将数字填充为指定长度的字符串,并在前面添加零。我们可以使用 String.prototype.padStart() 将数字转换为字符串后,再进行填充。

const padNumber = (n, l) => `${n}`.padStart(l, '0');

padNumber(1_234, 6); // '001234'