我们在开发的过程中总会碰到一些需求需要做字符串匹配,当遇到一些稍微复杂一点的匹配规则时,如果我们对正则还不那么清晰,我们总是会去网上搜索一些现成的正则匹配,ctrl+c,ctrl+v。时间长了我们对这种修修补补的方式不厌其烦。那么今天就对正则表达式做足功夫,磨刀不误砍柴工,首先推荐一个学习正则的一个可视化的工具网址: https://regexper.com,输入正则规则即可生成匹配规则的流程图。了解并吃透正则会为我们以后开发节省很多时间和精力,一劳永逸。先从最基本的概念开始吧,let go!
元字符
概念:有特殊含义的特殊字符,eg: * + ? $ ^ . | \ ( ) { } [ ]等
通用含义元字符
在大部分语言语境中代表同一种含义,如下表:
字符 | 含义 |
---|---|
\t | 水平制表符 |
\v | 垂直制表符 |
\n | 换行符 |
\r | 回车符 |
\0 | 空字符 |
\f | 换页符 |
\cX | Ctrl+X |
其他含义元字符
1. 字符类
- 我们可以使用元字符[]来构建一个简单的字符类
- 所谓的类是指符合某些特性的对象,一个泛指,而不是特指某个字符
- 表达式[abc]归为一类,表达式可以匹配这些类的字符
2. 字符类取反
- 我们可以使用元字符^来创建反向类/负向类
- 反向类的意思是不属于某个类的内容
- 表达式[^abc]表示不是字符a或b或c的内容
3. 范围类
- 正则表达式还提供了范围类
- 我们使用[a-z]来连接两个字符表示从a到z的任意字符
- 这是个闭区间也就包含a和z本身
4. 预定义类
- 正则表达式提供了一些特殊字符表示自一组比较复杂的匹配规则,如下表:
字符 | 等价 | 含义 |
---|---|---|
. | [^\r\n] | 除了回车符和换行符之外的所有字符 |
\d | [0-9] | 数字字符 |
\D | [^0-9] | 非数字字符 |
\s | [\t\n\x0B\f\r] | 空白符 |
\0 | [^\t\n\x0B\f\r] | 非空白符 |
\w | [a-zA-Z_0-9] | 单词字符(字母数字下划线) |
\W | [^a-zA-Z_0-9] | 非单词字符 |
5. 边界
- 正则表达式还提供了几个常用的边界匹配字符,如下表:
字符 | 含义 |
---|---|
^ | 以xxx开始 |
$ | 以xxx结束 |
\b | 单词边界 |
\B | 非单词边界 |
6. 量词
字符 | 含义 |
---|---|
? | 出现一次或0次(最多出现一次) |
+ | 出现一次或多次(最少出现一次) |
* | 出现0次或多次(任意次) |
{n} | 出现n次 |
{n, m} | 出现n到m次 |
{n, } | 至少出现n次 |
js正则的贪婪模式和非贪婪模式
- 贪婪模式:\d{3,6}匹配数字3到6次,那么在匹配的过程中会尽可能多的的匹配。如:
'12345678'.replace(/\d{3,6}/, 'X') // "X78"
- 非贪婪模式:在量词后面加上 ? ,可以让正则表达式尽可能少的匹配,也就是非贪婪模式。
'12345678'.replace(/\d{3,6}?/, 'X') // "X45678"
分组
对一组正则规则加(),可以对正则进行分组,
比如:对taoyouyou这组单词匹配三次
taoyouyou{3}
可视化后能看到只是最后一个单词u重复了3次,并没有达到目的
(taoyouyou){3}
taoyouyou重复了3次
捕获组引用
对字符串进行分组后,
捕获组的编号是按照“(”出现的顺序,从左到右,从1开始进行编号的 。
在replace方法中可以通过$number(number表示捕获组的编号)进行引用
在js表达式中分组引用通过RegExp对象暴露出来,
即通过RegExp.$number引用
例如实现2019-02-22 => 02/22/2019的转化。
'2019-02-22'.replace(/(\d{4})-(\d{2})-(\d{2})/,'$2/$3/$1') // "02/22/2019"
反向引用
正则表达式中所定义的捕获的反向引用指的是
将捕获作为正则表达式中能够成功匹配术语时的候选字符串
这种术语表示法是在反斜杆后面加一个要引用的捕获数量。
捕获组捕获到的内容,不仅可以在正则表达式外部通过程序进行引用,也可以在正则表达式内部进行引用,这种引用方式就是反向引用。反向引用的作用通常是用来查找或限定重复、查找或限定指定标识配对出现等等。
例如:([a-z])\1{2}也就表达连续三个相同的小写字母。
忽略分组
不需要捕获某些分组只需要在分组内加 ?: 就可以了。
(?:taoyouyou).(ok)
前瞻
- 正则表达式从文本头部向尾部解析,文本尾部方向称为 “前”
- 前瞻:正则表达式匹配到规则的时候,向前检查是否符合断言,后顾/后瞻方向相反
- JavaScript不支持后顾
- 符合和不符合某项断言称为肯定/正向匹配和否定/负向匹配
名称 | 正则 | 含义 |
---|---|---|
正向前瞻 | exp(?=assert) | |
负向前瞻 | exp(?!assert) | |
正向后顾 | exp(?<=assert) | JavaScript不支持 |
负向后顾 | exp(?<!assert) | JavaScript不支持 |
正则对象属性
- global: 是否全文匹配,默认false,匹配规则后面加g开启
- ignoreCase: 是否大小写敏感, 默认false,匹配规则后面加i开启
- multiline: 多行搜索, 默认false,匹配规则后面加m开启
- lastIndex: 当前表达式匹配内容最后一个字符的下一个位置
- source: 正则表达式的文本字符串
这几个属性都是只读属性
reg=/\d/gmi
reg.global // true
reg.ignoreCase // true
reg.multiline // true
reg.lastIndex // 0
reg.source // "\d"
对象方法
1. 正则test方法
RegExp.prototype.test(str)
- 用于测试字符串参数中是否存在匹配正则表达式模式的字符串
- 如果存在则返回true,否则返回false
需要注意的是:
var reg2=/\w/g;
进行reg2.test(‘ab’)时,第三次会变成false
原因:
while(reg2.test('ab')){
console.log(reg2.lastIndex);
}
即进行test时,正则表达式的lastIndex会发生变化
输出:1 2 表示第一个匹配上了、第二个匹配上了
2. 正则exec方法
RegExp.prototype.exec(str)
- 使用正则表达式模式对字符串参数进行匹配搜索,并将更新全局对象属性以反映匹配结果
- 如果没有匹配则返回null,否则返回一个结果数组:
属性index(声明匹配文本的第一个字符的位置), input(存放被检索的字符串string)
数组本身: [与正则表达式相匹配的文本,与正则对象的第一个子表达式(分组)相匹配的文本, 与正则对象的第二个子表达式(分组)相匹配的文本,…… ]
var reg3=/\d(\w)(\w)\d/; // 非全局搜索只能匹配到第一个结果
var reg4=/\d(\w)(\w)\d/g; // 全局搜索匹配全文
var ts = '$1az2bb3cy4dd5ee'
var ret1= reg3.exec(ts) // (3) ["1az2", "a", "z", index: 1, input: "$1az2bb3cy4dd5ee", groups: undefined]
console.log(reg3.lastIndex+'\t'+ret1.index+'\t'+ret1.toString())
// "0 1 1az2, a, z"
while(ret2 = reg4.exec(ts)){
console.log(reg4.lastIndex+'\t'+ret2.index+'\t'+ret2.toString())
}
// "5 1 1az2, a, z"
// "11 7 1az2, a, z"
3. 字符串对象search方法
String.prototype.search(reg)
- search方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串
- 返回第一个匹配结果index,查不到返回-1
- search()不执行全局匹配,它将忽略标志g, 并总是从字符串的开始进行检索
4. 字符串对象match方法
String.prototype.match(reg)
- match方法将检索字符串,以找到一个或多个与regexp相匹配的字符串
- regexp是否具有标志g对结果影响很大
非全局调用
- 返回数组的第一个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本
- 除了常规的数组元素之外,返回的数组还含有2个对象属性:index声明匹配文本的起始字符在字符串的位置,input声明对stringObject的引用
- 全局调用的返回与正则表达式的exec方法相类似,即:[与正则表达式相匹配的文本,与正则对象的第一个子表达式(分组)相匹配的文本, 与正则对象的第二个子表达式(分组)相匹配的文本,…… ],数组有index和input属性
全局调用
- 如果regexp具有标志g则match方法将执行全局检索,找到字符串中的所有匹配子字符串
- 没有找到任何匹配的子串,则返回null
- 如果找到了一个或多个匹配子串,则返回-一个数组
- 数组元素中存放的是字符串中所有的匹配子串,而且也没有index属性或input属性
var reg3=/\d(\w)(\w)\d/; // 非全局搜索只能匹配到第一个结果
var reg4=/\d(\w)(\w)\d/g; // 全局搜索匹配全文
var ts = '$1az2bb3cy4dd5ee'
var ret1= ts.match(reg3) // (3) ["1az2", "a", "z", index: 1, input: "$1az2bb3cy4dd5ee", groups: undefined]
console.log(reg3.lastIndex+'\t'+ret1.index) // 0, 1
var ret2 = ts.match(reg4) // (2) ["1az2", "3cy4"]
console.log(reg4.lastIndex+'\t'+ret2.index) // 0 undefined
5. 字符串对象split方法
String.prototype.split(reg)
- 我们经常使用split方法把字符串分割为字符数组
eg: ‘a,b,c,d’.split(’,’); //[“a”, “b”, “c”, “d”] - 在一些复杂的分割情况下我们可以使用正则表达式解决
eg: ‘alb2c3d’.split(/\d/; //[“a”, “b”, “c”, “d”]
6. 字符串对象replace方法
String.prototype.replace
- String.prototype.replace(str, replaceStr)
- String.prototype.replace(reg, replaceStr)
- String.prototype.replace(reg, function)
function参数含义
function会在每次匹配替换的时候调用,有四个参数
1、匹配字符串
2、正则表达式分组内容,没有分组则没有该参数
3、匹配项在字符串中的index
4、原字符串
'a1b2c3d4e5'.replace(/\d/g, function(match, index, origin){
return parseInt(match) + 1;
}) // "a2b3c4d5e6"
'a1b2c3d4e5'.replace(/(\d)(\w)(\d)/g, function(match, group1, group2, group3, index, origin){
console.log(match)
return group1 + group3;
})
// 1b2
// 3d4
// "a12c34e5"