Why Regular Expression

我们先来看看,我们干哈要学正则表达式这玩意儿:

  • 复杂的字符串搜寻、替换工作,无法用简单的方式(类似借助标准库函数)达成。
  • 能够帮助你进行各种字符串验证。
  • 不止应用于编程语言中:JavaScript、JAVA、Perl、PHP、C#...
  • 也应用于许多操作系统的主流指令中:Linux/Unix、Mac、Windows PowerScript

在我们常用的开发工具中,如Fiddler Willow、WebStorm、Vim,正则表达式也能帮助我们方便的进行Find&Replace的工作。由于正则表达式的流派很多,这篇文章主要是描述JavaScript中的正则表达式。

介绍点语法

定义

所谓正则表达式,就是一种描述字符串结构模式的形式化表达方法。

这是《精通正则表达式》对于它的定义,反正我看了这句话还是不知道正则表达式是干嘛用的,不过没关系,下面我们先来看一下JavaScript的正则表达式中一些常用的语法。

创建方式

在JavaScript中,我们可以通过RegExp()构造函数或者RegExp直接量两种方式去创建正则表达式。

var pattern1 = /s$/;
var pattern2 = new RegExp('s$');

上面代码中的pattern1和pattern2是等价的,都是用来匹配所有以字母s结尾的字符串。

多说两句:

在创建变量时,对于布尔、数值、字符串、null和undefined这个五个原始值类型来说,原始类型优于封装对象,原因如下。

1、不同于字符串直接量,new出来的String是一个真正的对象,这意味着你不能使用内置操作符来比较两个截然不同的String对象的内容。

var s = new String('hello');
typeof 'hello'; // string
typeof s; // object
var s1 = new String('hello');
var s2 = new String('hello');
s1 === s2; // false

这是因为每个String对象对是一个单独的对象,其总是只等于其自身。使用不严格相等运算符也是一样。

s1 == s2; // false

2、在ES5规范中,就像[],{}这样的对象直接量一样,程序运行时每次碰到RegExp直接量都会创建新对象。比如,如果在循环体中写var pattern = /s$/,则每次遍历都会创建一个新的正则表达式对象。然而在ES3规范中一个正则表达式直接量会在执行到它时转换为一个RegExp对象,同一段代码的正则表达式直接量的每次运算都返回同一个对象。而ES5做了相反的规定。用下面这段代码做比较。

function getRE() {
var re = /[a-z]/;
re.foo = 'bar';
return re;
} var reg = getRE();
re2 = getRE();
console.log(reg === re2); // 在ES3中返回true,在ES5中返回false
reg.foo = 'baz';
console.log(re2.foo); // 在ES3中返回'baz',在ES5中返回'bar'

显然ES5的规范更符合开发者的期望。

各种表格

直接量字符

字符 匹配
字母和数字字符 自身
\o NUL字符
\t 制表符(\u0009)
\n 换行符(\u000A)
\v 垂直制表符(\u000B)
\f 换页符(\u000C)
\r 回车符(\u000D)
\xnn 由十六进制数nn指定的拉丁字符
\uxxx 由十六进制数xxxx指定的Unicode字符
\cX 控制字符^X

注:

由十六进制数nn指定的拉丁字符,例如:\x0A等价于\n
由十六进制数xxxx指定的Unicode字符:\u0009等价于\t
控制字符^X:\cJ等价于换行符\n
如果不记得哪些标点符号需要反斜杆转义,可以在每个标点符号前都加上反斜杆。

字符类

字符 匹配
[...] 方括号内任意字符
[^...] 不在方括号内的任意字符
. 除换行符和Unicode行终止符外的任意字符
\w 任何ASCⅡ字符组成的单词,等价于[a-zA-Z0-9_]
\W 任何不是ASCⅡ字符组成的单词,等价于[^a-zA-Z0-9_]
\s 任何Unicode空白符
\S 任何非Unicode空白符,注意\w和\S的不同
\d 任何ASCⅡ数字,等价于[0-9]
\D 除了ASCⅡ数字之外的任何字符,等价于[^0-9]
[\b] 退格直接量

注:

