当前位置: 首页 > 编程笔记 >

总结JavaScript的正则与其他语言的不同之处

何灼光
2023-03-14
本文向大家介绍总结JavaScript的正则与其他语言的不同之处,包括了总结JavaScript的正则与其他语言的不同之处的使用技巧和注意事项,需要的朋友参考一下

前言

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

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

1.空字符类

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

$echo | grep '[]'
grep: Unmatched [ or [^

$echo | sed '/[]/'
sed:-e 表达式 #1,字符 4:未终止的地址正则表达式

$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中,空字符类是合法的正则组成部分,不过它的效果是"永不匹配",也就是匹配什么都会失败.相当于一个空否定正向环视(empty negative lookahead)(?!)的效果:

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

很显然,这种东西在JavaScript中没什么用.

2.否定空字符类

不包含任何字符的否定字符类[^]称之为否定空字符类(negative empty char class)或者叫空否定字符类(empty negative char class),都可以,因为这个名词是我"自创"的,和上面讲的空字符类类似,这种写法在其他语言中也是非法的:

$echo | grep '[^]'
grep: Unmatched [ or [^

$echo | sed '/[^]/'
sed:-e 表达式 #1,字符 5:未终止的地址正则表达式

$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)  //互补字符类,匹配任意字符
["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 "]" =~ /[]]/'
1

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

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

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

4.$锚点

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

$perl -e 'print "whatever\n" =~ s/$/替换字符/rg' //全局替换
whatever替换字符   //换行符前面的那个位置被替换
替换字符    //换行符后面的那个位置被替换

$js -e 'print("whatever\n".replace(/$/g,"替换字符"))' //全局替换
whatever
替换字符    //换行符后面的那个位置被替换

5.点号元字符"."

在JavaScript中的正则表达式中,点号元字符"."可以匹配四个行终止符(\r-回车符,\n-换行符,\u2028-行分隔符,\u2029-段落分隔符)之外的所有字符,而在其他常用语言中,只会排除掉换行符\n.

6.向前引用

我们都知道正则中有反向引用(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 -r '/(\2(a)){2}/'
sed:-e 表达式 #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中没有报错,是因为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"]

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

js> /(a\1){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",匹配结束.

总结

以上就是总结JavaScript的正则与其他语言不同之处的全部内容,希望本文的内容对大家的学习和工作能带来帮助。

 类似资料:
  • Nacos社区当前仅提供了Java版本的客户端,我们将主要依靠社区的贡献来发展多语言客户端。在未来,我们将向Nacos社区用户推荐那些最被广泛使用的以及支持最好的客户端作为Nacos相应语言的官方版本。 go cpp python nodejs more ...

  • 本文向大家介绍javascript正则表达式定义(语法)总结,包括了javascript正则表达式定义(语法)总结的使用技巧和注意事项,需要的朋友参考一下 本文讲述了javascript正则表达式定义(语法)。分享给大家供大家参考,具体如下: 正则表达式的2种定义方法:一种是直接调用RegExp(),第二种是直接用字面量来定义,即var re = /正则规则/; 2种定义方法本质都是调用RegEx

  • 问题内容: 是否有将正则表达式从一种流行语言的语法转换为另一种语法的工具?例如,将Python样式的正则表达式转换为Java样式的正则表达式? 或至少有人是否制定了一套规则来进行这些转换? 而且显然某些构造将无法转换。 问题答案: 转至本文,并单击 “ Regex信息对Regex口味的比较”链接,该链接使我进入了一个名为RegexBuddy的工具,听起来它可能可以满足您的要求。

  • 我试图建立MySQL查询与多个连接,其中加入的值的总和。有3个表:保管人,帐户和存款。账户和存款通过客户的customer_id字段连接到客户。在查询结束时,所有客户都按group_id分组: 问题是:连接的行重复,而我必须进行一些分析:汇总所有存款金额-您可以在这里看到我针对存款金额的解决方法。但真正的问题是如何计算“客户的首次存款”。在对结果进行分组之前,我们可能会看到: 所以,我需要的是总和

  • 当使用缺乏原生Pact支持的语言来写服务提供者时,你仍然可以使用通用的Pact提供者端验证工具来验证是否满足契约。 通用Pact提供者验证 下面的设置简化了任何语言的Pact提供者端的验证过程。 特性: 验证发布到Pact Broker的Pact文件 在开发环境验证供测试用的本地Pact*.json文件 安装有Ruby环境以及sane的预先配置的Docker镜像,缺省为src / Rakefile

  • 你会觉得将 Git 作为其他版本控制系统的客户端,或者在数据无损的情况下将几乎任何一个现有的仓库导入到 Git,都是一件很惬意的事。 在下一章,我们将要讲解 Git 的原始内部数据,如果需要的话你就可以加工每一个字节。