在 javascript 用 regular expression 為金額加上千位數分隔符號
為了方便使用者快速判斷金額,所以想加上千位數分隔符號。例如:
3000 --> 3,000
3000000 --> 3,000,000
搜尋資料後,結論:
如果數字有小數點的話,可以用
const amount = '3000.00'
amount.replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ',')
如果沒有小數點的話,可以用
const amount = '3000'
amount.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
不過這個鬼畫符的語法我完全看不懂,查資料的時候一度不肯相信這是真正的程式碼呢⋯⋯因緣際會在讀書會中,得到 Helix 同學提示的關鍵字「 regular expression」,決定試著弄懂,「amount.replace(/\B(?=(\d{3})+(?!\d))/g, ',')」到底是什麼意思。
一、String.prototype.replace() - MDN
範例:我不小心把 world 拼成 word,想要把句子裡所有的 word 都替換成 world。
const text = 'Hello word! Hello word!'
console.log(text.replace('word', 'world'))
結果印出 Hello world! Hello word!
很不幸地,只有第一個錯字有替換,其他錯字都被放生了。如果想要替換全部的 word,就要這樣寫:
const text = 'Hello word! Hello word!'
console.log(text.replace(/word/g, 'world'))
用 / 包起來的區域,代表這個區域是使用 regular expression。 g 的意思是 global。這段程式碼的意思是,把字串 'Hello word! Hello word!' 之中,每一個符合「word」這個 pattern的地方都替換成 world。
結果印出 Hello world! Hello world! 真是可喜可賀。
所以,目前為止,可以判斷
const amount = '3000'
amount.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
這段程式碼的意思是,把字串 '3000' 之中,每一個符合「\B(?=(\d{3})+(?!\d))」這個 pattern的地方都替換成 , 。
所以,剩下的未解之謎是,\B(?=(\d{3})+(?!\d)) 是什麼。第一個考量是,要怎麼斷句啊?二、斷句
- \B(?=(\d{3})+(?!\d)) 對應到 x(?=y) 語法
- (\d{3})+(?!\d) 對應到 x(?!y) 語法
- (\d{3})+ 對應到 x+ 語法
- (\d{3}) 對應到 x{n} 語法。小括號雖然沒有完美地對應到,但是,小括號可能會因為易讀性的考量而省略不寫,所以我目前不太擔心這樣的斷句會出問題。
三、用到的語法,分別是什麼意思
- \d:代表任意一個阿拉伯數字,只要是0123456789 其中之一,就會被選中。
範例一const text = '123'const result = text.replace(/\d/, ',')console.log(result) //,23(被 /\d/ 選到的部分是 '123':第一個 \d)
範例二const text = '123'const result = text.replace(/\d/g, ',')
console.log(result) //,,,(被 /\d/g 選到的部分是 '123':每一個 \d)
- x{n}:n 是一個正整數,代表 x 連續出現正好 n 次,多一次或少一次都不行,才會被選中。
範例一const text = '1234'const result = text.replace(/\d{2}/, ',')console.log(result) //,34 (被 /\d{2}/ 選到的部分是 '1234':第一個 \d{2})
範例二
const text = '1234'
const result = text.replace(/\d{2}/g, ',')
console.log(result) //,, (被 /\d{2}/g 選到的部分是 '1234':每一個 \d{2})
- x+:代表 x 連續出現一次或更多次,這些部分都會被一起選中。
範例一const text = '12345'const result = text.replace(/\d+/, ',')console.log(result) //, (被 /\d+/ 選到的部分是 '12345':\d 連續出現 5 次,這些部分都被一起選中)
範例二const text = '12345'const result = text.replace(/(\d{2})+/, ',')console.log(result) //,5 (被 /(\d{2})+/ 選到的部分是 '12345':\d{2} 連續出現 2 次,這些部分都被一起選中)
- x(?!y):代表,x 的後面沒有緊跟著 y 的話,x 才會被選中。
範例一const text = '123'const result = text.replace(/\d(?!3)/, ',')console.log(result) //,23 (被 /\d(?!3)/ 選到的部分是 '123':第一個「\d 後面沒有緊跟著 3」的 \d)
範例二
const text = '123'
const result = text.replace(/\d(?!3)/g, ',')
console.log(result) //,2, (被 /\d(?!3)/g 選到的部分是 '123':每一個「\d 後面沒有緊跟著 3」的 \d)
- x(?=y):代表,x 的後面有緊跟著 y 的話,x 才會被選中。
範例一const text = '12341234'const result = text.replace(/2(?=\d{2})/, ',')console.log(result) //1,341234(被 /2(?=\d{2})/ 選到的部分是 '12341234':第一個「 2 後面有緊跟著\d{2}」的 2)
範例二
const text = '12341234'
const result = text.replace(/2(?=\d{2})/g, ',')
console.log(result) //1,341,34(被 /2(?=\d{2})/g 選到的部分是 '12341234':每一個「 2 後面有緊跟著\d{2}」的 2)
- \B:若相鄰的兩個字元,是同一個類型的話,會選中這兩個字元之間的那個位置。看描述比較難理解,直接看例子比較快。(建議有興趣的人,去找 \b 這個語法對照著一起看,會比較清楚他們之間的差別)
範例一
const text = '123'
const result = text.replace(/\B/, ',')
console.log(result) //1,23(第一個「兩個相同類型字元之間的位置」)
範例二
const text = '123'
const result = text.replace(/\B/g, ',')
console.log(result) //1,2,3(每一個「兩個相同類型字元之間的位置」)
範例三:為了進一步了解「怎麼樣算是相同類型」而做的實驗
const text = ' 12abAB--~~@@&&** abc '
const result = text.replace(/\B/g, ',')
console.log(result) //, 1,2,a,b,A,B-,-,~,~,@,@,&,&,*,*, a,b,c ,
觀察範例三「逗號沒有出現的時機」,似乎「空白和字母」、「字母和-」會被當成不同類型 。觀察範例二「逗號沒有出現的時機」,可以知道字串第一個字母前面的位置,和最後一個字母後面的位置( 123 ),不是「兩個相同類型字元之間的位置」。
四、回到 amount.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
那麼,\B(?=(\d{3})+(?!\d)) 是什麼呢?
- \d 是指任意一個阿拉伯數字。
- \d{3} 是指,連續出現正好 3 個數字,例如 34307898、888,就會被選中。選中的部分用背景色代表。
- (\d{3})+ 是指,\d{3} 連續出現一次或更多次,這些部分都會被一起選中。例如 9458348、78405741。
- (\d{3})+(?!\d) 是指,\d{3})+ 的後面沒有緊跟著 \d 的話,\d{3})+ 才會被選中。例如 123(不符條件,完全沒被選中)、 1234、123456、12345678。
- \B(?=(\d{3})+(?!\d)) 是指,\B 的後面有緊跟著 (\d{3})+(?!\d) 的話,\B 才會被選中。再白話一點就是,兩個相鄰數字之間的位置,如果後面緊跟著「後面不是數字的 \d{3})+」的話,這個位置就會被選中。例如 1 234、12 345678。
- /\B(?=(\d{3})+(?!\d))/g 是指,每一個符合 \B(?=(\d{3})+(?!\d)) 這個條件的東西,都會被選中。例如 1 234、12 345 678。
針對第 6 點補充說明:在 12 345 678 中,
-
2 和 3 中間的位置之所以被選中,是因為他以
後面緊跟著「後面不是數字的 \d{3})+」(留意這件事: \d{3}) 重複兩次)
的方式符合條件。
-
5 和 6 中間的位置之所以被選中,是因為他以
後面緊跟著「後面不是數字的 \d{3})+」(留意這件事: \d{3}) 重複一次)
的方式符合條件。
2 和 3 中間的位子,是第一個符合條件的位置,所以在第 5 點的時候,只有這個位置被選中。而在第 6 點的時候,所有符合條件的位置都會被選中,所以 5 和 6 中間的位置也被選中了。
Comments
Post a Comment