方括号又叫字符组,注意某些元字符在字符组外和字符组内的意义不同。例如:^在字符组外匹配行的开头,在字符组内表示排除型字符;-在字符组外匹配普通连字符号,在字符组内(不在开头)表示一个范围;问号和点号在字符组外通常是元字符,但在字符组内只是匹配普通字符而已。

重复字符类

字符 匹配
{n,m} 匹配前一项至少n次,但不能超过m次
{n,} 匹配前一项n次或多次
{n} 匹配前一项n次
? 匹配前一项0次或1次,也就是说前一项是可选的,等价于{0,1}
+ 匹配前一项1次或多次,等价于{1,}
* 匹配前一项0次或多次,等价于{0,}

注:

javascript默认是贪婪匹配,也就是说匹配重复字符是尽可能多地匹配,而且允许后续的正则表达式继续匹配。而进行非贪婪匹配,只需要在待匹配的字符后面跟随一个问号即可:“??”、“+?”、“*?”、“{1,5}?”。比如:/a+/可以匹配一个或多个连续的字母a。当使用“aaa”作为匹配字符串时,/a+/会匹配它的三个字母。但是/a+?/会尽可能少的匹配,只能匹配第一个哦~

选择、分组和引用字符

字符 匹配
"竖线" 选择,匹配的是该符号左边的子表达式或右边的子表达式
(...) 组合,将几个项组合为一个单元,这个单元可通过“*”、“+”、“?”和"竖线"等符号修饰,而且可以记住和这个相匹配的字符串以供伺候的引用使用
(?:...) 只组合,把项组合到一个单元,但不记忆与该组相匹配的字符
\n 和第n个分组第一次匹配的字符相匹配,组是圆括号中的子表达式(也有可能是嵌套),组索引是从左到右的左括号数,“(?:”形式的分组不编码

锚字符

字符 匹配
^ 匹配字符串的开头,在多行检索中,匹配一行的开头
$ 匹配字符串的结尾,在多行检索中,匹配一行的结尾
\b 匹配一个单词的边界,简而言之,就是位于字符\w和字符\W之间的位置,或位于字符\w和字符串的开头或结尾之间的位置(但需要注意的是在字符组内[\b]匹配的是退格符)
\B 匹配非单词边界的位置
(?=p) 零宽正向先行断言,要求接下来的字符都与p匹配,但不能包括匹配p的那些字符
(?!p) 零宽负向先行断言,要求接下来的字符不与p匹配

修饰符

字符 匹配
i 执行不区分大小写的匹配
g 执行一个全局匹配,简而言之,即找到所有的匹配,而不是在找到第一个之后就停止
m 多行匹配模式,^匹配一行的开头和字符串的开头,$匹配行的结束和字符串的结束

用于模式匹配的String方法

方法 意义
String.search() 参数:一个正则表达式。返回:第一个与参数匹配的子串的起始位置,如果找不到,返回-1。不支持全局搜索,如果参数是字符串,会先通过RegExp构造函数转换成正则表达式。
String.replace() 检索和替换。第一个参数:正则表达式,第二个参数:要进行替换的字符串,也可以是函数。设置了g修饰符,则替换所有匹配的子串,否则只替换第一个子串。通过在替换字符串中使用“$n”,可以使用子表达式相匹配的文本来替换字符。
String.match() 参数:一个正则表达式。返回:一个由匹配结果组成的数组。设置g则返回所有匹配结果,否则数组的第一个元素是匹配的字符串,剩下的是圆括号中的子表达式,即a[n]中存放的是$n的内容。
String.split() 参数:正则表达式或字符串。返回:子串组成的数组。

RegExp对象

RegExp构造函数

var pattern = new RegExp(arg1, arg2);

arg1: 正则表达式中两条斜杆之间的文本

arg2: 可选,指定修饰符:g,m,i

作用:动态创建正则表达式,例如待检索的字符串是由用户输入的。

RegExp的属性

属性 意义
source 只读字符串,包含正则表达式的文本。
global 只读布尔值,是否带修饰符g
ignoreCase 只读布尔值,是否带修饰符i
multiline 只读布尔值,是否带修饰符m
lastIndex 可读写整数,如果带g修饰符,这个属性储存在整个字符串中下一次检索开始的位置,这个属性会被exec()和test()方法用到。

RegExp的方法

方法 意义
exec() 参数:字符串。在一个字符串中执行匹配检索,与String.macth()非全局检索类似,返回一个数组或null。
test() 参数:字符串。返回true or false
toString() 转换成字符串形式

关于RegExp对象的属性和方法多说两句:

RegExp对象的属性index包含了发生匹配的字符位置,属性input引用的是正在检索的字符串。
当调用exec()或test()的正则表达式具有修饰符g时,它将把当前正则表达式对象的lastIndex属性设置为紧挨着匹配子串的字符位置。如果没发现任何匹配结果,lastIndex将重置为0。可以通过此特性反复调用exec()或test()来遍历字符串。

ES5中,正则表达式直接量的每次计算都会创建一个新的RegExp对象,每个新的RegExp对象具有各自的lastIndex属性,这势必会大大减少“残留”lastIndex属性对程序造成的意外影响。

一些栗子

匹配URL

常见的URL:http://hostname/path.html当然,.htm或.shtml的结尾也很常见,或者干脆没有path部分,还包括http或https的协议头。

  1. 其实hostname的规则比较复杂,但是跟在http(s)://之后的就有可能是主机名,所以这个部分先简单的用[-a-z0-9_.]来匹配,再加上可能存在的端口号,所以再加上:, 就成了[-a-z0-9_.:]

  2. path部分变化更多,所以需要使用[-a-z0-9_:@&?=+,.!/~*%$]来匹配。注意,连字符必须放在字符组的开头,保证它是一个普通字符,而不是用来表示范围。

  3. 综合起来,我们得到的正则表达式就是:var patternURL = /https?:\/\/[a-z0-9_.:]+\/[-a-z0-9_:@&?=+,.!/~*%$]*(\.(html|htm|shtml))?/

  4. 因为我们降低了对匹配的要求,所以'http://.../foo.html' 这种显然不是合法URL的字符串也能匹配,不过我觉得还好,毕竟我们需要在正则匹配的复杂性和完整性之间取得平衡。

接下来,我们一步步地对URL进行分析。

我们可以将URL分为三个部分:

  1. 协议头:^http://或^https://

  2. 主机名:主机名是位于^http://之后和第一个反斜杆(如果有的话)之前的内容。

  3. 路径:除了上面两者之外的内容。

  4. 得到正则表达式:var patternURL = /^https?:\/\/([^/]+)(/.*)?$/

  5. 由于URL可能包含端口号,它位于主机名和路径之间,以冒号开头: (:(\d)+)?

  6. 得到正则表达式:var patternURL = /^https?:\/\/([^/:]+)(:(\d)+)?(/.*)?$/

  7. 匹配合法的主机名:由点号分隔部分组成,每个部分可以包括ASCⅡ字符、数字和连字符,但不能以连字符开头和结尾。则可以得到:var patternHostname = /[a-z0-9]|[a-z0-9][-a-z0-9]*[a-z0-9]/i

  8. 结尾的后缀部分只有有限个可能:(com|edu|gov|int|mil|net|org|biz|info|name|museum|coop|aero|[a-z][a-z])

  9. 完善后得到:var patternHostname =/^([a-z0-9]\.|[a-z0-9][-a-z0-9]{0,61}[a-z0-9]\.)(com|edu|gov|int|mil|net|org|biz|info|name|museum|coop|aero|[a-z][a-z])$/i

匹配HTML Tag

匹配HTML标签嘛,感觉很简单的样子,我们的第一反应可能是:var pattern = /<[^>]+>/
不过这样匹配可能存在的问题是:如果tag中含有>,上面的正则就不能正常匹配了。如:

<input name=123 value=">" >

虽然上面这种HTML的写法很少(sha)见(bi),但确实合法的。因此,简单的<[^>]+>就不能用了,需要想个聪明点的办法。

我们先来看一下HTML Tag中有什么规则:<...>中能够出现

  1. 引用文本(被单引号或双引号包裹的)
  2. 非引用形式的“其他文本”(包括除了>和引号之外的任何字符)

引用文本:HTML中的引文可以用双引号,也可以用单引号,但不允许嵌套转义的引号。

因此我们可以使用/("[^"]*"|'[^']*')/来匹配。

其他文本:除了>和引号之外的任意字符

可以使用/[^'">]/来匹配
现在可以得出匹配HTML Tag的正则表达式最终版!

var pattern = /<("[^"]*"|'[^']*'|[^'">])*>/

