總結(jié)JavaScript的正則與其他語(yǔ)言的不同之處
前言
最近發(fā)現(xiàn)JavaScript中的正則在某些地方的表現(xiàn)和其他語(yǔ)言或工具中的正則有些不同,比較另類.雖然你幾乎不可能寫出也幾乎用不到下面我講的這些正則,但是了解一下畢竟是好的.
本文中的代碼示例都是在兼容ES5的JavaScript環(huán)境中執(zhí)行的,也就是說,IE9之前版本,Fx4左右的版本,等,中的表現(xiàn)很有可能和我下面講的不一樣.
1.空字符類
不包含任何字符的字符類[]稱之為空字符類(empty char class),我相信你沒聽別人這么叫過,因?yàn)樵谄渌Z(yǔ)言中,這種寫法是非法的,所有的文檔和教程都不會(huì)講一種非法的語(yǔ)法.下面我演示一下其他語(yǔ)言或工具都是怎么報(bào)這個(gè)錯(cuò)的:
$echo | grep '[]'
grep: Unmatched [ or [^
$echo | sed '/[]/'
sed:-e 表達(dá)式 #1,字符 4:未終止的地址正則表達(dá)式
$echo | awk '/[]/'
awk: cmd. line:1: /[]/
awk: cmd. line:1: ^ unterminated regexp
awk: cmd. line:1: error: Unmatched [ or [^: /[]//
$echo | perl -ne '/[]/'
Unmatched [ in regex; marked by <-- HERE in m/[ <-- HERE ]/ at -e line 1.
$echo | ruby -ne '/[]/'
-e:1: empty char-class: /[]/
$python -c 'import re;re.match("[]","")'
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "E:\Python\lib\re.py", line 137, in match
return _compile(pattern, flags).match(string)
File "E:\Python\lib\re.py", line 244, in _compile
raise error, v # invalid expression
sre_constants.error: unexpected end of regular expression
而在JavaScript中,空字符類是合法的正則組成部分,不過它的效果是"永不匹配",也就是匹配什么都會(huì)失敗.相當(dāng)于一個(gè)空否定正向環(huán)視(empty negative lookahead)(?!)的效果:
js> "whatever\n".match(/[]/g) //空字符類,永不匹配 null js> "whatever\n".match(/(?!)/g) //空否定正向環(huán)視,永不匹配 null
很顯然,這種東西在JavaScript中沒什么用.
2.否定空字符類
不包含任何字符的否定字符類[^]稱之為否定空字符類(negative empty char class)或者叫空否定字符類(empty negative char class),都可以,因?yàn)檫@個(gè)名詞是我"自創(chuàng)"的,和上面講的空字符類類似,這種寫法在其他語(yǔ)言中也是非法的:
$echo | grep '[^]'
grep: Unmatched [ or [^
$echo | sed '/[^]/'
sed:-e 表達(dá)式 #1,字符 5:未終止的地址正則表達(dá)式
$echo | awk '/[^]/'
awk: cmd. line:1: /[^]/
awk: cmd. line:1: ^ unterminated regexp
awk: cmd. line:1: error: Unmatched [ or [^: /[^]//
$echo | perl -ne '/[^]/'
Unmatched [ in regex; marked by <-- HERE in m/[ <-- HERE ^]/ at -e line 1.
$echo | ruby -ne '/[^]/'
-e:1: empty char-class: /[^]/
$python -c 'import re;re.match("[^]","")'
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "E:\Python\lib\re.py", line 137, in match
return _compile(pattern, flags).match(string)
File "E:\Python\lib\re.py", line 244, in _compile
raise error, v # invalid expression
sre_constants.error: unexpected end of regular expression
$
而在JavaScript中,否定空字符類是合法的正則組成部分,它的效果和空字符類的效果剛剛相反,它可以匹配任意的字符,包括換行符"\n" ,也就是說,等同于常見的[\s\S]和[\w\W] :
js> "whatever\n".match(/[^]/g) //否定空字符類,匹配任意字符 ["w", "h", "a", "t", "e", "v", "e", "r", "\n"] js> "whatever\n".match(/[\s\S]/g) //互補(bǔ)字符類,匹配任意字符 ["w", "h", "a", "t", "e", "v", "e", "r", "\n"]
需要注意的是,它不能稱之為是"永匹配正則",因?yàn)樽址惐仨氁幸粋€(gè)字符才可能匹配,如果目標(biāo)字符串是空的,或者已經(jīng)被左邊的正則消耗完了,則匹配會(huì)失敗,比如:
js> /abc[^]/.test("abc") //c后面沒有字符了,匹配失敗.
false
想要了解真正的"永匹配正則",可以看看我以前翻譯的一篇文章:"空"正則
3.[]]和[^]]
這個(gè)講起來比較簡(jiǎn)單,就是:在Perl和其他一些linux命令的正則表達(dá)式中,字符類[]中如果包含了一個(gè)緊跟著左方括號(hào)的右方括號(hào)[]] ,則這個(gè)右方括號(hào)會(huì)被當(dāng)作一個(gè)普通字符,即只能匹配"]",而在JavaScript中,這種正則會(huì)被識(shí)別成一個(gè)空字符類后跟一個(gè)右方括號(hào),空字符類什么都不匹配.[^]]也類似:在JavaScript中,它匹配的是一個(gè)任意字符(否定空字符類)后跟一個(gè)右中括號(hào),比如"a]","b]" ,而在其他語(yǔ)言中,匹配的是任何非]的字符.
$perl -e 'print "]" =~ /[]]/'
1
$js -e 'print(/[]]/.test("]"))'
false
$perl -e 'print "x" =~ /[^]]/'
1
$js -e 'print(/[^]]/.test("x"))'
false
4.$錨點(diǎn)
有些初學(xué)者認(rèn)為$匹配的是換行符"\n" ,這是大錯(cuò)特錯(cuò)的,$是一個(gè)零寬斷言(zero-width assertion),它是不可能匹配到一個(gè)真正的字符的,它只能匹配一個(gè)位置.我要的講的區(qū)別發(fā)生在非多行模式中:你也許會(huì)認(rèn)為,在非多行模式中,$匹配的不就是最后一個(gè)字符后面的位置嗎?實(shí)際上沒那么簡(jiǎn)單,在其他大部分語(yǔ)言中,如果目標(biāo)字符串中的最后一個(gè)字符是換行符"\n" ,則$還會(huì)匹配那個(gè)換行符之前的位置,也就是匹配了末尾的換行符左右兩邊的兩個(gè)位置.很多語(yǔ)言中都有\(zhòng)Z和\z這兩個(gè)表示法,如果你知道它們之間的區(qū)別,那你應(yīng)該就明白了,在其他語(yǔ)言中(Perl,Python,php,Java,c#...),非多行模式下的$相當(dāng)于\Z,而在JavaScript中,非多行模式下的$相當(dāng)于\z(只會(huì)匹配最末尾的那個(gè)位置,不管最后一個(gè)字符是否是換行符).Ruby是個(gè)特例,因?yàn)樗J(rèn)就是多行模式,多行模式下$會(huì)匹配每個(gè)換行符前面的位置,當(dāng)然也會(huì)包括結(jié)尾處可能出現(xiàn)的那個(gè)換行符.余晟著的《正則指引》一書中也講到了這幾點(diǎn).
$perl -e 'print "whatever\n" =~ s/$/替換字符/rg' //全局替換
whatever替換字符 //換行符前面的那個(gè)位置被替換
替換字符 //換行符后面的那個(gè)位置被替換
$js -e 'print("whatever\n".replace(/$/g,"替換字符"))' //全局替換
whatever
替換字符 //換行符后面的那個(gè)位置被替換
5.點(diǎn)號(hào)元字符"."
在JavaScript中的正則表達(dá)式中,點(diǎn)號(hào)元字符"."可以匹配四個(gè)行終止符(\r-回車符,\n-換行符,\u2028-行分隔符,\u2029-段落分隔符)之外的所有字符,而在其他常用語(yǔ)言中,只會(huì)排除掉換行符\n.
6.向前引用
我們都知道正則中有反向引用(back reference),也就是用一個(gè)反斜杠+數(shù)字的形式引用到前面的某個(gè)捕獲分組已經(jīng)匹配到的字符串,目的是用來再次匹配或作為替換結(jié)果(\變成$).但有種特殊情況是,如果那個(gè)被引用的捕獲分組還沒開始(左括號(hào)為界),就使用了反向引用,會(huì)怎樣.比如正則/(\2(a)){2}/ , (a)是第二個(gè)捕獲分組,但在它的左邊使用了引用它的匹配結(jié)果的\2,我們知道正則是從左向右進(jìn)行匹配的,這就是本節(jié)的標(biāo)題向前引用(forwards reference)的來歷,它并不是一個(gè)嚴(yán)格的概念.那么現(xiàn)在你想想,下面的這句JavaScript代碼將返回什么:
js> /(\2(a)){2}/.exec("aaa")
???
在回答這個(gè)問題之前,先看看其他語(yǔ)言中的表現(xiàn).同樣,在其他語(yǔ)言中,這么寫也基本上是無效的:
$echo aaa | grep '(\2(a)){2}'
grep: Invalid back reference
$echo aaa | sed -r '/(\2(a)){2}/'
sed:-e 表達(dá)式 #1,字符 12:非法回引用
$echo aaa | awk '/(\2(a)){2}/'
$echo aaa | 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中沒有報(bào)錯(cuò),是因?yàn)閍wk不支持這種反向引用,其中的\2被解釋成了ASCII碼為2的字符.而在Perl Ruby Python中沒報(bào)錯(cuò),我不知道為什么這樣設(shè)計(jì),應(yīng)該都是學(xué)Perl的,但效果都一樣,就是這種情況下是不可能匹配成功的.
而在JavaScript中,不僅不報(bào)錯(cuò),還能匹配成功,看看和你剛才想的答案一樣不一樣:
js> /(\2(a)){2}/.exec("aaa")
["aa", "a", "a"]
防止你忘了exec方法返回的結(jié)果是什么,我說一下.第一個(gè)元素是完整的匹配字符串,也就是RegExp["$&"] ,后面的是每個(gè)捕獲分組匹配的內(nèi)容,也就是RegExp.$1和RegExp.$2.為什么能匹配成功呢,匹配過程是怎樣的?我的理解是:
首先進(jìn)入了第一個(gè)捕獲分組(最左邊的左括號(hào)),其中第一個(gè)有效匹配項(xiàng)是\2,然而這時(shí)第二個(gè)捕獲分組(a)還沒輪上,因此RegExp.$2的值還是undefined,所以\2匹配了目標(biāo)字符串中第一個(gè)a左邊的一個(gè)空字符,或者說"位置",就像^和其他零寬斷言一樣.重點(diǎn)是匹配成功了.繼續(xù)走,這時(shí)第二個(gè)捕獲分組(a)匹配到了目標(biāo)字符串中的第一個(gè)a,RegExp.$2的值也被賦值為"a",然后是第一個(gè)捕獲分組結(jié)束(最右邊的右括號(hào)),RegExp.$1的值也是"a".然后是量詞{2},也就是說,要從目標(biāo)字符串中的第一個(gè)a之后,開始進(jìn)行正則(\2(a))的新的一輪匹配,很關(guān)鍵的一點(diǎn)在這里:就是RegExp.$2的值也就是\2匹配的值還是不是第一輪匹配結(jié)束時(shí)的被賦的值"a",答案是:"不是",RegExp.$1和RegExp.$2的值都會(huì)被清空為undefined,\1和\2又會(huì)和第一次一樣,成功匹配一個(gè)空字符(相當(dāng)于無任何效果,寫不寫都一樣).成功匹配了目標(biāo)字符串中的第二個(gè)a,這時(shí)RegExp.$1和RegExp.$2的值又一次成為了"a",RegExp["$&"]的值成為了完整的匹配字符串,前兩個(gè)a:"aa".
在Firefox的早期版本(3.6)中,量詞的重新一輪匹配不會(huì)清空已有的捕獲分組的值,那么也就是說,在第二輪匹配的時(shí)候,\2會(huì)匹配上第二個(gè)a,從而:
js> /(\2(a)){2}/.exec("aaa")
["aaa", "aa", "a"]
另外,一個(gè)捕獲分組的結(jié)束要看右括號(hào)是否閉合,比如/(a\1){3}/,雖然用到\1的時(shí)候,第一個(gè)捕獲分組已經(jīng)開始匹配了,但還沒結(jié)束,這同樣是向前引用,所以\1匹配的仍然是空:
js> /(a\1){3}/.exec("aaa")
["aaa", "a"]
再解釋一個(gè)例子:
js> /(?:(f)(o)(o)|(b)(a)(r))*/.exec("foobar")
["foobar", undefined, undefined, undefined, "b", "a", "r"]
*號(hào)是量詞,第一輪匹配過后:$1為"f",$2為"o",$3為"o",$4為undefined,$5為undefined,$6為undefined.
第二輪匹配開始時(shí):捕獲到的值全部重置為undefined.
第二輪匹配過后:$1為undefined,$2為undefined,$3為undefined,$4為"b",$5為"a",$6為"r".
&被賦值為"foobar",匹配結(jié)束.
總結(jié)
以上就是總結(jié)JavaScript的正則與其他語(yǔ)言不同之處的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)和工作能帶來幫助。
相關(guān)文章
js加強(qiáng)的經(jīng)典分頁(yè)實(shí)例
js加強(qiáng)的經(jīng)典分頁(yè)實(shí)例,需要的朋友可以參考一下2013-03-03
html+javascript實(shí)現(xiàn)可拖動(dòng)可提交的彈出層對(duì)話框效果
本文為大家介紹下使用html+javascript實(shí)現(xiàn)可拖動(dòng)彈出層、對(duì)話框、可提交,具體代碼如下,感興趣的朋友可以參考下,希望對(duì)大家有所幫助2013-08-08
javascript 實(shí)現(xiàn)父窗口引用彈出窗口的值的腳本
javascript 實(shí)現(xiàn)父窗口引用彈出窗口的值的腳本...2007-08-08
js使用函數(shù)綁定技術(shù)改變事件處理程序的作用域
在html頁(yè)面里面為某個(gè)元素的事件指定處理程序有很多種方式2011-12-12
UniApp?WebView頁(yè)面中的請(qǐng)求跨域問題解決
在使用UniApp開發(fā)中,通過WebView組件加載本地網(wǎng)頁(yè)時(shí),往往會(huì)遇到跨域問題,下面這篇文章主要介紹了UniApp?WebView頁(yè)面中的請(qǐng)求跨域問題解決的相關(guān)資料,需要的朋友可以參考下2024-10-10
Omi v1.0.2發(fā)布正式支持傳遞javascript表達(dá)式
這篇文章主要介紹了Omi v1.0.2發(fā)布正式支持傳遞javascript表達(dá)式,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03
js 公式編輯器 - 自定義匹配規(guī)則 - 帶提示下拉框 - 動(dòng)態(tài)獲取光標(biāo)像素坐標(biāo)
這篇文章主要介紹了js公式編輯器 - 自定義匹配規(guī)則 - 帶提示下拉框 - 動(dòng)態(tài)獲取光標(biāo)像素坐標(biāo)的方法,,需要的朋友可以參考下2018-01-01
JavaScript實(shí)現(xiàn)正則去除a標(biāo)簽并保留內(nèi)容的方法【測(cè)試可用】
這篇文章主要介紹了JavaScript實(shí)現(xiàn)正則去除a標(biāo)簽并保留內(nèi)容的方法,結(jié)合實(shí)例形式詳細(xì)分析了javascript針對(duì)a標(biāo)簽及span標(biāo)簽的正則匹配相關(guān)操作技巧,需要的朋友可以參考下2018-07-07
JavaScript實(shí)現(xiàn)給按鈕加上雙重動(dòng)作的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)給按鈕加上雙重動(dòng)作的方法,可實(shí)現(xiàn)文本框元素值添加及頁(yè)面跳轉(zhuǎn)的功能,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08

