正则的用法
const regex = /abc/g
const regex2 = new RegExp('abc', 'g')
regex 和 regex2的匹配内容是一样的,都是全局匹配abc字符串,
只不过regex是用正则表达式字面量,而regex2是调用RegExp构造函数写法。
元字符
^ 匹配字符串的开始 /^a/ 表示匹配以a开始
$ 匹配字符串的结尾 /a$/ 表示匹配以a结尾
\d [0-9] 表示0-9的任意一位数字 /^\d/ 表示以一个数字开始
\D [^0-9] 表示除数字外的任意字符 /^\D/ 表示不能以数字开始
\w [0-9a-zA-Z_] 表示数字、大小写字母和下划线
\W [^0-9a-zA-Z_] 表示非单词字符
\s [\t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符
\S [^ \t\v\n\r\f] 非空白符
\b 匹配一个单词边界
\B 匹配非单词边界
. [^\n\r\u2028\u2029]。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外。
要是想匹配字符串.需要转译\.
量词
{m, n} 出现了m-n次
{m} 出现了m次
{m,} 出现了至少mci
{,n} 最多出现了n次
? 出现了0次或1次, 等价于{0,1}
+ 最少出现了1次,等价于{1,}
* 出现0次或多次,等价于{0,}
贪婪与惰性
贪婪就是尽可能多的匹配,而惰性就是尽可能少的匹配
贪婪匹配写法:量词后面加个*
惰性匹配写法:量词后面加个?
/.*bbb/g.exec('abbbaabbbaaabbb1234') // abbbaabbbaaabbb
/.*?bbb/g.exec('abbbaabbbaaabbb1234') // abbb
贪婪模式用于匹配优先量词修饰的子表达式
惰性模式用于匹配忽略优先量词修饰子表达式
分组
正则中一对()即为一个分组
/(abx).*/.test('abxsss') 其中(abx) 就是一个分组
可使用构造函数的全局属性9来获取
/(abx).*/.exec('abxsss')
console.log(RegExp.$1) // abx
console.log(RegExp.$_) // abxsss
只能表示到9, 如果分组超过了九组,那就没法表示了
反向引用
/\d{4}(-|\/|\.)\d{2}\1\d{2}/
注意里面的\1,表示的引用之前的那个分组(-|\/|\.)。
不管它匹配到什么(比如-),\1都匹配那个同样的具体某个字符。
我们知道了\1的含义后,那么\2和\3的概念也就理解了,即分别指代第二个和第三个分组。
这里的分组只是在此之前的分组,在该反向引用之后的无法表示。
/\d{4}(-|\/|\.)\d{2}\2(\d{2})/
这里不能用\2来引用(\d{2})分组,这里只是当作匹配\2去匹配,而不是当作分组的引用
具名分组
/(\d{4}(-|\/|\.))\d{2}\2\d{2}/
这里的第二个分组应该是(-|\/|\.)
那/^((\d)(\d(\d)))$/的分组又是怎么分的呢?
对于过多括号嵌套情况,分清分组比较容易出错
括号嵌套的分组,应该从第一个(往里面数,每遇到一个(就是一个新的分组
具名分组就很好的解决了这个问题
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
这里就是用到了具名分组
可以通过\k<month>引用具体是哪个分组
/(?<year>\d{4})-(?<month>\d{2})-\k<month>/
具名分组结果会放在group属性里面
非捕获分组
也可以通过非捕获分组来降低分组的复杂度。非捕获分组是指不会将(?:)中的正则化为一个分组。
/^(?:(?:\d)(\d(\d)))\2$/
这里\2引用的分组是(\d)
零宽断言(环视)和负零宽断言
/z(?=x)y/ 表示z后面紧跟着的是x,但是不匹配x
/z(?!x)y/ 表示z后面紧跟着的不是x,但是不匹配x
/z(?<=x)y/ 表示y前面紧跟着的是x,但是不匹配x
/z(?<!x)y/ 表示y前面紧跟着的不是x,但是不匹配x
修饰符
1. g → global 全局搜索
2. i → ignoreCase 忽略大小写,大小写不敏感
3. m → multiline 换行
4. y → sticky “粘连”修饰符 后一次匹配都从上一次匹配成功的下一个位置开始
5. u → unicode Unicode 字符表示法
lastIndex
是正则表达式的一个可读可写的整型属性,用来指定下一次匹配的起始索引。
只有正则表达式使用了表示全局检索的 "g" 标志时,该属性才会起作用。此时应用下面的规则:
如果 lastIndex 大于字符串的长度,则 regexp.test 和 regexp.exec 将会匹配失败,然后 lastIndex 被设置为 0。
如果 lastIndex 等于字符串的长度,且该正则表达式匹配空字符串,则该正则表达式匹配从 lastIndex 开始的字符串。(then the regular expression matches input starting at lastIndex.)
如果 lastIndex 等于字符串的长度,且该正则表达式不匹配空字符串 ,则该正则表达式不匹配字符串,lastIndex 被设置为 0.。
否则,lastIndex 被设置为紧随最近一次成功匹配的下一个位置。
练习
1.检测是否满足
2016-06-12
2016/06/12
2016.06.12
这三种格式
/\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/.test('2012/12/13')
这样写有一个问题,那就是
/\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/.test('2012/12-13') 也是true
所以这里就得用到分组引用了
/\d{4}(-|\/|\.)\d{2}\1\d{2}/.test('2012/12-13') false
/\d{4}(-|\/|\.)\d{2}\1\d{2}/.test('2012/12/13') true
这里的\1指的是第一个分组匹配到了什么,这里就是什么
2.将 2016/12/04 改为 12/04/2016
传统做法可能是需要先以'/'分割字符串,然后在就行字符串拼接
现在可以这样写
'2016/12/04'.replace(/(\d{4})\/(\d{2})\/(\d{2})/, '$2/$3/$1')
或者
'2016/12/04'.replace(/(\d{4})\/(\d{2})\/(\d{2})/, function(...arg) {
return arg[2]+'/' + arg[3]+'/' + arg[1]
})
let result = /(?<year>\d{4})\/(?<month>\d{2})\/(?<day>\d{2})/.exec('2016/12/04')
console.log(result)
// 0: "2016/12/04"
// 1: "2016"
// 2: "12"
// 3: "04"
// groups:
// day: "04"
// month: "12"
// year: "2016"
// index: 0
// input: "2016/12/04"
3.可以正确筛选以下规则
'x=5' 5
'abcx=5' 5
'abc x=5' 5
'x=abc' null
str.match(/(?<=x=)\d+/)
4.可以正确筛选以下规则
'x=5' 5
'abcx=5' null
'abc x=5' 5
'x=abc' null
这个和上面的区别就在于x=之前是不是一个边界
str.match(/(?<=\bx=)\d+/)
不可以用一下的正则匹配
str.match(/(?<=\s+x=)\d+/)
因为这样的话'x=5'就不匹配了
5.可以正确筛选以下规则
'one "two three" four five six "seven eight" nine'
to
'"two three" four five six "seven eight"'
str.match(/".*"/g)
6.可以正确筛选以下规则
'one "two three" four five six "seven eight" nine'
to
['"two three"', '"seven eight"']
str.match(/".*?"/g)
这里和上面的区别就在于一个是贪婪匹配而这个是惰性匹配
7.可以正确筛选以下规则
'@@whatever@@@@whatever2@@'
to
'<blink>whatever</blink><blink>whatever2</blink>'
str.replace(/(@@)(.*?)(@@)/g, function(...arg) {
return '<blink>'+ arg[2]+'</blink>'
})
这里不能简单的将'@@'替换成'<blink>',因为'@@'必须是成对出现的
8.可以正确筛选以下规则
var text = "First line\nsecond line";
var regex = /(\S+) line\n?/y;
var match = regex.exec(text);
match[1]; // "First"
var match2 = regex.exec(text);
match2[1]// "Second"
var match3 = regex.exec(text);
match3 === null //"true"
这里是开启粘滞匹配的一个事例
9.测试以下代码
var re=/^\w$/g
re.test('a') true
re.test('a') false
re.test('a') true
re.test('a') false
re.test('a') true
re.test('a') false
re.test('a') true
re.test('a') false
...
为什么会出现这种情况呢?
这里就涉及到了lastIndex了,
var re=/^\w$/g
re.lastIndex = 0
re.test('a') true
这里正则匹配完成后re.lastIndex = 1
re.test('a') false
因为re.lastIndex = 1了,所以这里的re.test就会失效,当然re.exec也会失效。
可以理解为这时正则是从1这个位置去匹配的,所以匹配不到任何东西.只会就会再次把lastIndex改为0
re.lastIndex = 0
re.test('a') true
又重新从头开始匹配了,所以这里又再次为true了
结尾
本文并没有举例传统的一下正则,如邮箱格式,手机号码格式等,因为这些正则可以搜到很多。本文主要是用到了一下稍微高阶一点的正则方法。古人云:用好了正则,可以帮我们省下5000行代码。