给这个正则表达式来点注释:

<                # 开始的尖括号"<"
( # 任意数量的...
"[^"]*" # 双引号字符串
| # 或者是...
'[^']*' # 单引号字符串
| # 或者是...
[^'">] # "其他文本"
)* #
> # 结束的尖括号">"

需要注意的是,我们不用"+"来修饰[^'">]的原因是([^'">]+)*可能会带来灾难性的后果。匹配次数呈指数级增长。比如:对于简单的目标字符串helloworld,是星号会迭代10次,每一次迭代中[^'">]+匹配一个字符?还是星号迭代3次,内部的[^'">]+分别匹配5、2、3个字符?或者2、3、1、4个字符?还是其他情况?这样会把正则引擎搞疯掉的啦!

匹配String

其实匹配引号内字符串的最简单办法是用这个表达式:/"[^"]*"/

不过我们要容许其中包含转义的引号,例如:"we have a \"awesome\" world!"

下面进行任务分解:

  1. 匹配起始引号
  2. 匹配正文
  3. 匹配结束引号

不过由于转义之后的引号也能够出现的正文中,所以处理起来比较棘手哈。

我们还是以"we have a \"awesome\" world!"为例子。如果JavaScript中有逆序环视(lookaround)可用,我们可以这样写:var pattern = /"([^"]|(?<=\\)")*"/

