首頁 >web前端 >js教程 >JavaScript中的正規有幾個不同於其他語言的地方

JavaScript中的正規有幾個不同於其他語言的地方

高洛峰
高洛峰原創
2016-11-26 16:26:271324瀏覽

我接觸過不少語言,我很重視一門語言的正規表示式是否強大,還有正規與語法的結合是否緊密。在這一點上,JavaScript做的還不錯,至少有正規字面量。當然,最強的還是Perl。我最近發現,JavaScript中的正規在某些地方的表現和其他語言或工具中的正規則有些不同,比較另類。雖然你幾乎不可能寫出也幾乎用不到下面我講的這些正則,但是了解一下畢竟是好的。本文中的程式碼範例都是在相容ES5的JavaScript環境中執行的,也就是說,IE9之前版本,Fx4左右的版本等中的表現很有可能和我下面講的不一樣。

 

1.空字符類

不包含任何字符的字符類[]稱之為空字符類(empty char class),我相信你沒聽別人這麼叫過,因為在其他語言中,這種寫法是非法的,所有的文檔和教程都不會講一種非法的語法.下面我演示一下其他語言或工具都是怎麼報這個錯的:

 

$echo | grep '[]'

$echo | grep '[]'

grep: Unmatched [ 或 [^

 

$echo | sed '/[]/'

sed:-e 表達式#1,字元4:未終止的位址正則表達式

'/[]/'

awk: cmd. line:1: /[]/

awk: cmd. line:1:  ^ unterminated regexp

awk: cmd. line:1: error: Unmatched [ ord. : /[]//

 

$echo | perl -ne '/[]/'

Unmatched [ in regex; marked by  

$echo | ruby​​ -ne '/[]/'

-e:1: empty char-class: /[]/

 re

$python -c 'import rere. []","")'

Traceback (most recent call last):

  File "", line 1, in

  File "E:Pythonlibre.py", line 137, in match

    return _compile(pattern, flags).m​​atch(string)

  File "E:Pythonlibre.py", line 244, in _compile

. end of regular expression

而在JavaScript中,空字符類別是合法的正則組成部分,不過它的效果是”永不匹配”,也就是匹配什麼都會失敗.相當於一個空否定正向環視(empty negative lookahead)(? !)的效果:

 

js> "whatevern".match(/[]/g)      //空字元類別,永不匹配

null

js> "whatevern".match(/( /g)    //空否定正向環視,永不匹配

null

很顯然,這種東西在JavaScript中沒什麼用.

2.否定空字符類

不包含任何字符的否定字符類[ ^]稱之為否定空字元類別(negative empty char class)或叫空否定字元類別(empty negative char class),都可以,因為這個名詞是我」自創」的,和上面講的空字元類別類似,這種寫法在其他語言中也是非法的:

 

$echo | grep '[^]'

grep: Unmatched [ 或 [^

 

$echo |

sed:-e 表達式#1,字元5:未終止的位址正規表示式

 

$echo | awk '/[^]/'

awk: cmd. line:1: /[^]/'

awk: cmd. line:1: /[^]/

awk: cmd. line:1:  ^ unterminated regexp

awk: cmd. line:1: error: Unmatched [ or [^: /[^]//

 

| ^]/'

Unmatched [ in regex; marked by  

$echo | ruby​​ -ne '/[^]/ '

-e:1: empty char-class: /[^]/

 

$python -c 'import re;re.match("[^]","")'

Traceback (most recent call last):

  File "", line 1, in

  File "E:Pythonlibre.py", line 137, in match

)   return   含 return  ).

  File "E:Pythonlibre.py", line 244, in _compile

    raise error, v # invalid expression

sre_constants.error: unexpected Javaion

sre_constants.error: unexpected Javadion

,$n 85%)5,字元類是合法的正則組成部分,它的效果和空字符類的效果剛剛相反,它可以匹配任意的字符,包括換行符”n”,也就是說,等同於常見的[sS]和[wW]:

 

js> "whatevern".match(/[^]/g)                //否定空白字元類別,符合任意字元

["w", "h", "a", "t", "ae "t", "ae "h", "ae" , "v", "e", "r", "n"]

js> "whatevern".match(/[sS]/g)             //互補字元類別,符合任意字元

["w", "h", "a", "t", "e", "v", "e", "r", "n"]

需要注意的是,它不能稱之為是」永匹配正則」,因為字符類必須要有一個字符才可能匹配,如果目標字符串是空的,或者已經被左邊的正則消耗完了,則匹配會失敗,例如:

 

js> /abc[^]/.test("abc")     //c後面沒有字符了,匹配失敗.

false

則想要了解真正的”永匹配正匹配失敗.

,”可以看看我以前翻譯的一篇文章:「空」正規

3.[]]和[^]]

這個講起來比較簡單,就是:在Perl和其他一些linux命令的正則表達式中,字元類別[]中如果包含了一個緊跟著左方括號的右方括號[]],則這個右方括號會被當作一個普通字符,即只能匹配”]”,而在JavaScript中,這種正則會被辨識成一個空字元類別後面跟著一個右方括號,空字元類別什麼都不匹配.[^]]也類似:在JavaScript中,它匹配的是一個任意字元(否定空字元類別)後跟一個右中括號,例如”a]”,”b]”,而在其他語言中,匹配的是任何非]的字符.

 

