【JavaScript】正則表達式刪除代碼註釋

文章目錄
  1. 1. 【JavaScript】正則表達式刪除代碼註釋
  2. 2. 代碼實現與運行效果
  3. 3. 單行註釋 // 的處理
  4. 4. 多行註釋 /* */的處理
  5. 5. 整合
  6. 6. 單行註釋要排除 http:// 等

【JavaScript】正則表達式刪除代碼註釋

約定:本文中,以數字內容表示代碼正文,其餘字符內容表示註釋內容。

代碼註釋有三種形式:

第一種:

1
123456 // aabbccdd

第二種:

1
123456 /* aabbccdd */ 123456

第三種:

1
2
3
123456 /* aabbccdd
* aabbccdd *
* aabbccdd */ 123456

其實第二種和第三種是同一類型。

代碼實現與運行效果

直接給代碼,看運行效果,然後再來講正則表達式爲什麼要這麼寫.

代碼實現如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function deleteCodeComments(code) {
// 以下的兩種方式都可以 // sodino.com
// var reg1 = /\/\/.*/g;
// var reg2 = /\/\*[\s\S]*?\*\//g;
var reg = /(\/\/.*)|(\/\*[\s\S]*?\*\/)/g;
var result =
// code.replace(reg1, '').replace(reg2, '');
code.replace(reg, '');
return result;
}
string = '123456 //aabbccddee';
string = string + '\n\r' + string + '\n\r' + string;
// result = string.replace(/\/\/.*/g, "");
result = deleteCodeComments(string);
console.log('-------------------------------------');
console.log('delete // :');
console.log(result);
string = '654321/* aabbccdd */123456/* aabbcc'
+ '\n\r' + '* aabbcc *'
+ '\n\r' + '* aabbcc *'
+ '\n\r' + '* aabbcc **/ 123456';
string = string + string + string;
// result = string.replace(/\/\*[.\s\S]*?\*\//g, ''); // sodino.com
result = deleteCodeComments(string);
console.log('-------------------------------------');
console.log('delete /* */ multi line :');
console.log(result);

控制檯輸出如下:

delete.code.comments

單行註釋 // 的處理

代碼的每一行中,雙斜杆//及其後內容,不管是任何字符,都是註釋。
所以,只是正規表達式挑出以//開始,後續不論任何字符都直接可以刪除。
所以具體的表達如下:

1
//.*

開頭的//表示遇到//就開始匹配,
.表示//後可匹配的字符內容爲除了換行符以外的任意一個字符,
*表示//後面符合條件的字符出現次數可以爲0或任意次數。
考慮到//在JavaScript中需要轉義,所以在定義時轉義後的表達式如下:

1
2
// 原型是://.*
var reg1 = /\/\/.*/;

使用該表達式匹配代碼,會發現只有第一次出現的的註釋會被刪除,第二個及以後的註釋都還在。好吧…需要再添加全局匹配功能,即添加g。代碼如下:

1
var reg1 = /\/\/.*/g;

這樣,不論代碼中出現多少行 // 註釋,都可以被一一匹配出現替換掉了。

多行註釋 /* */的處理

/* */的特殊之處在於它可以單行註釋,也可以多行註釋,所以比//要多過濾掉換行符,所以在//中的.只代表非換行符的其它所有字符,所以.就不能用了。

看看正規表達式語法規範中,可以用\s\S的合集來表示所有的字符。

字符含義
\s匹配任何空白字符,包括空格、製表符、換頁符等等。等價於[ \f\n\r\t\v]。
\S匹配任何非空白字符。等價於[^ \f\n\r\t\v]。

合集的表示用[]\s\S包含在內即可。所以改進一下,初始的表達式如下:

1
2
// 原型是:/*[\s\S]**/
var reg2 = /\/\*[\s\S]*\*\//g;

即以/*開始,以*/結尾,中間的任意字符的組合都符合匹配規則。當然,/**/在上面的表達式中都用轉義字符\轉義了。

但上面的表達式還有問題。當遇到如下的代碼,

1
123456/* aabbc */ 123 /* aabbcc */ 456

匹配的結束字符串*/有兩個,那麼每次匹配時,該告訴解釋器以該選擇哪個呢?
這就涉及到正規表達式的貪婪模式與非貪婪模式。

