Skip to content

Latest commit

 

History

History
112 lines (67 loc) · 6.31 KB

watchOutRegexInReplace.md

File metadata and controls

112 lines (67 loc) · 6.31 KB

JavaScript正则表达式replace的一个细节点

经常听大家说JavaScript是魔法语言,咱却没有什么深刻体会。直到这回碰上这个细节问题...

trymagic_smaller-2021-11-06

0x00 踩到坑

昨天咱经过一番考虑后决定将 Python正则表达式细节小记 这篇笔记发到个人博客上。选好文章音乐,复制markdown内容...发布!

按照惯例我检查了一下发布后的文章内容,然后就见到了一个奇怪的现象:

issuehappens-2021-11-06

文章内容到一半的时候全被替换成 模板里的HTML 了...

之前调试博客的时候从没遇到过这个问题,我一时就有点摸不着头脑( >﹏<。),但没关系,比对一下原文档就应该知道问题在哪了:

comparison-2021-11-06

很明显能发现是从$开始被替换了,我心里咯噔一下:怕不是和正则表达式有关!不过在排查正则表达式之前我去改了一下博客后台部分的代码:

modifiedcode-2021-11-06

结果问题仍然存在,我接着还拿着这样的内容片段进行复现,但没能成功($并没有被替换成其他内容):

test$

 $ 
test$

0x01 错在哪

到最后,我还是怀疑回了正则表达式,但想来想去还是摸不着头脑,正则表达式和用作替换的字符串有什么关系,$不是用在正则表达式里的吗?而且为什么刻意用$去复现又不行呢?

baldscratch-2021-11-07

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

googlesearch-2021-11-07

不查不知道,一查吓一跳,看到有老哥提到了replace函数接收的字符串不仅仅是字符串,我赶紧去MDN查了一下:

syntaxofreplace-2021-11-07

原来用作替换的字符串内能包括一些特殊的变量名(不过这个特性不止是JavaScript有,其他支持正则表达式的语言也多多少少支持,详细看0x04-事后

变量名 代表的值
$$ 插入一个 "$"。
$& 插入匹配的子串。
$` 插入当前匹配的子串左边的内容。
$' 插入当前匹配的子串右边的内容。
$n 假如第一个参数是 RegExp对象,并且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从1开始。如果不存在第 n个分组,那么将会把匹配到到内容替换为字面量。比如不存在第3个分组,就会用“$3”替换匹配到的内容。
$ 这里Name 是一个分组名称。如果在正则表达式中并不存在分组(或者没有匹配),这个变量将被处理为空字符串。只有在支持命名分组捕获的浏览器中才能使用。

表格原地址: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace#%E4%BD%BF%E7%94%A8%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BD%9C%E4%B8%BA%E5%8F%82%E6%95%B0

现在再回去看文章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文档这里的 指定一个函数作为参数

usefuncasarg-2021-11-07

0x03 教训

吃一堑长一智,以后写代码的时候还是不能掉以轻心了,说不定在哪个角落还有我不太清楚的用法。遇到不会或者不清楚的一定要多查文档,不然一旦写进项目里可能就会成为一个遗留的潜在问题。(ノへ ̄、)

0x04 事后

@Ajanuw 老哥提醒,不止是JavaScript的正则替换中待替换字串(replacement)有这种用法,例如:

不过$& , $` , $'这种写法似乎是JavaScript独有的 (Perl也有,是我才疏学浅) 了,这回也是正好踩在这上面了(还是好想吐槽这个特殊变量配上` , & , '的设计 Σ( ° △ °|||)︴