$perl -e 'print "]" =~ /[]]/ '

 

 

$js -e 'print(/[]]/.test("]"))'

false

] ]/'

 

 

$js -e 'print(/[^]]/.test("x"))'

false

4.$8是換行符」n」,這是大錯特錯的,$是一個零寬斷言(zero-width  assertion),它是不可能匹配到一個真正的字符的,它只能匹配一個位置.我要的講的區別發生在非多行模式中:你也許會認為,在非多行模式中,$匹配的不就是最後一個字符後面的位置嗎?實際上沒那麼簡單,在其他大部分語言中,如果目標字串中的最後一個字元是換行符”n”,則$還會匹配那個換行符之前的位置,也就是匹配了末尾的換行符左右兩邊的兩個位置.很多語言中都有Z和z這兩個表示法,如果你知道它們之間的區別,那你應該就明白了,在其他語言中(Perl,Python,php,Java,c#…),非多行模式下的$相當於Z,而在JavaScript中,非多行模式下的$相當於z(只會匹配最末尾的那個位置,不管最後一個字元是否是換行符).Ruby是個特例,因為它預設就是多行模式,多行模式下$會匹配每個換行符前面的位置,當然也會包括結尾處可能出現的那個換行符.餘晟著的《正則指引》一書中也講到了這幾點.

 

$perl - e 'print "whatevern" =~ s/$/替換字元/rg'      //全域替換

whatever替換字元                        //換行符號後面的位置被替換

 

$js -e 'print("whatevern".replace(/$/g,"替換字元"))' //全域替換

whatever

         whatever

取代那個

5.向前引用

我們都知道正則中有反向引用(back reference),也就是用一個反斜杠+數字的形式引用到前面的某個捕獲分組已經匹配到的字符串,目的是用來再次匹配或作為替換結果(變成$).但有種特殊情況是,如果那個被引用的捕獲分組還沒開始(左括號為界),就使用了反向引用,會怎樣.比如正規/(2(a)){2}/,(a)是第二個捕獲分組,但在它的左邊使用了引用它的匹配結果的2,我們知道正則是從左向右進行匹配的,這就是本節的標題向前引用(forwards reference)的來歷,它並不是一個嚴格的概念.那麼現在你想想,下面的這句JavaScript代碼將返回什麼:

 

js> /(2( a)){2}/.exec("aaa")

???

在回答這個問題之前,先看看其他語言中的表現.同樣,在其他語言中,這麼寫也基本上是無效的:

 

$echo aaa | grep '(2(a)){2}'

grep: Invalid back reference

 

$echo aaa | sed -/( }/'

sed:-e 表達式#1,字元12:非法回引

 

$echo aaa | awk '/(2(a)){2}/'

perl -ne 'print /(2(a)){2}/'

 

$echo aaa | ruby​​ -ne 'print $_ = ~/(2(a)){2}/'

 

$python -c 'import re;print re.match("(2(a)){2}","aaa")'

None

在awk中沒有報錯,是因為awk不支援反向引用,其中的2被解釋成了ASCII碼為2的字符.而在Perl Ruby Python中沒報錯,我不知道為什麼這樣設計,應該都是學Perl的,但效果都一樣,就是這種情況下是不可能匹配成功的.

而在JavaScript中,不僅不報錯,還能匹配成功,看看和你剛才想的答案一樣不一樣:

 

js> /(2(a)){2}/.exec("aaa" )

["aa", "a", "a"]

防止你忘了exec方法返回的結果是什麼,我說一下.第一個元素是完整的匹配字符串,也就是RegExp[" $&"],後面的是每個捕獲分組匹配的內容,也就是RegExp.$1和RegExp.$2.為什麼能匹配成功呢,匹配過程是怎樣的?我的理解是:

首先進入了第一個捕獲分組(最左邊的左括號),其中第一個有效匹配項是2,然而這時第二個捕獲分組(a)還沒輪上,因此RegExp.$2的值還是undefined,所以2匹配了目標字串中第一個a左邊的一個空字元,或者說」位置」,就像^和其他零寬斷言一樣.重點是匹配成功了.繼續走,這時第二個捕獲分組(a)匹配到了目標字串中的第一個a,RegExp.$2的值也被賦值為”a”,然後是第一個捕獲分組結束(最右邊的右括號),RegExp.$1的值也是”a”.然後是量詞{2},也就是說,要從目標字串中的第一個a之後,開始進行正則(2(a))的新的一輪匹配,很關鍵的一點在這裡:就是RegExp. $2的值也就是2匹配的值還是不是第一輪匹配結束時的被賦的值”a”,答案是:”不是“,RegExp.$1和RegExp.$2的值都會被清空為undefined,1和2又會和第一次一樣,成功匹配一個空字符(相當於無任何效果,寫不寫都一樣).成功匹配了目標字符串中的第二個a,這時RegExp.$1和RegExp.$2的值又一次成為了”a”,RegExp["$&"]的值成為了完整的匹配字符串,前兩個a:”aa”.

在Firefox的早期版本(3.6)中,量詞的重新一輪匹配不會清空已有的捕獲分組的值,那麼也就是說,在第二輪匹配的時候,2會匹配上第二個a,從而:

 

js> /(2(a )){2}/.exec("aaa")

["aaa", "aa", "a"]

另外,一個捕獲分組的結束要看右括號是否閉合,比如/(a1){ 3}/,雖然用到1的時候,第一個捕獲分組已經開始匹配了,但還沒結束,這同樣是向前引用,所以1匹配的仍然是空:

 

js> /(a1 ){3}/.exec("aaa")

["aaa", "a"]

再解釋一個例子:

 

js> /(?:(f)(o)(o)| (b)(a)(r))*/.exec("foobar")

["foobar", undefined, undefined, undefined, "b", "a", "r"]

*號是量詞,第一輪匹配過後:$1為”f”,$2為”o”,$3為”o”,$4為undefined,$5為undefined,$6為undefined.

第二輪匹配開始時:捕獲到的值全部重置為undefined.

第二輪匹配過後:$1為undefined,$2為undefined,$3為undefined,$4為”b”,$5為”a”,$6為”r”.

$&被賦值為”foobar”,配對結束.

最後一個思考題:

 

 

js> /(?:^(a)|1(a)|(ab)){2}/.exec("aab ")

????


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn