在 javascript 用 regular expression 為金額加上千位數分隔符號

 為了方便使用者快速判斷金額,所以想加上千位數分隔符號。例如:

3000 --> 3,000

3000000 --> 3,000,000

搜尋資料後,結論:

如果數字有小數點的話,可以用 

const amount = '3000.00'

amount.replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ',')

參考資料:Javascript之數值千分位符號(Comma)的顯示

如果沒有小數點的話,可以用 

const amount = '3000'

amount.replace(/\B(?=(\d{3})+(?!\d))/g, ',')

參考資料:[Javascrpt]轉換數字成含千分位的文字

 

不過這個鬼畫符的語法我完全看不懂,查資料的時候一度不肯相信這是真正的程式碼呢⋯⋯因緣際會在讀書會中,得到 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)) 是什麼。第一個考量是,要怎麼斷句啊?


二、斷句

比對一下 Regular expressions - MDN 表列的各種語法,我想,應該是這樣斷句的:

  • \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)) 是什麼呢?

  1. \d 是指任意一個阿拉伯數字。
  2. \d{3} 是指,連續出現正好 3 個數字,例如 34307898、888,就會被選中。選中的部分用背景色代表。
  3. (\d{3})+ 是指,\d{3} 連續出現一次或更多次,這些部分都會被一起選中。例如 9458348、78405741。
  4. (\d{3})+(?!\d) 是指,\d{3})+ 的後面沒有緊跟著 \d 的話,\d{3})+ 才會被選中。例如 123(不符條件,完全沒被選中)、 1234123456、12345678
  5. \B(?=(\d{3})+(?!\d)) 是指,\B 的後面有緊跟著 (\d{3})+(?!\d) 的話,\B 才會被選中。再白話一點就是,兩個相鄰數字之間的位置,如果後面緊跟著「後面不是數字的 \d{3})+」的話,這個位置就會被選中。例如 1 234、12 345678。
  6. /\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

Popular posts from this blog

Alpha Camp 全端開發課程學習心得

shop_platform - sqlalchemy.exc.TimeoutError