正则介绍
正则表达式(英语:Regular Expression,在代码中常简写为regex、regexp或RE)使用单个字符串来描述、匹配一系列符合某个句法规则的字符串搜索模式,可用于文本搜索和文本替换。
语法为 /正则表达式主体/修饰符(可选)
比如 var patt = /runoob/i
基础知识
特殊字符
正则表达式中有一些字符含义比较特殊,或者某个单词加上\
会变成特殊的含义,以下列举出一些
正则表达式 | 匹配内容 | 说明 |
\n | 换行符 | new line |
\f | 换页符 | form feed |
\r | 回车符 | return |
\s | 空白符 | space |
\t | 制表符 | tab |
\v | 垂直制表符 | vertical tab |
[\b] | 回退符 | backspace,之所以使用[]符号是避免和\b重复 |
/ | / | 因为js中使用 |
\d | 单个数字, [0-9] | digit |
\D | 除了[0-9] | Not digit |
\w | 包括下划线在内的单个字符,[A-Za-z0-9_] | word |
\W | 非单字字符,[^a-zA-Z_] | not word |
\s | 匹配空白字符,包括空格、制表符、换页符和换行符,[\n\f\r\t\v] | space |
\S | 匹配非空白字符,[^\n\f\r\t\v] | not space |
. | 除了换行符之外的任何字符 | |
^ | 匹配字符串的开始 | |
$ | 匹配字符串的结束 | |
\b | 单词边界 | boundary |
\B | 非单词边界 | not boundary |
重复匹配
重复匹配一些内容时可以使用重复匹配修饰符
修饰符 | 匹配方式 |
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
重复n次 | |
重复n次或更多次 | |
重复n到m次 | |
重复n到m次 | |
| | 表示或者,两边表达式任一满足即可 |
懒惰模式和独占模式
懒惰模式
正则表达式在进行重复匹配时,默认是贪婪匹配模式,也就是说会尽量匹配更多内容,但是有的时候我们并不希望他匹配更多内容,这时可以通过 ? 进行修饰来禁止重复匹配
let name = "haaaaaaa";
// * 匹配零个或多个,加上问号表示匹配0个
// 匹配结果 ["h"]
let reg = /ha*?/g;
console.log(name.match(reg));
// + 匹配一个或多个,这里只匹配1个
// 匹配结果 ["ha"]
reg = /ha+?/g;
console.log(name.match(reg)); // ["so"]
// ? 匹配0个或者1个,这里只匹配0个
// 匹配结果 ["h"]
reg = /ha??/g;
console.log(name.match(reg)); // ["s"]
// {2,5} 匹配2到5个,这里只匹配2个
// 匹配结果 ["h"]
reg = /ha{2,5}?/g;
console.log(name.match(reg)); // ["soo"]
独占模式
如果在表达式后加上一个加号(+),则会开启独占模式。同贪婪模式一样,独占模式一样会匹配最长。不过在独占模式下,正则表达式尽可能长地去匹配字符串,一旦匹配不成功就会结束匹配而不会回溯。
这里所说的匹配涉及到正则表达式引擎内容,这里不做特殊说明,请想要知道详情的小伙伴先自行百度。
三种模式示例
贪婪 | 懒惰 | 独占 |
X? | X?? | X?+ |
X* | X*? | X*+ |
X+ | X+? | X++ |
X | X{n}? | X{n}+ |
X | X{n,}? | X{n,}+ |
X | X{n,m}? | X{n,m}+ |
模式修饰符
正则表达式有默认的执行方式,如果有一些其他匹配规则需求,可以使用模式修正符添加匹配规则。
修饰符 | 说明 |
i | 不区分大小写字母的匹配 |
g | 全局搜索所有匹配内容 |
m | 多行匹配 |
s | 视为单行忽略换行符,使用. 可以匹配所有字符 |
y | 从 regexp.lastIndex 开始匹配,表示匹配到不符合的就停掉,不会继续往后匹配,必须连续的符合条件的 |
u | 正确处理四个字符的 UTF-16 编码 |
高级用法
分组匹配
所有以 ()
包裹的表达式都是一个分组,每个分组是一个子表达式
分组匹配时没有添加 g 模式修正符时只匹配到第一个
举例如下:
let s = '@NgModule({declarations: [AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent]})';
let pattern = /\[(App.*?)].*\[(App.*?)]/;
let result = s.match(pattern);
if (result) {
console.log(`匹配到的完整内容:${result[0]}`);
console.log(`第一个分组:${result[1]}`);
console.log(`第二个分组:${result[2]}`);
console.log(`具名分组:${result.groups}`);
console.log(`在原始字段的索引位置:${result.index}`);
console.log(`原始字段:${result.input}`);
} else {
console.log('没有任何匹配');
}
最终结果如下:
匹配到的完整内容:[AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent]
第一个分组:AppComponent
第二个分组:AppComponent
具名分组:undefined
在原始字段的索引位置:25
原始字段:@NgModule({declarations: [AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent]})
回溯引用
所谓回溯引用(backreference)指的是模式的后面部分引用前面的子字符串。
你可以把它想象成是变量,回溯引用的语法像\1,\2,....,其中\1表示引用的第一个子表达式,\2表示引用的第二个子表达式,以此类推。而\0则表示整个表达式。
举例如下,这里只不过是把第二个分组的表达式使用\1进行表示,匹配结果和上面的是相同的:
let s = '@NgModule({declarations: [AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent]})';
let pattern = /\[(App.*?)].*\[(\1)]/;
let result = s.match(pattern);
if (result) {
console.log(result);
console.log(`匹配到的完整内容:${result[0]}`);
console.log(`第一个分组:${result[1]}`);
console.log(`第二个分组:${result[2]}`);
console.log(`具名分组:${result.groups}`);
console.log(`在原始字段的索引位置:${result.index}`);
console.log(`原始字段:${result.input}`);
} else {
console.log('没有任何匹配');
}
回溯引用在替换字符串中十分常用,语法上有些许区别,用$1,$2...来引用要被替换的字符串。
替换字符串可以插入下面的特殊变量名:
变量 | 说明 |
$$ | 插入一个 "$" |
$& | 插入匹配的子串 |
$` | 使用 $` 插入当前匹配的子串左边的内容 |
$' | 使用 $' 插入当前匹配的子串右边的内容 |
$n | 假如第一个参数是 RegExp 对象,并且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从1开始 |
举例如下:
let s = '@NgModule({declarations: [AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent]})';
let pattern = /\[App(.*?)]/g;
console.log('替换固定单词 => ', s.replace(pattern, 'Base$1'));
s = '-center=';
pattern = /center/g;
console.log('增加左右字符数量 => ', s.replace(pattern, "$`$`$`$&$'$'$'"));
s = '(010)12345678 (0246)7654321';
pattern = /\((\d{3,4})\)(\d{7,8})/g;
console.log('电话替换为 - 连接 => ', s.replace(pattern, '$1-$2'));
嵌套分组和不记录分组
嵌套分组就是有很多的 /(1(2(3)))/ 回溯引用时的状态,如 /(1(2(3)))-\1\2\3/
如果我们不想子表达式被引用,可以使用非捕获正则(?:regex)这样就可以避免浪费内存,分组里面加上 ?:
表示不记录该分组,但是分组的功能仍然生效,比如 let s = 'haaaaa';s.replace(/(ha)(?:a)/, '$1,$2')
只会返回 ha,$2
分组具名
匹配的分组也可以起一个名字,结果将保存在返回的 groups 字段中
- 使用 ? 给分组起别名
- 使用 $ 读取别名
举例如下:
let s = '@NgModule({declarations: [AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent]})';
let pattern = /\[(?<componentName>App.*)]/;
let result = s.match(pattern);
if (result) {
// 注意:加 g 模式修饰符返回结果类型为 Array ,没有 groups 属性
console.log(result.groups);
} else {
console.log('没有任何匹配');
}
最终结果如下:
{componentName: 'AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent'}
断言匹配
有时,我们需要限制回溯引用的适用范围,通过断言匹配就可以达到这个目的。
断言虽然写在括号中但它不是分组,所以不会在匹配结果中保存,可以将断言理解为正则中的条件。
断言用来声明一个应该为真的事实,正则表达式中只有当断言为真时才会继续进行匹配。
零宽先行断言
正向肯定查找,用来检查接下来出现的是不是某个特定的字符集,语法为 (?=exp)
举例如下:
let s = '@NgModule({declarations: [AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent]})';
let pattern = /\[AppComponent](?=})/;
let result = s.match(pattern);
if (result) {
console.log(result.index);
} else {
console.log('没有任何匹配');
}
最终结果如下:
在原始字段的索引位置:90
可以看到只匹配到了第二个
零宽后行断言
反向肯定查找,用来检查前面出现的是不是某个特定的字符集,语法为 (?<=exp)
举例如下:
let s = '@NgModule({declarations: [AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent]})';
let pattern = /(?<=p:\s)\[AppComponent]/;
let result = s.match(pattern);
if (result) {
console.log(result.index);
} else {
console.log('没有任何匹配');
}
最终结果如下:
在原始字段的索引位置:90
可以看到只匹配到了第二个
零宽负向先行断言
正向否定查找,用来检查接下来不应该出现匹配内容的字符集,语法为 (?!exp)
举例如下:
let s = '@NgModule({declarations: [AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent]})';
let pattern = /\[AppComponent](?!,)/;
let result = s.match(pattern);
if (result) {
console.log(result.index);
} else {
console.log('没有任何匹配');
}
最终结果如下:
在原始字段的索引位置:90
可以看到只匹配到了第二个
零宽负向后行断言
反向否定查找,用来检查前面不应该出现的字符集,语法为 (?<!exp)
举例如下:
let s = '@NgModule({declarations: [AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent]})';
let pattern = /(?<!s:\s)\[AppComponent]/;
let result = s.match(pattern);
if (result) {
console.log(result.index);
} else {
console.log('没有任何匹配');
}
最终结果如下:
在原始字段的索引位置:90
可以看到只匹配到了第二个
正则可视化及测试工具
以下网站均可实现一定的正则可视化
- regex101
- Regexper
- Regulex
以下网站可以进行正则表达式的测试
- 正则表达式在线测试(提供现成demo,有广告)
- 正则表达式在线测试 | 菜鸟工具
js 使用正则表达式
变量转化为正则表达式
我就是为了这个醋包了这顿饺子
js中的正则表达式有两种创建RegExp对象方法
- 字面量方式
字面量方式,由包含在斜杠之间的模式组成,如/abc/
,脚本加载后,正则表达式字面量就会被编译。当正则表达式保持不变时,使用此方法可获得更好的性能 - RegExp对象的构造函数
我们正则表达式中可能有些内容需要通过变量进行动态替换,那么怎么把变量转换为正则表达式呢。可以直接使用构造函数 new RegExp 创建正则表达式。
let s = '@NgModule({declarations: [AppComponent], imports: [CoreModule], providers: [], bootstrap: [AppComponent]})';
let componentName = 'AppComponent';
let regExpString = `(?<!s:\\s)\\[${componentName}]`;
let pattern = new RegExp(regExpString);
let result = s.match(pattern);
if (result) {
console.log(`在原始字段的索引位置:${result.index}`);
} else {
console.log('没有任何匹配');
}
最终结果如下:
在原始字段的索引位置:90
常用方法
- RegExp对象
- exec 需要捕获结果时使用
- test 只是为了判断是否匹配时使用
- String对象
- match
- replace
具体的使用方法略,感兴趣的小伙伴请自行百度了