什么是正则表达式

简介

正则表达式(regular expression/regex/regexp/RE),是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”)。它使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。

正则表达式可以用来:

  • 验证字符串是否符合指定特征,比如验证是否是合法的邮件地址;
  • 用来查找字符串,从一个长的文本中查找符合指定特征的字符串,比查找固定字符串更加灵活方便;
  • 用来替换,比普通的替换更强大。

正则表达式定义

在javascript中,正则表达式的定义有两种方式,第一种是使用正则表达式字面量;第二种是使用构造函。

在具体介绍怎么定义前,先介绍下正则表达式的修饰符,它用以说明匹配模式的规则。

正则表达式有三个修饰符,分别是:

  • i,表示不区分大小写;
  • g,表示全局匹配;
  • m,表示多行匹配。

然后我们就可以一起来看看,正则表达式是怎样结合修饰符来定义的了:

//字面量定义,且不带修饰符
var pattern1=/at/;

//字面量定义,带一个修饰符(全局匹配)
var pattern2=/at/g;

//字面量定义,带一个修饰符(多行匹配)
var pattern3=/at/m;

//字面量定义,带一个修饰符(不区分大小写)
var pattern4=/at/i;

//字面量定义,带多个修饰符
var pattern5=/at/gi;

//构造函数定义,不带修饰符
var pattern6=new RegExp("at");

//构造函数定义,带一个修饰符(不区分大小写)
var pattern7=new RegExp("at","i");

//构造函数定义,带一个修饰符(多行匹配)
var pattern8=new RegExp("at","m");

//构造函数定义,带一个修饰符(全局匹配)
var pattern9=new RegExp("at","g");

//构造函数定义,带多个修饰符
var pattern10=new RegExp("at","gi");

由上面可以看出:

  • 正则表达式字面量通过一对斜杆(/)以及斜杆间包含的字符所组成的,如果有修饰符,则跟在第二个斜杆后面;
  • 用构造函数的方法,是通过new 跟RegExp()函数来完成的,该函数有两个参数,第一个参数是用字面量定义时,包含在斜杆直接的字符串,第二个参数是修饰符。

注意:可以同时使用多个修饰符。

如果同时使用多个修饰符,只要将多个修饰符连在一起书写。如上面例子所示。

知道了怎么定义正则表达式之后,接下来要介绍的就是,之前提到的在斜杆间的字符串是怎么表示的,它们有哪些含义。

正则表达式语法规则

直接量字符

普通字符

字母、数字、汉字、下划线、以及没有特殊定义的标点符号(有些标点符号有特殊定义,则不能归为普通字符),都是‘普通字符’。 正则表达式中的普通字符,在匹配一个字符串的时候,匹配的就是一个与它相同的一个字符。即按照字面含义进行匹配。

var reg = /a/,
    testTxt = 'aabc';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:a
var reg = /3/,
    testTxt = 'a2er23';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:3
var reg = /_/,
    testTxt = '_test';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:_
var reg = /你/,
    testTxt = '你好吗?';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:你

转义字符

正则表达式也支持非字母的字符匹配,这些字符通过反斜杠(\)作为前缀进行转义。

下表总结了一些正则表达式的直接字符量:

正则表达式中的直接量字符
字符 匹配
字母和数字字符 自身
\o NUL字符(\u0000)
\t 制表符(\u0009)
\n 换行符(\u000A)
\v 垂直制表符(\u000B)
\f 换页符(\u000C)
\r 回车符(\u000D)
\xnn 由十六进制数nn指定的拉丁字符,例如,\x0A等价于\n
\uxxxx 由十六进制数~指定的Unicode字符,例如\u0009等价于\t
\cX 控制字符^X,例如,\cJ等价于换行符\n

另外,有些具有特殊用处的标点符号(如^ $ . * + ? = ! : | \ / ( ) [ ] { }),如果需要匹配他们本身,就得对其进行转义(即在前面添加‘\’)。如:

var reg = /\?/,
    testTxt = '你好吗?';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:?