貪婪模式即儘可能的去匹配更多符合條件的字符內容,在上面的例子中,即匹配到*/ 456才結束,運算之後的結果將會是:123456 456
非貪婪模式即匹配儘可能少的字符內容,在上面的例子中以匹配到*/ 123結束,運算的結果將會是123456 123 456

很明顯,這種場景下我們需要的是非貪婪模式。
再次查詢正規表達式語法規範,可以用?來使用非貪婪模式,所以最後的改進代碼如下:

1
2
// 原型是:/*[\s\S]*?*/
var reg2 = /\/\*[\s\S]*?\*\//g;

整合

當然,我們也可以通過 |串接起兩個匹配規則。相當於’或‘運算。串接後的正規表達示如下:

1
var reg = /(\/\/.*)|(\/\*[\s\S]*?\*\/)/g;

單行註釋要排除 http:// 等

在使用過程中,發現上文中的單行註釋會把 http:// 或 ftp:// 等字符串定義一併當做註釋處理掉了。
下面給出帶http://的干擾項的源字符串,及當前過濾出的錯誤結果及期待的正確結果。

1
2
3
4
5
6
7
123456"http://sodino.com" // aabbccdd
// 錯誤的結果:
123456http:
// 期待的正確結果:
123456"http://sodino.com"

很可惜,JavaScript並不支持正則表達式中的反向否定預查,那隻好再想想辦法。

查看JavaScript文檔,發現String.prototype.replace(regexp|substr, newSubStr|function)的第二個參數是支持函數的。即目標替換符可以通過函數來按自定義的邏輯來決定,這也意味着替換規則是靈活可變的。

根據文檔,傳入的函數定義如下:

Possible nameSupplied value
matchThe 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+.
offsetThe 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.)
stringThe whole string being examined.

那麼通過該函數可以得到//開始的字符串match,由於在match中可能存在多個//,我們需要找到在match中第一個前綴不爲://位置,從該位置開始的內容皆爲代碼註釋。

優化後的deleteCodeComments()函數具體的實現和邏輯說明見如下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
function deleteCodeComments(code) {
var reg = /(\/\/.*)?|(\/\*[\s\S]*?\*\/)/g;
var result = code.replace(reg, (match, p1, p2, offset, string) => {
// match: 正則表達式匹配到的目標字符串
// p1: 表達式中第一備選的目標字符串,即單行註釋
// p1: 表達式中第二備選的目標字符串,即多行註釋
// offset: 匹配到的目標字符串在源字符串的起始位置
// string: 源字符串
var target = '';
if (p1 != undefined && p1 != null && p1.length > 0) {
console.log('-------------------------------------');
console.log('match', match);
console.log('p1', p1);
console.log('p2', p2);
console.log('offset', offset);
console.log('string', string);
if (offset == 0) {
// 匹配字符串起始就是'//',所以整行都是註釋
return target;
}
console.log('no return!!??');
// 獲取當前字符串中第一個純正的單選註釋'//'
var idxSlash = 0;
while ((idxSlash = match.indexOf('//', idxSlash)) >= 0 ){
var prefix = string.charAt(offset + idxSlash -1);
if (prefix === ':') {
// 前一個字符是':',所以不是單行註釋
idxSlash = idxSlash + '//'.length;
continue;
} else {
target = match.substring(0, idxSlash)
break;
}
};
}
return target;
});
return result;
}
function deleteCodeComments(code) {
// 另一種思路更簡便的辦法
// 將'://'全部替換爲特殊字符,刪除註釋代碼後再將其恢復回來
var tmp1 = ':\/\/';
var regTmp1 = /:\/\//g;
var tmp2 = '@:@/@/@';
var regTmp2 = /@:@\/@\/@/g;
code = code.replace(regTmp1, tmp2);
var reg = /(\/\/.*)?|(\/\*[\s\S]*?\*\/)/g;
code = code.replace(reg, '');
result = code.replace(regTmp2, tmp1);
return result;
}
string = '123456"http://sodino.com" // aabbcc http://sodino.com';
string = string + '\n\r' + string + '\n\r' + string;
// result = string.replace(/\/\/.*/g, "");
result = deleteCodeComments(string);
console.log('-------------------------------------');
console.log('delete // :');
console.log(result);

運行效果如下圖:
delete.code.comments


About Sodino

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章