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


Posted in Javascript onAugust 25, 2016

前言

最近发现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.$1RegExp.$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.$1RegExp.$2的值都会被清空为undefined,\1和\2又会和第一次一样,成功匹配一个空字符(相当于无任何效果,写不写都一样).成功匹配了目标字符串中的第二个a,这时RegExp.$1RegExp.$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的正则与其他语言不同之处的全部内容,希望本文的内容对大家的学习和工作能带来帮助。

Javascript 相关文章推荐
Jquery中增加参数与Json转换代码
Nov 20 Javascript
javascript+mapbar实现地图定位
Apr 09 Javascript
基于jquery的loading 加载提示效果实现代码
Sep 01 Javascript
javascript自然分类法算法实现代码
Oct 11 Javascript
简单讲解AngularJS的Routing路由的定义与使用
Mar 05 Javascript
详解Node.js中的事件机制
Sep 22 Javascript
jQuery ajax 当async为false时解决同步操作失败的问题
Nov 18 Javascript
微信小程序实现登录页云层漂浮的动画效果
May 05 Javascript
vue使用Element组件时v-for循环里的表单项验证方法
Jun 28 Javascript
vue.js使用v-if实现显示与隐藏功能示例
Jul 06 Javascript
vue-router的使用方法及含参数的配置方法
Nov 13 Javascript
详解JavaScript栈内存与堆内存
Apr 04 Javascript
js判断radiobuttonlist的选中值显示/隐藏其它模块的实现方法
Aug 25 #Javascript
jQuery实用小技巧_输入框文字获取和失去焦点的简单实例
Aug 25 #Javascript
JS实现图片延迟加载并淡入淡出效果的简单方法
Aug 25 #Javascript
JavaScript中闭包之浅析解读(必看篇)
Aug 25 #Javascript
jquery点击切换背景色的简单实例
Aug 25 #Javascript
浅析Javascript ES6中的原生Promise
Aug 25 #Javascript
微信JS接口大全
Aug 25 #Javascript
You might like
php最简单的删除目录与文件实现方法
2014/11/28 PHP
php实现有趣的人品测试程序实例
2015/06/08 PHP
php创建图像具体步骤
2017/03/13 PHP
PHP实现二维数组中的查找算法小结
2018/06/09 PHP
如何用javascript判断录入的日期是否合法
2007/01/08 Javascript
JS控件autocomplete 0.11演示及下载 1月5日已更新
2007/01/09 Javascript
jscript之Read an Excel Spreadsheet
2007/06/13 Javascript
浅谈JavaScript中Date(日期对象),Math对象
2015/02/05 Javascript
jquery获取当前元素索引值用法实例
2015/06/10 Javascript
jfinal与bootstrap的登录跳转实战演习
2015/09/22 Javascript
js实现遍历含有input的table实例
2015/12/07 Javascript
JavaScript解八皇后问题的方法总结
2016/06/12 Javascript
jQuery模拟Marquee实现无缝滚动效果完整实例
2016/09/29 Javascript
微信js-sdk上传与下载图片接口用法示例
2016/10/12 Javascript
详解nodejs异步I/O和事件循环
2017/06/07 NodeJs
JS获取字符对应的ASCII码实例
2017/09/10 Javascript
React Native悬浮按钮组件的示例代码
2018/04/05 Javascript
实例详解Vue项目使用eslint + prettier规范代码风格
2018/08/20 Javascript
Vuex的初探与实战小结
2018/11/26 Javascript
浅谈对于react-thunk中间件的简单理解
2019/05/01 Javascript
小程序云开发如何实现图片上传及发表文字
2019/05/17 Javascript
layui的表单提交以及验证和修改弹框的实例
2019/09/09 Javascript
[01:48]2018DOTA2亚洲邀请赛主赛事第二日五佳镜头 VG完美团战逆转TNC
2018/04/05 DOTA
python入门教程之识别验证码
2017/03/04 Python
Tensorflow实现神经网络拟合线性回归
2019/07/19 Python
pandas 如何分割字符的实现方法
2019/07/29 Python
django之从html页面表单获取输入的数据实例
2020/03/16 Python
Keras预训练的ImageNet模型实现分类操作
2020/07/07 Python
Python实现手绘图效果实例分享
2020/07/22 Python
斯洛伐克香水和化妆品购物网站:Parfemy-Elnino.sk
2020/01/28 全球购物
数据库基础的一些面试题
2012/02/25 面试题
感恩小明星事迹材料
2014/05/23 职场文书
伦敦奥运会的口号
2014/06/21 职场文书
中职三好学生事迹材料
2014/08/24 职场文书
2014年教师节演讲稿范文
2014/09/10 职场文书
工作违纪检讨书范文
2015/01/26 职场文书