注意:
a. 如果不记得哪些标点符号必须转义才能匹配其本身,可以在每个标点符号前都加反斜杠。
b. 字母和数字如果要匹配其本身,不能添加反斜杠,因为前面有提到,在一些字母前面添加反斜杠会有特殊意义。
c. 正则表达式的直接量只能匹配与之对应的一个字符。

var reg = /a/,
    testTxt = 'aaaabaada';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:a

字符类

一个字符类可以匹配它所包含的任意字符。

自定义能够匹配 '多种字符' 的表达式

使用方括号[]包含一系列字符,能够匹配其中任意一个字符。

var reg = /[abc]/,
    testTxt = 'abcd';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:a

使用[^]包含一系列字符,则能够匹配除所包含字符外的任意一个字符。

var reg = /[^abc]/,
    testTxt = 'abcde';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:d

能够与 '多种字符' 匹配的表达式

正则表达式中的一些方法,可以匹配‘多种字符’其中的任意一个。同[]跟[^]一样,属于一个字符类。如:\d,\s等。

下表列出了,正则表达式的字符类:

正则表达式的字符类
字符 匹配
[...] 方括号内的任意字符
[^...] 不在方括号内的任意字符
. 除换行符和其他Unicode行终止符之外的任意字符
\w 任何ASCII字符组成的单词,等价于[a-zA-Z0-9]
\W 任何不是ASCII字符组成的单词,等价于〔^a-zA-Z0-9]
\s 任何Unicode空白符
\S 任何非Unicode空白符的字符,注意\w和\S不同
\d 任何ASCII数字,等价于[0-9]
\D 除了ASCII数字之外的任何字符,等价于[^0-9]
\b 退格直接量(特例)
var reg = /\d/,
    testTxt = '12sgf';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:1

注意:
a. 正则表达式字符类,虽然可以匹配任意字符,但是只能是一个,不是多个。
b. 在方括号内也可以匹配上面其他的字符类。如/[^\d]/:

var reg = /[^\d]/,
    testTxt = '124gf';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:g

重复匹配

前面提到的表达式,都只能匹配一次。如果在正则表达式后跟随修饰匹配次数的特殊符号,那么就可以进行重复匹配。

使用方法是:‘次数修饰’放在‘被修饰的表达式’后面。

下表总结了这些表示重复的正则语法:

正则表达式的重复字符语法
字符 含义
{n,m} 匹配前一项至少n次,但不能超过m次
{n,} 匹配前一项n次或者更多次
{n} 匹配前一项n次
? 匹配前一项0次或者1次,也就是说前一项是可选的,等价于{0,1}
+ 匹配前一项1次或多次,等价于{1, }
* 匹配前一项0次或多次,等价于{0,}

上表中列出的匹配重复字符,总是尽可能多的匹配。称为‘贪婪的’匹配。

var reg = /a*/,
    testTxt = 'aaab';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:aaa
var reg = /a+/,
    testTxt = 'aaab';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:aaa
var reg = /a?/,
    testTxt = 'aaab';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:a
var reg = /a{2}/,
    testTxt = 'aaab';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:aa
var reg = /a{3,}/,
    testTxt = 'aaaab';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:aaaa
var reg = /a{2,4}/,
    testTxt = 'aaaaaaaaab';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:aaaa

非贪婪模式

同样可以使用正则进行非贪婪匹配,即尽可能少的匹配,只需要在待匹配字符后跟随一个问号即可。如:‘??’、‘+?’等。

var reg = /a+?/,
    testTxt = 'aaab';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:a

注意:正则表达式的模式匹配总是会寻找字符串中第一个可能匹配的位置。即,该匹配是从字符串中的第一个字符开始的,并不考虑它的子串中更短的匹配。如:

var reg = /a+?b/,
    testTxt = 'aaab';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:aaab
var reg = /a+b/,
    testTxt = 'aaab';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:aaab

选择、分组和引用

模式选择符

字符‘|’用于分隔供选择的字符。

‘|’左右两边表达式之间是‘或’的关系。匹配左边或者右边。

注意:选择项的尝试匹配次序是从左到右,直到发现了匹配项。如果左边的选择匹配,就忽略右边的匹配项,即使它产生更好的匹配。