但是这个正则表达式无法匹配下面这两个无聊的例子:"/-|-\\" or "[^-^]"
我本来想匹配"/-|-\\",结果匹配的确是"/-|-\\" or "

注:

这里的结束分隔符是一个引号,但正文也可能包含转义之后的引号。匹配开始和结束分隔符很容易,诀窍就在于,匹配正文的时候不要超越结束分隔符。

匹配正文的思路:1、不是引号:由[^"]匹配。2、是一个引号,而它左边又有一个反斜杆,那么这个引号也属于正文。使用逆序环视:/"([^"]|(?<=\\)")*"/

鉴于上面的例子,我们需要对var pattern = /"([^"]|(?<=\\)")*"/进行修改!

第一个表达式的问题在于,我们把反斜杆认为只是用来转义引号的,其实反斜杆在字符串中可以用来转义任何字符。因此,我们要匹配的文本其实是开始引号和结束引号之间,包括转义字符和非引号的任何字符。得到:/"(\\.|[^"])*"/

不过!

上面的表达式还是会错误的匹配:"You need a new\"world\" haha. 中的"You need a new\"world\" 即使这并不是一个字符串。

因为,这个表达式一开始匹配到了引号之后的文本,如果找不到结束的引号,它就会回溯。而[^"]匹配到了world\里的反斜杆后,之后的那个引号会被表达式认为是一个结束的引号。。。

继续改改改!

所以我们需要保证,字符串里的反斜杆不能以[^"]方式匹配。要将[^"]改为[^\\"]
上面的正则表达式使用了JavaScript正则表达式并不兹瓷的逆序环视,这里给出JavaScript支持的版本。

/(["'])(((\\.|[^\1\\])*)+)\1/ 或者 /^(['"])(((\\['"])?([^\1])*)+)\1/

好了,由于本人笔力有限,关于JavaScript的正则表达式只能介绍到这里,感兴趣的同学可以去阅读犀牛书的第十章以及《精通正则表达式》这本书

原文链接:http://ivweb.io/topic/56e804ef1a5f05dc50643106

本文编辑:宋秉金

玩转JavaScript正则表达式的更多相关文章

  1. javascript正则表达式语法

    1. 正则表达式规则 1.1 普通字符 字母.数字.汉字.下划线.以及后边章节中没有特殊定义的标点符号,都是"普通字符".表达式中的普通字符,在匹配一个字符串的时候,匹配与之相同的 ...

  2. JavaScript正则表达式,你真的知道?

    一.前言 粗浅的编写正则表达式,是造成性能瓶颈的主要原因.如下: var reg1 = /(A+A+)+B/; var reg2 = /AA+B/; 上述两个正则表达式,匹配效果是一样的,但是,效率就 ...

  3. 【JS】javascript 正则表达式 大全 总结

    javascript 正则表达式 大全 总结 参考整理了一些javascript正则表达式 目的一:自我复习归纳总结 目的二:共享方便大家搜索 微信:wixf150 验证数字:^[0-9]*$ 验证n ...

  4. 理清JavaScript正则表达式--上篇

    在JavaScript中,正则表达式由RegExp对象表示.RegExp对象呢,又可以通过直接量和构造函数RegExp两种方式创建,分别如下: //直接量 var re = /pattern/[g | ...

  5. 理清JavaScript正则表达式--下篇

    紧接:"理清JavaScript正则表达式--上篇". 正则在String类中的应用 类String支持四种利用正则表达式的方法.分别是search.replace.match和s ...

  6. JavaScript正则表达式详解(一)正则表达式入门

    JavaScript正则表达式是很多JavaScript开发人员比较头疼的事情,也很多人不愿意学习,只是必要的时候上网查一下就可以啦~本文中详细的把JavaScript正则表达式的用法进行了列表,希望 ...

  7. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  8. Python自动化 【第十八篇】:JavaScript 正则表达式及Django初识

    本节内容 JavaScript 正则表达式 Django初识 正则表达式 1.定义正则表达式 /.../  用于定义正则表达式 /.../g 表示全局匹配 /.../i 表示不区分大小写 /.../m ...

  9. JavaScript正则表达式下——相关方法

    上篇博客JavaScript 正则表达式上——基本语法介绍了JavaScript正则表达式的语法,有了这些基本知识,可以看看正则表达式在JavaScript的应用了,在一切开始之前,看看RegExp实 ...

随机推荐

  1. Html 让文字显示在图片的上面

    如题: 第一种方式便是将 image 作为背景图片,即:background-image:url("......."); 在此可以控制背景图片的横向和纵向的平铺: backgrou ...

  2. bzoj 3100 排列

    题目大意: 给你长度为 \(1e6\) 的序列, 求最大的 \(K\) 使得序列中含有一个 \(K\) 的排列 做法: 性质: 区间包含1, 元素不重, 区间最大值=区间长度 枚举一个 \(1\) 让 ...

  3. [bzoj3132]上帝造题的七分钟——二维树状数组

    题目大意 你需要实现一种数据结构,支援以下操作. 给一个矩阵的子矩阵的所有元素同时加一个数. 计算子矩阵和. 题解 一看这个题,我就首先想到用线段树套线段树做. 使用二维线段树的错误解法 其实是第一次 ...

  4. Intellij IDEA创建spring MVC项目

    相信各位未来的Java工程师已经接触到了spring MVC这个框架的强大之处,看了很多的教程,都是eclipse的,在intellij IDEA这个强大的工具面前居然不能很顺畅的,今天我就带领大家用 ...

  5. 填坑webpack

    1.Concepts: webpack is a module bundler for modern JS applications. Since there are lots of complex ...

  6. CVE-2016-6662 利用条件

    首先执行SET GLOBAL 需要超级用户权限,所以利用条件要么用户本身是超级用户要么用户有trigger权限,通过创建trigger,由超级用户触发SET GLOBAL. 然而MYsql有个通过fi ...

  7. measure time program

    #include <time.h> int delay(int time) { int i,j; for(i =0;i<time;i++) for(j=0;j<10000;j+ ...

  8. 《锋利的JQuery》读书要点笔记1——认识JQuery&&选择器

    <锋利的jQuery>源码下载,包括了这本书中全部代码以及用到的CSS文件 第一章 认识jQuery jQuery是个Js库.首先该明确的一点是:在jQuery库中$就是jQuery的一个 ...

  9. HDU 6357.Hills And Valleys-字符串非严格递增子序列(LIS最长非下降子序列)+动态规划(区间翻转l,r找最长非递减子序列),好题哇 (2018 Multi-University Training Contest 5 1008)

    6357. Hills And Valleys 自己感觉这是个好题,应该是经典题目,所以半路选手补了这道字符串的动态规划题目. 题意就是给你一个串,翻转任意区间一次,求最长的非下降子序列. 一看题面写 ...

  10. 关于 hibernate 中 hashCode爆栈的探讨

    今天在 hibernate 的一对多映射测试 merge 方法时,出现了以下的异常: 我们可以看到,这里的错误有非常明显的重复性,很显然是做了间接递归,并且递归的调用是 hashMap 中的hashC ...