經常聽大家說JavaScript是魔法語言,咱卻沒有什么深刻體會。直到這回碰上這個細節問題...

0x00 踩到坑
昨天咱經過一番考慮后決定將 Python正則表達式細節小記 這篇筆記發到個人博客上。選好文章音樂,復制markdown內容...發布!
按照慣例我檢查了一下發布后的文章內容,然后就見到了一個奇怪的現象:

文章內容到一半的時候全被替換成 模板里的HTML 了...
之前調試博客的時候從沒遇到過這個問題,我一時就有點摸不着頭腦( >﹏<。),但沒關系,比對一下原文檔就應該知道問題在哪了:

很明顯能發現是從$開始被替換了,我心里咯噔一下:怕不是和正則表達式有關!不過在排查正則表達式之前我去改了一下博客后台部分的代碼:

結果問題仍然存在,我接着還拿着這樣的內容片段進行復現,但沒能成功($並沒有被替換成其他內容):
test$
$
test$
0x01 錯在哪
到最后,我還是懷疑回了正則表達式,但想來想去還是摸不着頭腦,正則表達式和用作替換的字符串有什么關系,$不是用在正則表達式里的嗎?而且為什么刻意用$去復現又不行呢?

實在不行只能去求助一下某搜索引擎了:

不查不知道,一查嚇一跳,看到有老哥提到了replace函數接收的字符串不僅僅是字符串,我趕緊去MDN查了一下:

原來用作替換的字符串內能包括一些特殊的變量名(不過這個特性不止是JavaScript有,其他支持正則表達式的語言也多多少少支持,詳細看0x04-事后)
| 變量名 | 代表的值 |
|---|---|
| $$ | 插入一個 "$"。 |
| $& | 插入匹配的子串。 |
| $` | 插入當前匹配的子串左邊的內容。 |
| $' | 插入當前匹配的子串右邊的內容。 |
| $n | 假如第一個參數是 RegExp對象,並且 n 是個小於100的非負整數,那么插入第 n 個括號匹配的字符串。提示:索引是從1開始。如果不存在第 n個分組,那么將會把匹配到到內容替換為字面量。比如不存在第3個分組,就會用“$3”替換匹配到的內容。 |
| $
|
這里Name 是一個分組名稱。如果在正則表達式中並不存在分組(或者沒有匹配),這個變量將被處理為空字符串。只有在支持命名分組捕獲的瀏覽器中才能使用。 |
現在再回去看文章markdown內容,有一部分我是這樣寫的:
到了這里,我發現老師說的在```[]```中**被當作普通字符**的元字符只是一部分罷了,主要是 ```*```,```?```,```+```,```{}```,```()```,```$``` 這些元字符。
毫無疑問其中的$`就被替換為了匹配字串左邊的內容,也就是模板的前面一部分,才導致文章被處理成這樣。
可以說這種設計有點魔法了,萬萬沒想到JavaScript竟然在待替換字符串這里內置了一些類似於$` , $& , $'變量名的用法。要是我沒有想着把這篇小記發到個人博客上,說不定還得要好一陣子才能發現這個問題。
0x02 解決方法
解決方法其實很簡單,str.replace(regexp|substr, newSubStr|function)的第二個參數是可以接受一個函數的,而這個函數的 返回值 就被直接用作匹配項替換了,而不是先尋找一遍$變量名。
比如我原來是這樣寫的:
str.replace(new RegExp('\\{\\[' + from + '\\]\\}','gi'), to);
那么我用箭頭函數改寫一下就行了:
str.replace(new RegExp('\\{\\[' + from + '\\]\\}','gi'), ()=>to);
其實就相當於:
str.replace(new RegExp('\\{\\[' + from + '\\]\\}','gi'), function(){
return to;
});
關於這個函數傳入的參數可以看MDN文檔這里的 指定一個函數作為參數

0x03 教訓
吃一塹長一智,以后寫代碼的時候還是不能掉以輕心了,說不定在哪個角落還有我不太清楚的用法。遇到不會或者不清楚的一定要多查文檔,不然一旦寫進項目里可能就會成為一個遺留的潛在問題。(ノへ ̄、)
0x04 事后
經 @Ajanuw 老哥提醒,不止是JavaScript的正則替換中待替換字串(replacement)有這種用法,例如:
不過$& , $` , $'這種寫法似乎是JavaScript獨有的 (Perl也有,是我才疏學淺) 了,這回也是正好踩在這上面了(還是好想吐槽這個特殊變量配上` , & , '的設計 Σ( ° △ °|||)︴
