怎麼實現千位符
將數字轉為可讀性比較高的具有千位符是筆試/面試經常被問到的,實現方案也有很多種。 一般情況下,按照我們普通的想法,就是將數字轉為字串,然後使用字串的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()
複製程式碼