正则介绍

正则表达式(英语: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对象方法

  1. 字面量方式
    字面量方式,由包含在斜杠之间的模式组成,如/abc/,脚本加载后,正则表达式字面量就会被编译。当正则表达式保持不变时,使用此方法可获得更好的性能
  2. 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

具体的使用方法略,感兴趣的小伙伴请自行百度了