var reg = /Tom|Jack/,
    testTxt = 'I\'m Tom, he is Jack.';

document.write('匹配结果为:' + reg.exec(testTxt)); //匹配结果为:Tom

模式单元

把正则表达式的一部分放在圆括号内,你可以将他们行程组。然后你可以对整个组使用一些正则操作,例如重复操作符。

var reg = /java(script)?/,
    testTxt = 'java';

document.write('结果为:' + reg.exec(testTxt)); //结果为:java,
var reg = /java(script)?/,
    testTxt = 'javascript';

document.write('结果为:' + reg.exec(testTxt)); //结果为:javascript,script

/java(script)?/可以匹配字符串“java”,其后可以有“script”也可以没有。

向后引用

带圆括号的表达式的另一个用途是允许在同一正则表达式的候补引用前面的子表达式。

当用“()”定义了一个正则表达式组后,正则引擎则会把匹配的组按照顺序编号,存入缓存。

可以用“\数字”的方式,对匹配的组进行向后引用。“\1”引用第一个匹配的向后引用组,“\2”引用第二个组,以此类推,“\n”引用第n个组。而“\0”则引用整个被匹配的正则表达式本身。

var reg = /(\w)\1{4,}/,
    testTxt = 'aa bbbb abcdefg ccccc 111121111 999999999';

document.write('结果为:' + reg.exec(testTxt)); //结果为:ccccc,c

注意:因为子表达式可以嵌套另一个子表达式,所以它的位置是参与计数的左括号的位置。

var reg = /(\w([0-9]))\2{4,}/,
    testTxt = 'aa bbbb abcdefg ccccc 111121111 a99999999';

document.write('结果为:' + reg.exec(testTxt)); //结果为:a99999999,a9,9

可以对相同的向后引用组进行多次引用。

var reg = /([a-c])x\1x\1/,
    testTxt = 'aa bbbb axaxa ccccc 111121111 a99999999';

document.write('结果为:' + reg.exec(testTxt)); //结果为:axaxa,a

注意:
1. 一个后向引用不能用于它自身。/([abc]\1)/是错误的。
2. 向后引用不能用于字符集内部。/(a)[\1b]/中的“\1”并不表示向后引用。
3. 向后引用会降低引擎的速度,因为它需要存储匹配的组。(?:exp)会告诉引擎,对于组(exp)、不存储匹配的值以供向后引用。
4. 对正则表达式中前一个子表达式的引用,并不是指对子表达式模式的引用,而是指与那个模式相匹配的文本的引用。

下表对正则表达式的选择、分组和引用运算符做了总结:

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

指定匹配位置

有些正则表达式的元素匹配的是指定匹配发生的合法位置,而不是实际字符,像这样的元素我们称为正则表达式的锚。

常见的正则表达式的锚有:“^”表示开始位置,“$”表示结束位置,“\b”表示单词边界,“\B”表示非单词边界。

var reg = /^a\w/,
    testTxt = 'ax b99999999';

document.write('结果为:' + reg.exec(testTxt)); //结果为:ax
var reg = /\wa$/,
    testTxt = 'ax b99999999a';

document.write('结果为:' + reg.exec(testTxt)); //结果为:9a
var reg = /\bis\b/,
    testTxt = 'This island is beautiful';

document.write('结果为:' + reg.exec(testTxt)); //结果为:is

零宽断言

任意正则表达式都可以作为锚点条件。

零宽度正预测先行断言
“(?=exp)”就是一个零宽度正预测先行断言。它断言自身出现的位置后面能匹配表达式exp,但最终匹配的数据不包含exp。

var reg = /Windows (?=NT|XP)/,
    testTxt = "Windows 98, Windows NT, Windows 2000";

