怎么实现千位符
将数字转为可读性比较高的具有千位符是笔试/面试经常被问到的,实现方案也有很多种。 一般情况下,按照我们普通的想法,就是将数字转为字符串,然后使用字符串的substr、slice、substring来实现,从右到左,每隔三位插入一个",",eg:
var readableNumber = function(number) {
if(!Number(number)) {
throw TypeError('arugment must be number or can be transfer into number');
}
var numberStr = '' + number,
len = numberStr.length,
index = - 3;
// 第一步:最高位不足3的情况,用0补上
switch(len % 3) {
case 1: numberStr = '00' + numberStr; break;
case 2: numberStr = '0' + numberStr; break;
}
var result = "";
len = numberStr.length;
// 第二步:从右往做,每隔三个位置打一个","
while(-index <= len) {
result = numberStr.substr(index, 3) + ',' + result;
index -= 3;
}
// 第三步:将第一步在前面添加的0去掉以及尾部多于的","也去掉
return result.replace(/(^0+|,$)/g, '');
}
当然还可以数字转为数组,然后循环数组,每隔三个元素插入一个",",这种方式和第一种方式其实都是差不多的思路,通过循环来实现。
还有另外的思路,就是使用正则表达式匹配,将每隔三个数字替替换成三个数字+","的方式,如下:
var readableNumber = function(number) {
if(!Number(number)) {
throw TypeError('arugment must be number or can be transfer into number');
}
var numberStr = '' + number,
len = numberStr.length
// 第一步:最高位不足3的情况,用0补上
switch(len % 3) {
case 1: numberStr = '00' + numberStr; break;
case 2: numberStr = '0' + numberStr; break;
}
// 第二步:每隔三个数字添加一个",",并将头部多于的0和尾部多于的","去掉
return numberStr.replace(/(\d{3})/g, '$1,').replace(/(^0+)/g, '');
}
两种方式,显然用正则表达式效率会高点。第一种方式时间复杂度是O(n),第二种....(不知道怎么算正则的时间复杂度),但本人对比了下,千万级的数字,第一种方式要花费500-600毫秒,第二种方式100-200毫秒。
到这里就结束了?不不不,还有一种更加简洁的方式,有人是这样写的,体验下:
vartoThousands = function(number) {
return (number + '').replace(/(\d)(?=(\d{3})+$)/g, '$1,');
}
溜不溜☺☺☺?是不是看不懂啊~我也看不懂,感受到正则的牛逼之处,但好事多磨,我们来慢慢研究下。
首先,这个用到的是正向预查的正则表达式(当然还有反向预查),那什么是正向预查呢?我来讲下
正则相关
正向预查
?=就是正向预查的正则表达式,所谓正向,是因为它匹配的是?=表达式左边的表达式。比如有<表达式1>(?=<表达式2>),那么它只能匹配到<表达式2>左边的符合<表达式1>的字符串。看下面例子
/\d+(?=%)/.test('50%') // 匹配出50,这个表达式是要提取出百分数字,RegExp.$1=50
不知道说得明白不~~~
反向预查
?<=是反向预查的正则表达式,和正向相反,它匹配的是?<=表达式右边的表达式。
/(?<=\$)(\d+)/.test("$90") // 匹配出90,这个表达式是要提取金额数字,RegExp.$1=90
这里讲一下,我曾经做过一个需求,就是用户输入框只能输入“金钱”,把非金钱的字符过滤掉,我就用到的反向预查。
// 0开头.开头或者非数字的都去掉
let value = target.value.replace(/^0|^\.|[^\d.]/g, '')
// 将多余的.去掉,会将12.36.3254...过滤为12.36
value = value.replace(/(?<=\d+\.(\d+)?)(\.+\d*)*/g, '')
target.value = value
好啦,那么回到正题,针对/(\d)(?=(\d{3})+$)/g,我们来讲解下。
根据正向预查,/(\d)(?=(\d{3})+)/就是匹配符合右边有3的倍数个数字以上的左边一个数字的字符串,它能把11231234替换成1,1231234。
这明显不是我们要的结果,我们只想要右边是3的倍数个,而不是3的倍数个以上,所以我们加上$,变成/(\d)(?=(\d{3})+$)/,这个能把11231234替换成11,231234,这个结果我们比起上一步更接近我们想要的了,但右边的还没替换完成。
所以接下来我们以同样的匹配规则继续匹配右边的231234。 继续往右边匹配我们就想到了global修饰符,于是有/(\d)(?=(\d{3})+$)/g,最后就是我们要的结果了。顺便讲下global修锁符
global修饰符
g的作用,就是匹配完第一次后,继续匹配剩下的。
var reg = /(\d)(?=(\d{3})+$)/g
// 刚开始reg.lastIndex = 0,即从0号位置开始匹配
reg.test('1234567') // 匹配“1”,且lastIndex继续往前面走,因为匹配到1只有1位,即reg.lastIndex = 1,下次从1号位置开始匹配
reg.test('1234567') // 匹配“234”,且lastIndex继续往前面走,因为匹配了234,所以走3位,来到了4,下次从4号位置开始匹配
reg.test('1234567') // 匹配到456,lastIndex继续往前走,走3位,来到了7,7大于总位数,所以结束
可能讲得不够清晰,有错误的地方请指正。
补充,根据各位大佬的回复,还有一种方式:
Number("1234564").toLocaleString()