0.前言
关于js的正则,它非常有用,掌握好正则,将会对代码逻辑的实现和业务的稳定性有很大帮助。但是正则表达式方法众多,规则难记。在此,我分享一下我的学习和记忆方法:首先理解正则表达式的创建以及参数的含义。其次弄明白和正则相关方法的分类(正则对象方法(RegExp.prototype)和字符串方法(string.prototype))和用法。然后要知道正则的一些默认行为,比如正则默认是贪婪匹配。最后要搞懂匹配规则,这些规则也要分组来记忆,比如元字符、转义符、预定义模式等。
1.概述
正则表达式(regular expression)用来按照“给定模式”匹配文本。常用来匹配Email地址等文本匹配。
创建正则表达式有两种方式:
var regex = /xyz/i;
var regex = new RegExp('xyz',i);
上面两种写法是等价的,都新建了一个正则表达式对象。推荐使用前者,因为前者的效率较高、便利且直观。
2.实例属性
- RegExp.prototype.ignoreCase:返回一个布尔值,表示是否设置了
i
修饰符。 - RegExp.prototype.global:返回一个布尔值,表示是否设置了
g
修饰符。 - RegExp.prototype.multiline:返回一个布尔值,表示是否设置了
m
修饰符 - RegExp.prototype.lastIndex:返回一个数值,表示下一次开始搜索的位置。该属性可读写,但是只在进行连续搜索时有意义。
- RegExp.prototype.source:返回正则表达式的字符串形式(不包括反斜杠),该属性只读。
3.实例方法
3.1 RegExp.prototype.test()
返回一个布尔值,表示当前模式是否能匹配参数字符串。
如果正则表达式带有g
修饰符,则每一次test
方法都从上一次结束的位置开始向后匹配。
var r = /x/g;
var s = '_x_x';
r.lastIndex // 0
r.test(s) // true
r.lastIndex // 2
r.test(s) // true
r.lastIndex // 4
r.test(s) // false
带有g
修饰符时,可以通过正则对象的lastIndex
属性指定开始搜索的位置
var r = /x/g;
var s = '_x_x';
r.lastIndex = 4;
r.test(s) // false
3.2 RegExp.prototype.exec()
返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回null
。
var s = '_x_x';
var r1 = /x/;
var r2 = /y/;
r1.exec(s) // ["x"]
r2.exec(s) // null
如果正则表示式包含圆括号(即含有“组匹配”),则返回的数组会包括多个成员。第一个成员是整个匹配成功的结果,后面的成员就是圆括号对应的匹配成功的组。也就是说,第二个成员对应第一个括号,第三个成员对应第二个括号,以此类推。
var s = '_x_x';
var r = /_(x)/;
r.exec(s) // ["_x", "x"]
如果正则表达式加上g
修饰符,则可以使用多次exec
方法,下一次搜索的位置从上一次匹配成功结束的位置开始。
var reg = /a/g;
var str = 'abc_abc_abc'
while(true) {
var match = reg.exec(str);
if (!match) break;
console.log('#' + match.index + ':' + match[0]);
}
4. 字符串的实例方法
String.prototype.match()
:返回一个数组,成员是所有匹配的子字符串。String.prototype.search()
:按照给定的正则表达式进行搜索,返回一个整数,表示匹配开始的位置。String.prototype.replace()
:按照给定的正则表达式进行替换,返回替换后的字符串。String.prototype.split()
:按照给定规则进行字符串分割,返回一个数组,包含分割后的各个成员。
4.1 String.prototype.match()
对字符串进行正则匹配,返回匹配结果。匹配成功返回一个数组,匹配失败返回null
。带有g
修饰符,会一次性返回所有匹配成功的结果。
4.2 String.prototype.search()
返回第一个满足条件的匹配结果在整个字符串中的位置。如果没有任何匹配,则返回-1
。
4.3 String.prototype.replace()
4.3.1 替换匹配的值。它接受两个参数,第一个是正则表达式,表示搜索模式,第二个是替换的内容。
加g
修饰符,替换所有匹配成功的值。否则只替换第一个匹配成功的值。
应用,就是消除字符串两端空格:
var str = ' xxxxxxx ';
str.replace(/^s+|s+$/g, '')
4.3.2 replace方法的第二个参数可以使用$符号代替
- $&:匹配的子字符串。
- $`:匹配结果前面的文本。
- $’:匹配结果后面的文本。
- $n:匹配成功的第
n
组内容,n
是从1开始的自然数。 - $$:指代美元符号
$
。
'hello world'.replace(/(w+)s(w+)/, '$2 $1')
// "world hello"
'abc'.replace('b', '[$`-$&-$']')
// "a[a-b-c]c"
4.3.3 replace方法的第二个参数还可以是一个函数,将每一个匹配内容替换为函数返回值。
'3 and 5'.replace(/[0-9]+/g, function (match) {
return 2 * match;
})
// "6 and 10"
作为replace方法第二个参数的替换函数,可以接受多个参数。其中,第一个参数是捕捉到的内容,第二个参数是捕捉到的组匹配(有多少个组匹配,就有多少个对应的参数)。此外,最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置(比如从第五个位置开始),最后一个参数是原字符串。下面是一个网页模板替换的例子。
4.4 String.prototype.split()
按照正则规则分割字符串,返回一个由分割后的各个部分组成的数组。
// 非正则分隔
'a, b,c, d'.split(',')
// [ 'a', ' b', 'c', ' d' ]
// 正则分隔,去除多余的空格
'a, b,c, d'.split(/, */)
// [ 'a', 'b', 'c', 'd' ]
// 指定返回数组的最大成员
'a, b,c, d'.split(/, */, 2)
[ 'a', 'b' ]
贪婪匹配:
// 例一
'aaa*a*'.split(/a*/)
// [ '', '*', '*' ]
// 例二
'aaa**a*'.split(/a*/)
// ["", "*", "*", "*"]
如果正则表达式带有括号,则括号匹配的部分也会作为数组成员返回:
'aaa*a*'.split(/(a*)/)
// [ '', 'aaa', '*', 'a', '*' ]
5. 匹配规则
5.1 元字符
- 点字符(.) :匹配除回车(
r
)、换行(n
) 、行分隔符(u2028
)和段分隔符(u2029
)以外的所有字符。 - 位置字符
^
表示字符串的开始位置,$
表示字符串的结束位置。 - 选择符(
|
):表示“或关系”(OR),即cat|dog
表示匹配cat
或dog
。
5.2 转义符
正则表达式中那些有特殊含义的元字符,如果要匹配它们本身,就需要在它们前面要加上反斜杠。
正则表达式中,需要反斜杠转义的,一共有12个字符:^
、.
、[
、$
、(
、)
、|
、*
、+
、?
、{
和。
如果使用RegExp
方法生成正则对象,转义需要使用两个斜杠,因为字符串内部会先转义一次。
(new RegExp('1+1')).test('1+1')
// false
(new RegExp('1+1')).test('1+1')
// true
5.3 特殊字符
cX
表示Ctrl-[X]
,其中的X
是A-Z之中任一个英文字母,用来匹配控制字符。[b]
匹配退格键(U+0008),不要与b
混淆。n
匹配换行键。r
匹配回车键。t
匹配制表符 tab(U+0009)。v
匹配垂直制表符(U+000B)。f
匹配换页符(U+000C)。0
匹配null
字符(U+0000)。xhh
匹配一个以两位十六进制数(x00
-xFF
)表示的字符。uhhhh
匹配一个以四位十六进制数(u0000
-uFFFF
)表示的 Unicode 字符。
5.4 预定义模式
d
匹配0-9之间的任一数字,相当于[0-9]
。D
匹配所有0-9以外的字符,相当于[^0-9]
。w
匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]
。W
除所有字母、数字和下划线以外的字符,相当于[^A-Za-z0-9_]
。s
匹配空格(包括换行符、制表符、空格符等),相等于[ trnvf]
。S
匹配非空格的字符,相当于[^ trnvf]
。b
匹配词的边界。B
匹配非词边界,即在词的内部。
5.5 字符类
表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内,比如[xyz]
表示x
、y
、z
之中任选一个匹配。
5.5.1 脱字符(^)
如果方括号内的第一个字符(脱字符只有在字符类的第一个位置才有特殊含义,否则就是字面含义)是[^]
,则表示除了字符类之中的字符,其他字符都可以匹配。比如,[^xyz]
表示除了x
、y
、z
之外都可以匹配。
如果方括号内没有其他字符,即只有[^]
,就表示匹配一切字符,其中包括换行符。相比之下,点号作为元字符(.
)是不包括换行符的。
var s = 'Please yesnmake my day!';
s.match(/yes.*day/) // null
s.match(/yes[^]*day/) // [ 'yesnmake my day']
5.5.2 连字符(-)
对于连续序列的字符,连字符(-
)用来提供简写形式,表示字符的连续范围。比如,[abc]
可以写成[a-c]
,[0123456789]
可以写成[0-9]
,同理[A-Z]
表示26个大写字母。
由于存在ASCII编码顺序和常识顺序相悖的情况,不建议使用 -
。
5.6 重复类
模式的精确匹配次数,使用大括号({}
)表示。{n}
表示恰好重复n
次,{n,}
表示至少重复n
次,{n,m}
表示重复不少于n
次,不多于m
次。
/lo{2}k/.test('look') // true
/lo{2,5}k/.test('looook') // true
5.7 量词符
?
问号表示某个模式出现0次或1次,等同于{0, 1}
。*
星号表示某个模式出现0次或多次,等同于{0,}
。+
加号表示某个模式出现1次或多次,等同于{1,}
。
5.8 贪婪模式
上一小节的三个量词符,默认情况下都是最大可能匹配,即匹配直到下一个字符不满足匹配规则为止。这被称为贪婪模式。
var s = 'aaa';
s.match(/a+/) // ["aaa"]
上面代码中,模式是/a+/
,表示匹配1个a
或多个a
,那么到底会匹配几个a
呢?因为默认是贪婪模式,会一直匹配到字符a
不出现为止,所以匹配结果是3个a
。
如果想将贪婪模式改为非贪婪模式,可以在量词符后面加一个问号。
var s = 'aaa';
s.match(/a+?/) // ["a"]
5.9 组匹配
5.9.1 概述
正则表达式的括号表示分组匹配,括号中的模式可以用来匹配分组的内容。
/fred+/.test('fredd') // true
/(fred)+/.test('fredfred') // true
上面代码中,第一个模式没有括号,结果+
只表示重复字母d
,第二个模式有括号,结果+
就表示匹配fred
这个词。
正则表达式内部,还可以用n
引用括号匹配的内容,n
是从1开始的自然数,表示对应顺序的括号。
var tagName = /<([^>]+)>[^<]*</1>/;
tagName.exec("<b>bold</b>")[1]
// 'b'
上面代码中,圆括号匹配尖括号之中的标签,而1
就表示对应的闭合标签。
5.9.2 非捕获组
(?:x)
称为非捕获组(Non-capturing group),表示不返回该组匹配的内容,即匹配的结果中不计入这个括号。
var m = 'abc'.match(/(?:.)b(.)/);
m // ["abc", "c"]
// 正常匹配
var url = /(http|ftp)://([^/rn]+)(/[^rn]*)?/;
url.exec('http://google.com/');
// ["http://google.com/", "http", "google.com", "/"]
// 非捕获组匹配
var url = /(?:http|ftp)://([^/rn]+)(/[^rn]*)?/;
url.exec('http://google.com/');
// ["http://google.com/", "google.com", "/"]
5.9.2 先行断言
x(?=y)
称为先行断言(Positive look-ahead),x
只有在y
前面才匹配,y
不会被计入返回结果。比如,要匹配后面跟着百分号的数字,可以写成/d+(?=%)/
。
“先行断言”中,括号里的部分是不会返回的。
var m = 'abc'.match(/b(?=c)/);
m // ["b"]
5.9.3 先行否定断言
x(?!y)
称为先行否定断言(Negative look-ahead),x
只有不在y
前面才匹配,y
不会被计入返回结果。比如,要匹配后面跟的不是百分号的数字,就要写成/d+(?!%)/
。
/d+(?!.)/.exec('3.14')
// ["14"]