document.write('第一个与之匹配的子串的起始位置为:' + testTxt.search(reg) + '
'); //第一个与之匹配的子串的起始位置为:12 document.write('结果为:' + reg.exec(testTxt)); //结果为:Windows

上面的例子表示,是“Windows NT”中的“Windows”被匹配到了。

负向零宽断言

零宽度负预测先行断言
“(?!exp)”就是一个零宽度负预测先行断言。它断言自身出现的位置后面不能匹配表达式exp。

var reg = /Windows (?!98|2000)/,
    testTxt = "Windows 98, Windows NT, Windows 2000";

document.write('第一个与之匹配的子串的起始位置为:' + testTxt.search(reg) + '
'); //第一个与之匹配的子串的起始位置为:12 document.write('结果为:' + reg.exec(testTxt)); //结果为:Windows

上面的例子表示,是“Windows NT”中的“Windows”被匹配到了。

下表对正则表达式的锚字符做了总结:

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

用于模式匹配的String方法

String对象中,有几个方法可以用于正则表达式模式匹配。它们分别是:search()、replace()、match()和split()。

search()方法的参数是一个正则表达式(如果传人的不是正则表达式,它也会将其转换为正则表达式)。该方法返回第一个与之匹配的子串的起始位置,如果找不到匹配的子串,则返回-1。

注意,该方法不支持全局检索。但是支持其他两个修饰符(i, m)。

var reg = /java/i,
    testTxt = "Javascirpt不是java!";

document.write('第一个与之匹配的子串的起始位置为:' + testTxt.search(reg) + '
'); //第一个与之匹配的子串的起始位置为:12 document.write('结果为:' + reg.exec(testTxt)); //结果为:Java

replace()

replace()方法用于替换检索字符串。

它有两个参数,第一个参数是正则表达式(如果不是正则表达式,则自己搜索传入的字符串,不会将其转换为正则表达式);第二个参数是要替换的字符串。

在默认情况下,替换所匹配的第一个子串。当设置了全局修饰符,则替换字符串中所有匹配的子串。返回替换后的字符串。

var reg = /java/g,
    testTxt = "javascirpt不是java!";

document.write('结果为:' + testTxt.replace(reg, 'Java')); //结果为:Javascirpt不是Java! 

match()

match()方法只有一个参数,它是正则表达式(如果不是正则表达式,也会将其转换为正则表达式)。返回一个由匹配结果组成的数组。如果传入的正则表达式没有带参数g,那么只检索第一个匹配;如果有带参数g,则返回所有匹配结果。

var reg = /\d/g,
    testTxt = "1+2=3;";

document.write('结果为:' + testTxt.match(reg)); //结果为:1,2,3

注意:如果正则表达式中存在分组,那么哪怕match()执行的不是全局检索,它返回的也是一个数组。数组第一个元素存放完整的匹配,从第二个元素开始,就依次存放与分组匹配的字串。如果没有分组,直接检索第一个匹配。

var reg = /([jJ]ava)script/g,
    testTxt = "javascriptJavascript";

document.write('结果为:' + testTxt.match(reg)); //结果为:javascript,Javascript
var reg = /([jJ]ava)script/,
    testTxt = "javascriptJavascript";

document.write('结果为:' + testTxt.match(reg)); //结果为:javascript,java
var reg = /javascript/,
    testTxt = "javascriptJavascript";

document.write('结果为:' + testTxt.match(reg)); //结果为:javascript

split()

split()方法可以将调用它的字符串拆分为子串组成的数组。它有一个参数,即要使用的分隔符。

var reg = /\s*,\s*/,
    testTxt = "1, 2, 3, 4, 5";

document.write('结果为:' + testTxt.split(reg)); //结果为:1,2,3,4,5
var testTxt = "1, 2, 3, 4, 5";

document.write('结果为:' + testTxt.split(',')); //结果为:1, 2, 3, 4, 5

RegExp的方法

RegExp对象有两个用于执行模式匹配操作的方法,分别是exec()和test()。

exec()

exec()方法对一个指定的字符串执行一个正则表达式。它有一个参数,即被指定检索的字符串。

执行该方法时,如果找到了一个匹配,那么它将返回一个数组。与match()方法执行非全局检索一样,即数组第一个元素存放完整的匹配,从第二个元素开始,就依次存放与分组匹配的字串。如果它没有找到匹配,将返回null。

注意:不管正则表达式有没有设置全局修饰,exec()返回的结果都是一样的。这点与match()不一样。

var testTxt = "javascriptJavascript",
    reg = /([jJ]ava)script/g;

document.write('结果为:' + reg.exec(testTxt)); //结果为:javascript,java
var testTxt = "javascriptJavascript",
    reg = /([jJ]ava)script/;

document.write('结果为:' + reg.exec(testTxt)); //结果为:javascript,java
var testTxt = "javascriptJavascript",
    reg = /javascript/;

document.write('结果为:' + reg.exec(testTxt)); //结果为:javascript

第一个例子与第二个例子唯一的区别是一个设置了全局修饰而另一个没有。但是它们的返回结果是一样的。第三个例子没有分组字符,所以就只返回一个匹配。

exec()返回的结果数组还有两个属性,第一个属性是index,包含了发生匹配的字符位置;第二个属性是input属性,指向正则检索的字符串。如:

var testTxt = "javascriptJavascript",
    reg = /([jJ]ava)script/,
    result = reg.exec(testTxt);

document.write('发生匹配的字符位置为:' + result.index + '
'); //发生匹配的字符位置为:0 document.write('被检索的字符串为:' + result.input + '
'); //被检索的字符串为:javascriptJavascript

test()

test()方法的参数也是一个字符串,它也将对传入的参数进行检索,如果包含正则表达式的一个匹配结果,就返回true;否则返回false。

var testTxt = "javascriptJavascript",
    reg = /([jJ]ava)script/;

document.write('结果为:' + reg.test(testTxt)); //结果为:true

正则表达式语法汇总

正则表达式语法
字符 匹配/含义
字母和数字字符 自身
\o NUL字符(\u0000)
\t 制表符(\u0009)
\n 换行符(\u000A)
\v 垂直制表符(\u000B)
\f 换页符(\u000C)
\r 回车符(\u000D)
\xnn 由十六进制数nn指定的拉丁字符,例如,\x0A等价于\n
\uxxxx 由十六进制数~指定的Unicode字符,例如\u0009等价于\t
\cX 控制字符^X,例如,\cJ等价于换行符\n
[...] 方括号内的任意字符
[^...] 不在方括号内的任意字符
. 除换行符和其他Unicode行终止符之外的任意字符
\w 任何ASCII字符组成的单词,等价于[a-zA-Z0-9]
\W 任何不是ASCII字符组成的单词,等价于〔^a-zA-Z0-9]
\s 任何Unicode空白符
\S 任何非Unicode空白符的字符,注意\w和\S不同
\d 任何ASCII数字,等价于[0-9]
\D 除了ASCII数字之外的任何字符,等价于[^0-9]
\b 退格直接量(特例)
{n,m} 匹配前一项至少n次,但不能超过m次
{n,} 匹配前一项n次或者更多次
{n} 匹配前一项n次
? 匹配前一项0次或者1次,也就是说前一项是可选的,等价于{0,1}
+ 匹配前一项1次或多次,等价于{1, }
* 匹配前一项0次或多次,等价于{0,}
| 选择,匹配的是该符号左边的子表达式或右边的子表达式
(…) 组合,将几个项组合为一个单元,这个单元可通过“*”、“+”、“?”和“|”等符号加以修饰,而且可以记住和这个组合相匹配的字符串以供此后的引用使用
(?:…) 只组合,把项组合到一个单元,但不记忆与该组相匹配的字符
\n 和第n个分组第一次匹配的字符相匹配,组是圆括号中的子表达式(也有可能是嵌套的),组索引是从左到右的左括号数,“(?:”形式的分组不编码
^ 匹配字符串的开头,在多行检索中,匹配一行的开头
$ 匹配字符串的结尾,在多行检索中,匹配一行的结尾
\b 匹配一个单词的边界,简言之,就是位于字符、w和、W之间的位置,或位于字符、w和字符串的开头或者结尾之间的位置(但需要注意,[\b]匹配的是退格符)
\B 匹配非单词边界的位置
(?=p) 零宽正向先行断言,要求接下来的字符都与P匹配,但不能包括匹配P的那些字符
(?!p) 零宽负向先行断言,要求接下来的字符不与P匹配