JS 数值千分位的 6 种方法 & 性能对比

3,876 阅读1分钟

数值千分位就是把 987654321.02 这种转换为 987,654,321.02 这种形式

1、数组转字符串遍历拼接

  1. 数字转字符串,并按照 . 分割
  2. 整数部分拆分成字符串数组:类似 [9, 3, 8, 7, 6, 5, 4, 3, 2]
  3. 遍历,按照每 3 位添加 , 号
  4. 拼接整数部分 + 小数部分
/**
 * 将数字格式化为千位分隔符的形式
 * @param number 要格式化的数字
 * @returns 格式化后的字符串
 */
function format(number) {
  // 将数字转为字符串,并按照小数点拆分
  const [int, fraction] = String(number).split('.')

  // 将整数部分拆分为数组
  const intArr = int.split('')

  let res = ''

  // 遍历整数部分的数组
  intArr.forEach((item, index) => {
    // 非第一位且是 3 的倍数,添加 ","
    if (index !== 0 && index % 3 === 0) {
      res = res + ',' + item
    } else {
      // 正常添加字符
      res = res + item
    }
  })

  // 整数和小数拼接
  return res + (!!fraction ? `.${fraction}` : '')
}

console.log(format(987654321.02)) // 输出 987,654,321.02

2、字符串+ substring截取

  1. 数字转字符串,并按照 . 分割
  2. 整数部分对3求模,获取多余部分
  3. 按照3截取 ,并添加 ,
  4. 拼接整数部分 + 小数部分
function format(number) {
  // 将数字转为字符串,并按照小数点拆分
  const [int, fraction] = String(number).split('.')

  // 计算整数部分多余的位数
  const f = int.length % 3

  // 截取多余的位数
  let res = int.substring(0, f)

  // 每三位添加 , 和对应的字符
  for (let i = 0; i < Math.floor(int.length / 3); i++) {
    res += ',' + int.substring(f + i * 3, f + (i + 1) * 3)
  }

  // 如果没有多余的位数,则截取最前面的 ,
  if (f === 0) {
    res = res.substring(1)
  }

  // 整数和小数拼接
  return res + (!!fraction ? `.${fraction}` : '')
}

console.log(format(987654321.02)) // 输出 987,654,321.02

3、除法 + 求模

  1. 值对 1000 求模,获得最高三位
  2. 值除以 1000,值是否大于 1 判定是否结束
  3. 重复1、2步,直到退出循环
  4. 拼接整数部分 + 小数部分
function format(number) {
  let n = number
  let temp
  let mod
  let r = ''

  // 循环处理整数部分
  do {
    // 获取后三位,可能有小数
    mod = n % 1000
    // 判断值是不是大于1,即三位数以上
    n = n / 1000
    // 相当于Math.floor() 去除小数部分
    temp = ~~mod

    // 如果 n >= 1 证明可千分位,在之前 % 1000 的时候 1001 会变成 1,所以需要使用 padStart 补0
    r = (n >= 1 ? `${temp}`.padStart(3, '0') : temp) + (!!r ? ',' + r : '')
  } while (n >= 1)

  // 处理小数部分
  const strNumber = String(number)
  const index = strNumber.indexOf('.')
  if (index >= 0) {
    r += strNumber.substring(index)
  }

  return r
}

console.log(format(987654321.02)) // 输出 987,654,321.02

4、正则先行断言

// 断言前面是 hello ,后面是 a-z
console.log(/hello (?=[a-z]+)/.test("hello a")); // true
console.log(/hello (?=[a-z]+)/.test("hello 1")); // false

// 方式一:
function format(number) {
  const strNumber = String(number)
  const index = strNumber.indexOf('.')
  let intStr = strNumber
  let fractionStr = ''

  // 处理整数部分
  if (index >= 0) {
    intStr = strNumber.substring(0, index)
    fractionStr = strNumber.substring(index)
  }

  // 将整数部分按照千位分隔符格式化
  let r = ''
  const reg = /\d{1,3}(?=(\d{3})+$)/g
  r = intStr.replace(reg, '$&,')

  return r + fractionStr
}

console.log(format(987654321)); // 987,654,321

5、Intl.NumberFormat

  • 基本功能:国际化的数字处理方案,它可以用来显示不同国家对数字的处理偏好
  • 属于国际化 API 规范:ECMA-402
  • 语法:new Intl.NumberFormat([locales[, options]])
// 方式一:设置初始参数,性能较差
function format(number, minimumFractionDigits, maximumFractionDigits) {
  minimumFractionDigits = minimumFractionDigits || 2
  maximumFractionDigits = maximumFractionDigits || 2
  maximumFractionDigits = Math.max(minimumFractionDigits, maximumFractionDigits)

  return new Intl.NumberFormat('en-us', {
    maximumFractionDigits: maximumFractionDigits || 2,
    minimumFractionDigits: minimumFractionDigits || 2
  }).format(number)
}

// 方式二:默认配置选项
function format(number) {
  return new Intl.NumberFormat('en-us').format(number)
}

console.log(format(987654321.02)) // 987,654,321.02

6、toLocalString

  • 功能:其能把数字转为特定语言环境下的表示字符串
  • 底层调用 Intl.NumberFormat
  • 语法:numObj.toLocaleString([locales [, options]])
