零散专题02 正则表达式

正则表达式学习笔记。

[TOC]

元操作符

操作符 含义
. 除换行符(\n)之外的任意字符
\w 匹配字母、下划线、数字、汉字
\s 匹配任意空白字符
\d 任意数字
^ 匹配字符串开始
$ 匹配字符串结束
\b 单词分界处(单词的开头或结尾),用于限定单词范围
` `
[] 字符集,不关心顺序,使用-指定范围

反义操作符

与某些元字符匹配相反的内容

操作符 含义
\W 匹配字母、下划线、数字、汉字之外的任意字符
\S 匹配任意非空白字符
\D 任意非数字字符
\B 匹配不是单词开头或者结尾的位置
[^abc] 匹配字符集之外的字符

重复限定符

操作符 含义
* 重复零次或更多次
? 重复零次或一次
+ 重复一次或更多次
{n} 重复n
{n,} 重复n次或更多次
{n,m} 重复n次至m

分组

分组匹配

重复限定符值限制它左边最近的一个字符,如果需要匹配多个字符,就需要使用()来将括号中的内容作为一个整体。

例如,要匹配含有多个ab时:

1
^(ab)*

捕获组

使用()来进行匹配时,()中的内容会保存到内存中用数字或者显示命名的组里,以深度优先进行编号,后面可以通过序号或者名称来使用匹配的结果

捕获组存在的前提是整项已匹配

数字编号捕获组

捕获组默认使用数字编号。例如,有这样一个表达式:

1
(\d)(\d)

当用它来匹配12时,会有是三个分组:

  1. 12,整个表达式本身
  2. 1,第一个捕获组
  3. 2,第二个捕获组

在JavaScript的replace方法的第二个参数中,就可以使用$1来访问第一个捕获组

命名编号捕获组

语法:(?<name>exp)

作用:人为为捕获组添加名称,例如:

1
(?<a>\d)(?<b>\d)

上面的表达式,在replace方法中除了用$1来访问第一个捕获组外,还可以使用$<a>来访问

非捕获组

语法:(?:exp)

作用:与捕获组相反,如果指向进行分组匹配,而不需要保存到捕获分组中,就需要使用非捕获组

反向引用

匹配到捕获组后,会保存到内存中,不仅可以在外部引用,也可以在正则表达式内部进行引用,这就是反向引用(也叫后向引用)

反向引用会大大增加捕获组的能力,因为反向引用,引用的是匹配子表达式的内容,注意,不是子表达式本身,而是内容。这个内容可以用来查找一些重复的内容或者进行字符替换

引用的方式是在正则表达式内部使用\1来引用第一个捕获组,\2引用第二个捕获组,以此类推,\0对应的是整个正则表示

举个例子

举个例子,要查找'aabbbbgbddesddfiid'里面成对的字母

1
2
3
4
5
6
7
const str = 'aabbbbgbddesddfiid';

const reg = /(\w)\1/g

const result = str.match(reg);

console.log(result); // ["aa", "bb", "bb", "dd", "dd", "ii"]

字节跳动的面试题

一个在字节跳动的面试题,找出一个字符串中出现次数最多的字符,当时脑子抽,非要计算出每个字符出现的次数,存起来,然后取最大的

实际上使用反向引用根本就没有这么麻烦,使用match方法得到同一个字符串为一项组成的数组(就和上面的例子一样),然后按照树组成员的length排序即可

1
2
3
4
5
6
7
8
9
const str = 'asdfaaaa';

const result = [...str]
.sort()
.join('')
.match(/(\w)\1*/g)
.sort((a, b) => b.length - a.length)[0][0];

console.log(result); // a

零宽断言

零宽断言是为了判断一个位置,这个位置满足正则表达式的匹配,它不占字符,只匹配位置。零宽断言分为以下几种:

  • 正向先行断言
  • 正向后行断言
  • 负向先行断言
  • 负向后行断言

里面的正、负向指的是与匹配正则表达式还是非匹配的区别,先行、后行的区别是匹配前面(右侧)还是后面(左侧)的内容

正向先行断言

语法:(?=pattern)

作用:匹配pattern表达式前面的内容,返回这个位置。也就是说,此位置右侧的字符能满足pattern时条件为真,匹配此位置左侧的字符

举个例子,有这样一段HTML代码:

1
'<span class="read-count">阅读数:641</span>''

我们希望匹配出641,就可以使用正向先行断言,641前面的字符是</span>,匹配出这个位置即可:

1
2
3
4
5
6
const str = '<span class="read-count">阅读数:641</span>';

// 注意,标签结束的 / 需要转义
const exp = /\d+(?=<\/span\>)/;

const result = str.match(exp)[0]; // 641

正向后行断言

语法:(?<=pattern)

作用:与正向先行断言的区别就是,匹配的是后面(即左侧)的内容满足表达式要求的位置。

使用正向后行断言,也可以完成上面的例子

1
2
3
4
5
const str = '<span class="read-count">阅读数:641</span>';

const exp = /(?<=阅读数:)\d+/;

const result = str.match(exp)[0]; // 641

负向先行断言

语法:(?!pattern)

作用:匹配非pattern表达式前面的内容,返回这个位置。

举个例子,要匹配出我爱祖国,我是祖国的花朵中不是的花朵前面的祖国,正则可以这样写:

1
祖国(?!的花朵)

负向后行断言

语法:(?<!pattern)

作用:匹配非pattern表达式后面的内容,返回这个位置。

3 下面的表达式表示的意义任意2-8位数字,^表示后面的内容是字符串的开始,$表示前面的内容是字符串的结束

1
^\d{2,8}$

贪婪匹配和非贪婪匹配

贪婪匹配

当正则表达式中包含重复限定符时,在整个表达式得到匹配的前提下,匹配尽可能多的字符,这种匹配方式叫做贪婪匹配

默认的限定符都是贪婪匹配的。

当多个量词同时出现时,如果能满足各自最大程度的匹配,就会互不干扰。如果不能满足,会根据深度优先原则,也就是从左到右的每个贪婪量词,优先最大数量的满足,剩余的再分配给下一个量词匹配

非贪婪匹配

与贪婪匹配相反的就是非贪婪匹配,它在满足整个表达式满足匹配的前提下,匹配尽可能少的字符。

贪婪量词是在普通的贪婪量词后面加上?,例如*?+???{n,m}?

操作符 含义
*? 匹配字母、下划线、数字、汉字之外的任意字符
\S 匹配任意非空白字符
\D 任意非数字字符
\B 匹配不是单词开头或者结尾的位置
[^abc] 匹配字符集之外的字符

JS中的正则表达式

JS中表达式,默认区分大小写,找到第一个匹配项就结束,单行寻找:

1
const reg = /pattern/
  • /pattern/后面添加标识符g,表示全局模式,应用于所有字符串
  • 加标识符i,表示不区分大小写
  • 加标识符m,表示多行模式,继续寻找下一行

JS常用的正则表达式的几个方式有replacetestexecmatch,详细的介绍可以参考这篇笔记《JS46 JS中的match和exec方法》

实例

正则验证空格

1
2
3
4
5
6
7
8
const reg = /\s/;

const testEmpty = str => reg.test(str)

console.log(testEmpty('a')); // false
console.log(testEmpty('a b')); // true
console.log(testEmpty('')); // false
console.log(testEmpty(' ')); // true

去除空格

1
2
3
4
5
6
const reg = /\s/g;

const replaceEmpty = str => str.replace(reg, '')

console.log(replaceEmpty('a')); // a
console.log(replaceEmpty(' a b ')); // ab

参考