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

lxf2023-03-18 20:10:01

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

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

  1. 数字转字符串,并按照 . 分割
  2. 整数部分拆分成字符串数组:类似 [9, 3, 8, 7, 6, 5, 4, 3, 2]
  3. 遍历,按照每 3 位添加 , 号
  4. 拼接整数部分 + 小数部分
function format(number) {
  // 转为字符串,并按照 . 拆分
  const arr = String(number).split(".");

  // 整数拆分
  const int = arr[0].split("");

  // 小数
  const fraction = arr[1] || "";

  let res = "";

  int.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 arr = String(number).split(".");
  const int = arr[0];
  const fraction = arr[1] || "";

  // 多余的位数
  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 r = "";
  do {
    // 求模的值,用于获取高三位,这里可能有小数
    mod = n % 1000;
    // 值是不是大于1,是继续的条件
    n = n / 1000;
    // 高三位
    temp = ~~mod;
    // 1. 填充 : n>1 循环未结束, 就要填充为比如,1 => 001,
    // 不然 1 001, 就会变成 '11',
    // 2. 拼接 ","
    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

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

// 方式一:
function format(number) {
  // 三位数字进行分组,查找前面有 1 到 3 位的数字
  const reg = /\d{1,3}(?=(\d{3})+$)/g;
  // $&, 也可换成 $1, 表示被匹配到的内容
  return String(number).replace(reg, "$&,");
}

// 方式二:
function format(number) {
  const reg = /\d{1,3}(?=(\d{3})+$)/g;
  // match 为匹配之后的内容
  return String(number).replace(reg, function (match) {
    return match + ",";
  });
}

// 此方法不能处理小数
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

性能比拼

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

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

<!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 arr = String(number).split(".");
        const int = arr[0].split("");
        const fraction = arr[1] || "";
        let res = "";
        int.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 arr = String(number).split(".");
        const int = arr[0];
        const fraction = arr[1] || "";
        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 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 reg = /(\d{1,3})(?=(\d{3})+(?:$|.))/g;
        return (number + "").replace(reg, "$1,");
      }
    </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) {
        var data = new Array(count).fill(0).map(function (i) {
          var rd = Math.random();
          var 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) {
        var start = performance.now();
        for (var i = 0; i < data.length; i++) {
          fn(data[i]);
        }
        var time = performance.now() - start;

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

      function executeTest() {
        var 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) {
        var el = document.createElement("p");
        el.innerHTML = msg;
        messageEl.appendChild(el);
      }
    </script>
  </body>
</html>