function format(number, minimumFractionDigits, maximumFractionDigits) {
  minimumFractionDigits = minimumFractionDigits || 2
  maximumFractionDigits = maximumFractionDigits || 2
  maximumFractionDigits = Math.max(minimumFractionDigits, maximumFractionDigits)

  return number.toLocaleString('en-us', {
    maximumFractionDigits: maximumFractionDigits || 2,
    minimumFractionDigits: minimumFractionDigits || 2
  })
}

// 方式二:
function format(number) {
  return number.toLocaleString('en-us')
}

console.log(format(987654321.02)) // 987,654,321.02

性能比拼

这里测试了一万条数据,可以看出 除法+ 求模位运算 是其中最高效的方式

<!DOCTYPE html>
<html>
  <body>
    <div style="text-align: center">
      <p><input type="number" value="5000" id="textCount" /></p>
      <p>
        <input type="button" onclick="executeTest()" value="测试" />
        <input
          type="button"
          onclick="javascript:document.getElementById('messageEl').innerHTML=''"
          value="清除"
        />
      </p>
    </div>
    <div id="messageEl" style="width: 300px; margin: auto"></div>

    <script>
      function format_with_array(number) {
        const [int, fraction] = String(number).split('.')
        const intArr = int.split('')
        let res = ''
        intArr.forEach((item, index) => {
          if (index !== 0 && index % 3 === 0) {
            res = res + ',' + item
          } else {
            res = res + item
          }
        })
        return res + (!!fraction ? `.${fraction}` : '')
      }
    </script>

    <script>
      function format_with_substring(number) {
        const [int, fraction] = String(number).split('.')
        const f = int.length % 3
        let res = int.substring(0, f)
        for (let i = 0; i < Math.floor(int.length / 3); i++) {
          res += ',' + int.substring(f + i * 3, f + (i + 1) * 3)
        }
        if (f === 0) {
          res = res.substring(1)
        }
        return res + (!!fraction ? `.${fraction}` : '')
      }
    </script>

    <script>
      function format_with_mod(number) {
        let n = number
        let temp
        let mod
        let r = ''
        do {
          mod = n % 1000
          n = n / 1000
          temp = ~~mod
          r = (n >= 1 ? `${temp}`.padStart(3, '0') : temp) + (!!r ? ',' + r : '')
        } while (n >= 1)
        const strNumber = String(number)
        const index = strNumber.indexOf('.')
        if (index >= 0) {
          r += strNumber.substring(index)
        }
        return r
      }
    </script>

    <script>
      function format_with_regex(number) {
        const strNumber = String(number)
        const index = strNumber.indexOf('.')
        let intStr = strNumber
        let fractionStr = ''
        if (index >= 0) {
          intStr = strNumber.substring(0, index)
          fractionStr = strNumber.substring(index)
        }
        let r = ''
        const reg = /\d{1,3}(?=(\d{3})+$)/g
        r = intStr.replace(reg, '$&,')

        return r + fractionStr
      }
    </script>

    <script>
      // function format_with_toLocaleString(number, minimumFractionDigits, maximumFractionDigits) {
      //     minimumFractionDigits = minimumFractionDigits || 2;
      //     maximumFractionDigits = (maximumFractionDigits || 2);
      //     maximumFractionDigits = Math.max(minimumFractionDigits, maximumFractionDigits);

      //     return number.toLocaleString("en-us", {
      //         maximumFractionDigits: maximumFractionDigits || 2,
      //         minimumFractionDigits: minimumFractionDigits || 2
      //     })
      // }

      function format_with_toLocaleString(number) {
        return number.toLocaleString('en-us')
      }
    </script>

    <script>
      // function format_with_Intl(number, minimumFractionDigits, maximumFractionDigits) {
      //     minimumFractionDigits = minimumFractionDigits || 2;
      //     maximumFractionDigits = (maximumFractionDigits || 2);
      //     maximumFractionDigits = Math.max(minimumFractionDigits, maximumFractionDigits);

      //     return new Intl.NumberFormat('en-us', {
      //         maximumFractionDigits: maximumFractionDigits || 2,
      //         minimumFractionDigits: minimumFractionDigits || 2
      //     }).format(number)
      // }

      const format = new Intl.NumberFormat('en-us')

      function format_with_Intl(number) {
        return format.format(number)
      }
    </script>

    <script>
      function getData(count) {
        const data = new Array(count).fill(0).map(function (i) {
          const rd = Math.random()
          let r = rd * Math.pow(10, Math.trunc(Math.random() * 12))
          if (rd > 0.5) {
            r = ~~r
          }
          return r
        })
        return data
      }

      function test(data, fn, label) {
        const start = performance.now()
        for (let i = 0; i < data.length; i++) {
          fn(data[i])
        }
        const time = performance.now() - start

        message((fn.name || label) + ':' + time.toFixed(2) + 'ms')
      }

      function executeTest() {
        const data = getData(+textCount.value)
        test(data, format_with_array)
        test(data, format_with_mod)
        test(data, format_with_substring)
        test(data, format_with_regex)
        test(data, format_with_toLocaleString)
        test(data, format_with_Intl)
        message('-------------------')
      }

      function message(msg) {
        const el = document.createElement('p')
        el.innerHTML = msg
        messageEl.appendChild(el)
      }
    </script>
  </body>
</html>