【JavaScript】正则表达式删除代码注释
【JavaScript】正则表达式删除代码注释
约定:本文中,以数字内容表示代码正文,其余字符内容表示注释内容。
代码注释有三种形式:
第一种:
1 | 123456 // aabbccdd |
第二种:
1 | 123456 /* aabbccdd */ 123456 |
第三种:
1 | 123456 /* aabbccdd |
其实第二种和第三种是同一类型。
代码实现与运行效果
直接给代码,看运行效果,然后再来讲正则表达式为什么要这么写.
代码实现如下:
1 | function deleteCodeComments(code) { |
控制台输出如下:
单行注释 //
的处理
代码的每一行中,双斜杆//
及其后内容,不管是任何字符,都是注释。
所以,只是正规表达式挑出以//
开始,后续不论任何字符都直接可以删除。
所以具体的表达如下:
1 | //.* |
开头的//
表示遇到//
就开始匹配,.
表示//
后可匹配的字符内容为除了换行符以外的任意一个字符,*
表示//
后面符合条件的字符出现次数可以为0或任意次数。
考虑到//
在JavaScript中需要转义,所以在定义时转义后的表达式如下:
1 | // 原型是://.* |
使用该表达式匹配代码,会发现只有第一次出现的的注释会被删除,第二个及以后的注释都还在。好吧…需要再添加全局匹配功能,即添加g
。代码如下:
1 | var reg1 = /\/\/.*/g; |
这样,不论代码中出现多少行 //
注释,都可以被一一匹配出现替换掉了。
多行注释 /* */
的处理
/* */
的特殊之处在于它可以单行注释,也可以多行注释,所以比//
要多过滤掉换行符,所以在//
中的.
只代表非换行符的其它所有字符,所以.
就不能用了。
看看正规表达式语法规范中,可以用\s
和\S
的合集来表示所有的字符。
字符 | 含义 |
---|---|
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。 |
合集的表示用[]
将\s\S
包含在内即可。所以改进一下,初始的表达式如下:
1 | // 原型是:/*[\s\S]**/ |
即以/*
开始,以*/
结尾,中间的任意字符的组合都符合匹配规则。当然,/*
及*/
在上面的表达式中都用转义字符\
转义了。
但上面的表达式还有问题。当遇到如下的代码,
1 | 123456/* aabbc */ 123 /* aabbcc */ 456 |
匹配的结束字符串*/
有两个,那么每次匹配时,该告诉解释器以该选择哪个呢?
这就涉及到正规表达式的贪婪模式与非贪婪模式。
贪婪模式即尽可能的去匹配更多符合条件的字符内容,在上面的例子中,即匹配到*/ 456
才结束,运算之后的结果将会是:123456 456
。
非贪婪模式即匹配尽可能少的字符内容,在上面的例子中以匹配到*/ 123
结束,运算的结果将会是123456 123 456
。
很明显,这种场景下我们需要的是非贪婪模式。
再次查询正规表达式语法规范,可以用?
来使用非贪婪模式,所以最后的改进代码如下:
1 | // 原型是:/*[\s\S]*?*/ |
整合
当然,我们也可以通过 |
串接起两个匹配规则。相当于’或‘运算。串接后的正规表达示如下:
1 | var reg = /(\/\/.*)|(\/\*[\s\S]*?\*\/)/g; |
单行注释要排除 http://
等##
在使用过程中,发现上文中的单行注释会把 http://
或 ftp://
等字符串定义一并当做注释处理掉了。
下面给出带http://
的干扰项的源字符串,及当前过滤出的错误结果及期待的正确结果。
1 | 123456"http://sodino.com" // aabbccdd |
很可惜,JavaScript并不支持正则表达式中的反向否定预查,那只好再想想办法。
查看JavaScript文档,发现String.prototype.replace(regexp|substr, newSubStr|function)的第二个参数是支持函数的。即目标替换符可以通过函数来按自定义的逻辑来决定,这也意味着替换规则是灵活可变的。
根据文档,传入的函数定义如下:
Possible name | Supplied value |
---|---|
match | The matched substring. (Corresponds to $& above.) |
p1, p2, … | The nth parenthesized submatch string, provided the first argument to replace() was a RegExp object. (Corresponds to $1, $2, etc. above.) For example, if /(\a+)(\b+)/, was given, p1 is the match for \a+, and p2 for \b+. |
offset | The offset of the matched substring within the whole string being examined. (For example, if the whole string was ‘abcd’, and the matched substring was ‘bc’, then this argument will be 1.) |
string | The whole string being examined. |
那么通过该函数可以得到//
开始的字符串match
,由于在match
中可能存在多个//
,我们需要找到在match
中第一个前缀不为:
的//
位置,从该位置开始的内容皆为代码注释。
优化后的deleteCodeComments()函数具体的实现和逻辑说明见如下代码:
1 | function deleteCodeComments(code) { |
运行效果如下图: