配色: 字号:
总结JavaScript的正则与其他语言的不同之处
2016-09-30 | 阅:  转:  |  分享 
  
总结JavaScript的正则与其他语言的不同之处

前言

最近发现JavaScript中的正则在某些地方的表现和其他语言或工具中的正则有些不同,比较另类.虽然你几乎不可能写出也几乎用不到下面我讲的这些正则,但是了解一下毕竟是好的.

本文中的代码示例都是在兼容ES5的JavaScript环境中执行的,也就是说,IE9之前版本,Fx4左右的版本,等,中的表现很有可能和我下面讲的不一样.

1.空字符类

不包含任何字符的字符类[]称之为空字符类(emptycharclass),我相信你没听别人这么叫过,因为在其他语言中,这种写法是非法的,所有的文档和教程都不会讲一种非法的语法.下面我演示一下其他语言或工具都是怎么报这个错的:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15 $echo|grep''[]''

grep:Unmatched[or[^

$echo|sed''/[]/''

sed:-e表达式#1,字符4:未终止的地址正则表达式

$echo|awk''/[]/''

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

awk:cmd.line:1:^unterminatedregexp

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

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

Unmatched[inregex;markedby<--HEREinm/[<--HERE]/at-eline1.

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

-e:1:emptychar-class:/[]/

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

Traceback(mostrecentcalllast):

File"",line1,in

File"E:\Python\lib\re.py",line137,inmatch

return_compile(pattern,flags).match(string)

File"E:\Python\lib\re.py",line244,in_compile

raiseerror,v#invalidexpression

sre_constants.error:unexpwww.visa158.comlarexpression 而在JavaScript中,空字符类是合法的正则组成部分,不过它的效果是"永不匹配",也就是匹配什么都会失败.相当于一个空否定正向环视(emptynegativelookahead)(?!)的效果:

?

1

2

3

4 js>"whatever\n".match(/[]/g)//空字符类,永不匹配

null

js>"whatever\n".match(/(?!)/g)//空否定正向环视,永不匹配

null ?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16 $echo|grep''[^]''

grep:Unmatched[or[^

$echo|sed''/[^]/''

sed:-e表达式#1,字符5:未终止的地址正则表达式

$echo|awk''/[^]/''

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

awk:cmd.line:1:^unterminatedregexp

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

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

Unmatched[inregex;markedby<--HEREinm/[<--HERE^]/at-eline1.

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

-e:1:emptychar-class:/[^]/

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

Traceback(mostrecentcalllast):

File"",line1,in

File"E:\Python\lib\re.py",line137,inmatch

return_compile(pattern,flags).match(string)

File"E:\Python\lib\re.py",line244,in_compile

raiseerror,v#invalidexpression

sre_constants.error:unexwww.hunanwang.netlarexpression

$ 而在JavaScript中,否定空字符类是合法的正则组成部分,它的效果和空字符类的效果刚刚相反,它可以匹配任意的字符,包括换行符"\n",也就是说,等同于常见的[\s\S]和[\w\W]:

?

1

2

3

4 js>"whatever\n".match(/[^]/g)//否定空字符类,匹配任意字符

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

js>"whatever\n".match(/[\s\S]/g)//互补字符类,匹配任意字符

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

1

2 js>/abc[^]/.test("abc")//c后面没有字符了,匹配失败.

false ?

1

2

3

4

5

6

7

8

9

10

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

1

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

false

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

1

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

false 4.$锚点

有些初学者认为$匹配的是换行符"\n",这是大错特错的,$是一个零宽断言(zero-widthassertion),它是不可能匹配到一个真正的字符的,它只能匹配一个位置.我要的讲的区别发生在非多行模式中:你也许会认为,在非多行模式中,$匹配的不就是最后一个字符后面的位置吗?实际上没那么简单,在其他大部分语言中,如果目标字符串中的最后一个字符是换行符"\n",则$还会匹配那个换行符之前的位置,也就是匹配了末尾的换行符左右两边的两个位置.很多语言中都有\Z和\z这两个表示法,如果你知道它们之间的区别,那你应该就明白了,在其他语言中(Perl,Python,php,Java,c#...),非多行模式下的$相当于\Z,而在JavaScript中,非多行模式下的$相当于\z(只会匹配最末尾的那个位置,不管最后一个字符是否是换行符).Ruby是个特例,因为它默认就是多行模式,多行模式下$会匹配每个换行符前面的位置,当然也会包括结尾处可能出现的那个换行符.余晟著的《正则指引》一书中也讲到了这几点.

?

1

2

3

4

5

6

7 $perl-e''print"whatever\n"=~s/$/替换字符/rg''//全局替换

whatever替换字符//换行符前面的那个位置被替换

替换字符//换行符后面的那个位置被替换

$js-e''print("whatever\n".replace(/$/g,"替换字符"))''//全局替换

whatever

替换字符//换行符后面的那个位置被替换 ?

1

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

??? 在回答这个问题之前,先看看其他语言中的表现.同样,在其他语言中,这么写也基本上是无效的:

?

1

2

3

4

5

6

7

8

9

10 $echoaaa|grep''(\2(a)){2}''

grep:Invalidbackreference

$echoaaa|sed-r''/(\2(a)){2}/''

sed:-e表达式#1,字符12:非法回引用

$echoaaa|awk''/(\2(a)){2}/''

$echoaaa|perl-ne''print/(\2(a)){2}/''

$echoaaa|ruby-ne''print$_=~/(\2(a)){2}/''

$python-c''importre;printre.match("(\2(a)){2}","aaa")''

None 在awk中没有报错,是因为awk不支持这种反向引用,其中的\2被解释成了ASCII码为2的字符.而在PerlRubyPython中没报错,我不知道为什么这样设计,应该都是学Perl的,但效果都一样,就是这种情况下是不可能匹配成功的.

而在JavaScript中,不仅不报错,还能匹配成功,看看和你刚才想的答案一样不一样:

?

1

2 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,从而:

?

1

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

["aaa","aa","a"] 另外,一个捕获分组的结束要看右括号是否闭合,比如/(a\1){3}/,虽然用到\1的时候,第一个捕获分组已经开始匹配了,但还没结束,这同样是向前引用,所以\1匹配的仍然是空:

?

1

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

["aaa","a"] 再解释一个例子:

?

1

2 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",匹配结束.



献花(0)
+1
(本文系白狐一梦首藏)