什么是正则?
RegExp: regular expression
按照某种规则去匹配合适条件的字符串
新建正则表达式:
一、字面量形式
var reg = /xyz/
二、使用RegExp构造函数
var reg = new RegExp('xyz')
注意:
1.可以接受第二个参数,全局标志
var reg = new RegExp('xyz','gim')
2.有时需要双重转义字符
var reg = new RegExp('\\wxyx\\w') // \w单词字符
实例属性:
1.修饰符,布尔值,
RegExp.prototype.ignoreCase:返回一个布尔值,表示是否设置了i修饰符。
RegExp.prototype.global:返回一个布尔值,表示是否设置了g修饰符。
RegExp.prototype.multiline:返回一个布尔值,表示是否设置了m修饰符。
注意都是只读的
2.与修饰符无关的属性
(1)RegExp.prototype.lastIndex:返回一个整数,表示下一次开始搜索的位置。该属性可读写,但是只在进行连续搜索时有意义
(2)RegExp.prototype.source:返回正则表达式的字符串形式(不包括反斜杠),该属性只读
实例方法
(1)RegExp.prototype.test()
匹配,有就返回true无就返回false
注意:如果正则表达式带有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
所以经常会出现同一句代码返回效果却不一样,就是这个lastIndex搞得鬼
(2)RegExp.prototype.exec()
用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回null。
注意:如果正则表达式中含有()组匹配,那么返回的结果也会有成功匹配的组成员
var s = '_x_x';
var r = /_(x)/;
r.exec(s) // ["_x", "x"]
返回的数组还包括以下其他属性:
- input:整个原字符串。
- index:整个模式匹配成功的开始位置(从0开始计数)。
如果正则表达式加上g修饰符,则可以使用多次exec方法,下一次搜索的位置从上一次匹配成功结束的位置开始
利用g修饰符允许多次匹配的特点,可以用一个循环完成全部匹配。
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]);
}
// #0:a
// #4:a
// #8:a
js字符串与正则表达式相关的方法
- String.prototype.match():返回一个数组,成员是所有匹配的子字符串。
- String.prototype.search():按照给定的正则表达式进行搜索,返回一个整数,表示匹配开始的位置。
- String.prototype.replace():按照给定的正则表达式进行替换,返回替换后的字符串。
- String.prototype.split():按照给定规则进行字符串分割,返回一个数组,包含分割后的各个成员。
- match() 方法
类似于正则对象的exec方法,匹配成功返回一个数组,匹配失败返回null
不同之处在于,match()返回所有成功匹配结果,exec()一次只能返回一个
var s = 'abba';
var r = /a/g;
s.match(r) // ["a", "a"]
r.exec(s) // ["a"]
此外,match()不支持lastIndex属性
- search()方法
字符串对象的search方法,返回第一个满足条件的匹配结果在整个字符串中的位置。如果没有任何匹配,则返回-1。
'_x_x'.search(/x/)
- replace()方法
字符串对象的replace方法可以替换匹配的值。它接受两个参数,第一个是正则表达式,表示搜索模式,第二个是替换的内容。
str.replace(search, replacement)
有g全局标志就全部替换,没有就替换一个
'aaa'.replace('a', 'b') // "baa"
'aaa'.replace(/a/, 'b') // "baa"
'aaa'.replace(/a/g, 'b') // "bbb"
具体实例 模拟实现trim()函数
var str = ' #id div.class ';
str.replace(/^\s+|\s+$/g, '')
// "#id div.class"
replace方法的第二个参数可以使用美元符号$,用来指代所替换的内容。
$&:匹配的子字符串。
$`:匹配结果前面的文本。
$':匹配结果后面的文本。
$n:匹配成功的第n组内容,n是从1开始 的自然数。
$$:指代美元符号$。
'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')
// "world hello" 适用于组成员变换位置
'abc'.replace('b', '[$`-$&-$\']')
// "a[a-b-c]c"
注意:replace第二个参数还可以是函数,将每个匹配内容替换成函数的返回值
'3 and 5'.replace(/[0-9]+/g, function (match) {
return 2 * match;
})
// "6 and 10" 将匹配到的3和5都乘了2
function 的参数有:
- 匹配的字符串
- 分组内容,如果有3个分组,这部分就有三个参数都传入
- index 捕捉到的内容在字符串的位置
- 原字符串
例子
var prices = {
'p1': '$1.99',
'p2': '$9.99',
'p3': '$5.00'
};
var template = '<span id="p1"></span>'
+ '<span id="p2"></span>'
+ '<span id="p3"></span>';
template.replace(
/(<span id=")(.*?)(">)(<\/span>)/g,
function(match, $1, $2, $3, $4){
return $1 + $2 + $3 + prices[$2] + $4;
}
);
// "<span id="p1">$1.99</span><span id="p2">$9.99</span><span id="p3">$5.00</span>"
分析一下正则表达式
'<span id="p1"></span>'
$1: (<span id=")
$2: (.*?) 就是p1 后面利用$2在哈希中取值prices[$2]
$3:(">)
$4:(</span>)
很经典,四个组匹配$1-$4
split()方法
字符串对象的split方法按照正则规则分割字符串,返回一个由分割后的各个部分组成的数组。
str.split(separator, [limit])
第一个参数是正则表达式,表示分隔规则,第二个参数是返回数组的最大成员数。
// 非正则分隔
'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', '*' ]
匹配规则
1.字面量字符和元字符
“字面量字符”:也叫原意文本字符
/dog/ 就是表示d、o、g三个字母连在一起元字符
'* + ? $ ^ . | \ ( ) { } [ ]' 需要用\转义
(1).字符
点字符(.)匹配除回车(\r)、换行(\n) 、行分隔符(\u2028)和段分隔符(\u2029)以外的所有字符
(2) 位置字符
^ 开始 $ 结束
(3) 或字符 |
2.特殊字符
特殊字符 | 含义 |
---|---|
\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 字符。 |
3.字符类
使用元字符[]来构造一类
[abc] 就是把a、b、c、归为一类
脱字符(^)
如果方括号内的第一个字符是[^],
则表示除了字符类之中的字符,其他字符都可以匹配。比如,[^xyz]表示除了x、y、z之外都可以匹配。
/[^abc]/.test('hello world') // true
/[^abc]/.test('bbc') // false
上面代码中,字符串hello world不包含字母a、b、c中的任一个,所以返回true;字符串bbc不包含a、b、c以外的字母,所以返回false。
如果方括号内没有其他字符,即只有[^],就表示匹配一切字符,其中包括换行符。相比之下,点号作为元字符(.)是不包括换行符的。
var s = 'Please yes\nmake my day!';
s.match(/yes.*day/) // null
s.match(/yes[^]*day/) // [ 'yes\nmake my day']
上面代码中,字符串s含有一个换行符,点号不包括换行符,所以第一个正则表达式匹配失败;第二个正则表达式[^]包含一切字符,所以匹配成功。
注意,脱字符只有在字符类的第一个位置才有特殊含义,否则就是字面含义。
字符类去反^ [^abc] 非a或b或c的内容
范围类 - [a-z] a到z任意字符
中间可以连写[a-zA-Z]
4.预定义模式
预定义字符 | 含义 |
---|---|
\d | 匹配0-9之间的任一数字,相当于[0-9]。 |
\D | 匹配所有0-9以外的字符,相当于[^0-9]。 |
\w | 匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]。 |
\W | 除所有字母、数字和下划线以外的字符,相当于[^A-Za-z0-9_]。 |
\s | 匹配空格(包括换行符、制表符、空格符等),相等于[ \t\r\n\v\f]。 |
\S | 匹配非空格的字符,相当于[^ \t\r\n\v\f]。 |
\b | 匹配词的边界。 |
\B | 匹配非词边界,即在词的内部。 |
5.重复类+量词
符号 | 含义 |
---|---|
{n} | 出现n次 |
{n,m} | 出现n到m次 |
{n,} | 至少出现n次 |
? | 出现零次或1次(<=1) |
+ | 出现一次或多次(>=1) |
* | 出现零次或者多次(任意次) |
6.贪婪模式和非贪婪模式
默认情况下是贪婪模式,会最大可能匹配
var s = 'aaa';
s.match(/a+/) // ["aaa"]
上面代码中,模式是/a+/,表示匹配1个a或多个a,那么到底会匹配几个a呢?因为默认是贪婪模式,会一直匹配到字符a不出现为止,所以匹配结果是3个a。
如果想将贪婪模式改为非贪婪模式,可以在量词符后面加一个问号。
var s = 'aaa';
s.match(/a+?/) // ["a"]
上面代码中,模式结尾添加了一个问号/a+?/,这时就改为非贪婪模式,一旦条件满足,就不再往下匹配。
非贪婪模式 | 含义 |
---|---|
+? | 表示某个模式出现1次或多次,匹配时采用非贪婪模式。 |
*? | 表示某个模式出现0次或多次,匹配时采用非贪婪模式。 |
?? | 表格某个模式出现0次或1次,匹配时采用非贪婪模式。 |
7.修饰符
修饰符 | 含义 |
---|---|
g修饰符 | 表示全局匹配(global),加上它以后,正则对象将匹配全部符合条件的结果,主要用于搜索和替换。 |
i修饰符 | 以后表示忽略大小写(ignorecase)。 |
m修饰符 | 表示多行模式(multiline),会修改^和$的行为 |
8.组匹配
正则表达式的括号表示分组匹配,括号中的模式可以用来匹配分组的内容。
var m = 'abcabc'.match(/(.)b(.)/);
m// ['abc', 'a', 'c']
注意,使用组匹配时,不宜同时使用g修饰符,否则match方法不会捕获分组的内容。
var m = 'abcabc'.match(/(.)b(.)/g);
m // ['abc', 'abc']
正向引用:正则表达式内部,还可以用\n引用括号匹配的内容
/(.)b(.)\1b\2/.test("abcabc")
// true \1引用第一个组(.)\2引用第二个组(.)
反向引用 $1 $2
'abc'.replace(/(a)b(c)/,'$2b$1')
//"cba"
非捕获组:
(?:x)称为非捕获组(Non-capturing group),表示不返回该组匹配的内容,即匹配的结果中不计入这个括号。
var m = 'abc'.match(/(?:.)b(.)/);
m // ["abc", "c"] 第一个组设置成了非捕获组了,所以不在返回的数组里
前瞻
先行断言(正向前瞻):reg(?=y)
先行否定断言(负向前瞻):x(?!y)
就好像你站在原地,向前眺望:
正向前瞻型分组 - 你前方是什么东西吗?
负向前瞻型分组 - 你前方不是什么东西吗?
太拗口了,我喜欢称之为肯定表达式与否定表达式。先举个正向前瞻的例子:
var reg = /kid is a (?=doubi)/
reg.test('kid is a doubi') // true
reg.test('kid is a shabi') // false
kid is a 后面跟着什么?如果是doubi才能匹配成功。
而负向前瞻则刚好相反:
var reg = /kid is a (?!doubi)/
reg.test('kid is a doubi') // false
reg.test('kid is a shabi') // true
如果前瞻型分组也不会捕获值。那么它与非捕获型的区别是什么?看例子:
var reg, str = "kid is a doubi"
reg = /(kid is a (?:doubi))/
reg.test(str)
RegExp.$1 // kid is a doubi
reg = /(kid is a (?=doubi))/
reg.test(str)
RegExp.$1 // kis is a
可见,非捕获型分组匹配到的串,仍会被外层的捕获型分组捕获到,但前瞻型却不会。当你需要参考后面的值,又不想连它一起捕获时,前瞻型分组就派上用场了。
最后,JS不支持后瞻型分组。