正则表达式介绍

353 阅读1分钟

什么是正则表达式?

使用特定的符号所描述的一种规则,用于字符串模式匹配,从而实现查找、校验和替换等功能。

如何创建正则表达式?

创建正则可以通过以下两种方式:

  1. 通过 RegExp 对象
new RegExp("RegExp", "i");
  1. 字面量形式, 用 // 包裹构建的正则(常用)
/RegExp/i

RegExp 对象不常用,不做过多介绍,更详细的内容请挪步至:MDN-RegExp()

正则表达式特殊字符

符号含义
g修饰符,表示全局匹配
i忽略匹配的大小写
^匹配开头位置
$匹配结尾位置
m多行匹配
.通配符,除换行符等的任意字符
[]字符组,表示可选项,只能为[]内的要求字符
-连字符,省略字符组中的数据
\s空白,即空格、换行、tab 缩进等(\S 为 \s 的非)
\w数字字母下划线 word(\W 为 \w 的非) 即 [a-zA-Z_]
\d一位数字 digit(\D 为 \d 的非) 即 [0-9]
\转义字符
?前面字符连续出现零次或一次 0 | 1
+前面字符连续出现一次或多次 >=1
*前面字符连续出现零次或多次 >=0
{m,n}m、n 为数字,表示前面字符连续出现 m 到 n 区间中的次数
\b边界符,它的前一个字符和后一个字符不全是 \w,\B 是其非,即除了 \b 的其他位置
?=p指 p 字符前面的位置,此位置后面匹配 p, ?!p 是 ?=p 的非
?<=p指 p 字符后面的位置,?<! 是 ?<=p 的非
()表示捕获分组,会把每个分组里的匹配的值保存起来,使用 $n 获取,多层嵌套以最外层开始
\nn 为数字,反向引用,引用之前的匹配的第 n 个分组
(?:)表示非捕获分组匹配的值不会保存

字符在正则中的使用

  • g
'abcabc'.replace(/a/,'g')

// 'gbcabc' 当匹配到第一个 a 时会被替换,然后会停止匹配。此处涉及到贪婪模式和惰性模式,后续会介绍到

'abcabc'.replace(/a/g,'g')

// 'gbcgbc' 所有 a 都会被替换

  • i
'abcABC'.replace(/a/g,'i')

// 'ibcABC' 只替换了 a

'abcABC'.replace(/a/gi,'i')

// 'ibciBC' a 和 A 都被替换
  • ^ $

匹配开头结尾的 位置

'abc123'.replace(/^/g,'M')

// 'Mabc123' 查找到字符串开头的 位置

'abc123'.replace(/$/g,'M')

//'abc123M' 查找到字符串结尾的 位置

当用 ^$ 包裹的正则会实现精准匹配

/abc/.test('ABCabc')

// ture 该正则会去 'ABCabc' 中去查找,只要存在 abc 满足条件即可,所以返回 true

/^abc$/.test('ABCabc')

// false 该正则指开始和结尾只包裹 abc 的字符串,即匹配的字符串必须是 abc。从而实现精准匹配
  • m

^$ 单独与 g 组合时,只会匹配一行数据,此时需通过 mg 配合实现多行匹配

'a\nb\nc\nb'.replace(/^\w+/g,'m')

// 'm\nb\nc\nb' 只有 a 被替换,其他换行数据并未匹配到

'a\nb\nc\nb'.replace(/\w+$/g,'m')

// 'a\nb\nc\nm' 只有最后一行 b 被替换

'a\nb\nc\nb'.replace(/^\w+/gm,'m')

// 'm\nm\nm\nm' 所有 m 都会被替换 /\w+$/gm 同理。

  • .

. 除了换行符、回车符、行分隔符和段分隔符除外,即 \n\r\u2028\u2029 以外的任意字符

'abc123_!@#\n\r'.replace(/./g,'.')

// 'MMMMMMMMMM\n\r'
  • []

[] 中可以放任意字符,含义为从 [] 中匹配一个字符

'abc123_!@#\n\r'.replace(/[abc123]/g,'M')

// 'MMMMMM_!@#\n\r'
  • -

配合 [] 使用,达到省略的目的

'059_abcz'.replace(/[0-9a-z]/g,'-')

// '---_----' 0-9 十个数字以及 a-z 二十六个字母都会被匹配到
  • \s \w \d
'\n a b 0 9 _'.replace(/\s/g,'s')

// 'ssasbs0s9s_'

'\n a b 0 9 _'.replace(/\w/g,'s')

// '\n s s s s s'

'\n a b 0 9 _'.replace(/\d/g,'s')

// '\n a b s s _'
  • \

如何替换字符串的 . ?

'1.2'.replace(/./g,'-')

// '---',在正则中 . 表示几乎任意字符,要想匹配实际含义的 . 需要进行转译

'1.2'.replace(/\./g,'-')

// '1-2'
  • ?

匹配个数 [0,1]

/^abc?$/.test('ab')

// true

/^abc?$/.test('abc')

// true,?前面的字符可要可不要

  • *

匹配个数 [0,+∞)

/^abc*$/.test('ab')

// true

/^abc*$/.test('abcccc')

// true

  • +

匹配个数 [1,+∞)

/^abc+$/.test('ab')

// false c 至少存在一个

/^abc+$/.test('abcccc')

// true

  • {m,n}

1、{m,},匹配个数 [m,+∞),至少 m 个

2、{m,n},匹配个数 [m,n] 区间任意个数

3、{m} 和 {m,m} 含义一致,匹配个数 m

/^abc{2,3}$/.test('abcc')

// true

/^abc{2,3}$/.test('abccc')

// true
  • \b

可以理解为匹配不连续 \w 的左右两边的 位置

'123 ace _ $%s^'.replace(/\b/g,'b')

// 'b123b baceb b_b $%bsb^',可以看到,只要字符 \w 左或右边不是 \w ,那么该位置就是 \b 位置

  • ?=p

匹配 p 之前的 位置,换句话说该位置后面必须满足是 p(p 指正则、字符串等)

'123abc'.replace(/(?=a)/g,'?')

// '123?abc',找到了 a 前面的位置 ?=a 是整体需用 () 包裹

?=p^ 前时,表示要匹配的字符串必须满足 p

/(?=[a-z])^[a-z0-9]*$/.test('123')

// false,(?=[a-z]) 表示字符串开头必须是一个字母,这样才能满足 (?=[a-z]) 在 ^ 之前;^[a-z0-9]*$ 表示字符串为数字字母。

/(?=[a-z])^123$/.test('a123')

// true

?!p?=p 的非,即除了 p 前面位置的其他位置

'123abc'.replace(/(?!a)/g,'?')

// '?1?2?3a?b?c?'
  • ?<=p

?=p 相反,?<=p 匹配的是 p 后面的 位置

'123abc'.replace(/(?<=a)/g,'?')

// '123a?bc',找到匹配字符 a 后面的位置

同理当 ?<=p$ 后时,表示要匹配的字符串必须满足 p

/^[a-z0-9]*$(?<=[a-z])/.test('123')

// false,(?<=[a-z]) 表示字符串结尾必须是一个字母,这样才能满足 (?<=[a-z]) 在 $ 之后;^[a-z0-9]*$ 表示字符串为数字字母。

/^[a-z0-9]*$(?<=[a-z])/.test('123a')

// true
  • ()

() 包裹叫捕获分组,可以通过 $n 进行获取

'2022-02-24'.replace(/(\d{4})-(\d{2})-(\d{2})/,'$3/$2/$1')

// '24/02/2022' ,每个 () 都会形成捕获组,可以通过 $n 来获取它们

当多层 () 时,按照从左到右的顺序进行组的划分

'abc-123-   '.replace(/((\w{3}-(\d{3}))-(\s*))/,(_,$1,$2,$3,$4)=>{console.log($1,"$",$2,"$",$3,"$",$4,"$")})

// abc-123-    $ abc-123 $ 123 $     $

上例可以看出

$1: 为最外层 ((\w{3}-(\d{3}))-(\s*) 匹配整个字符串 'abc-123- '

$2: 为 (\w{3}-(\d{3})) 匹配字符串 'abc-123'

$3: 为 (\w{3}-(\d{3})) 包裹的 (\d{3}) 匹配为 '123'

$4: 为 (\s*) 匹配剩余空格 ' '

由此可以看出分组的规则:从左到右递归匹配

  • \n

写一个支持 2016-06-12、2016/06/12、2016.06.12 三种格式的正则

/\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/.test('2016-06-12');

// true

/\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/.test('2016-06/12');

// true,可以看出上述正则存在问题可以存在前后连接的字符不一致的情况

/\d{4}(-|\/|\.)\d{2}\1\d{2}/.test('2016-06/12');

// false

/\d{4}(-|\/|\.)\d{2}\1\d{2}/.test('2016-06-12');

// true

由案例可以看出 \n 反向引用(只能引用之前出现的分组),匹配的是与之对应分组匹配的结果。

  • (?:)
'2022-02-04'.replace(/(?:\d{4})-(\d{2})-(\d{2})/,(_,$1,$2)=>{console.log($1,$2)})

// 02 04,由此可以看出 (?:) 的效果

正则可视化工具

regexper,图形化的方式解析正则,让你更清晰的洞悉正则要表达的含义。

正则方法

方法含义
match找到符合正则的所有数据,以数组的形式返回
replace找到符合正则的数据并替换
search查询正则所寻找的字符位置
test对字符串进行校验,以 boolean 值返回结果
  1. match
'ab'.match(/a/) => ['a', index: 0, input: 'ab', groups: undefined]

含义:

groups: 一个捕获组数组 或 undefined(如果没有定义命名捕获组)

index: 匹配的结果的开始位置

input: 搜索的字符串

  1. replace
  • 第一个参数非正则:匹配到第一个字符串内容,用第二个进行替换(非贪婪匹配)
'aba'.replace('a',1) => '1ba'
  • 第一个参数为正则,第二个参数为非 $ 字符: 将符合正则的数据进行替换
'aba'.replace(/a/g,1) => '1b1'
  • 第一个参数为正则,第二个参数为 $ 字符: 将符合正则的数据用 $ 规则进行替换
'1a2b'.replace(/([0-9])([a-z])/g,'$1.') => '1.2.'

'1a2b'.replace(/([0-9])([a-z])/g,'$&.') => '1a.2b.'

'start 1a end'.replace(/([0-9])([a-z])/,"-$`")

 => 'start -start  end'

'start 1a end'.replace(/([0-9])([a-z])/,"-$'")
 => 'start - end end'

'1a2b'.replace(/([0-9])([a-z])/g,"$$.") => '$.$.'

$ 含义:

$i(i 取值范围 1~99):表示从左到右正则子表达式所匹配的文本

$&:表示与正则表达式匹配的全部文本

$`:表示匹配字符串的左边的所有文本

$':表示匹配字符串的右边的所有文本

$$:表示 $ 转译

  • 第一个参数为正则,第二个为函数:
var a = '1a2b'.replace(/([0-9])([a-z])/g,(match,$1,$2,index,s)=>{
  console.log(match,$1,$2,index,str);
  return $1
})

// 1a 1 a 0 1a2b
// 2b 2 b 2 1a2b
// a = 12
  1. search
'hello World'.search(/[A-Z]/g)

// 6
  1. test
/hello/.test('hello World')

// true

贪婪匹配和非贪婪匹配(惰性匹配)

  • 贪婪匹配,匹配时如果遇到 +,?,*,{n},{n,},{n,m} 标识符,代表是贪婪匹配,会尽可能多的去匹配内容
var str='aacbacbc';
var reg=/a.*b/;
var res=str.match(reg);
  • 非贪婪模式,正则表达式去匹配时,会尽量少的匹配符合条件的内容,一旦发现匹配符合要求,立马就匹配成功,而不会继续匹配下去。(除非有 g,会开启下一组匹配,全局匹配)。或者在贪婪模式的标识符后面加上一个 ?
var str='aacbacbc';
var reg=/a.*?b/;
var res=str.match(reg);

原理:贪婪是先吃进,回溯再让出,非贪婪是先忽略,回溯再吃进

练习

  1. <div id="container" class="main"></div> 中提取出 id="container"。
  2. 数字的千位分隔符表示法,例:把"12345678",变成"12,345,678"。
  3. 密码长度 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符。

答案

  1. /id="[^"]*"/
  2. /(?!^)(?=(\d{3})+$)/g;
  3. /((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A-Z]))^[0-9A-Za-z]{6,12}$/ 或者 /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/

Q & A

如果要匹配任意字符?

使用 [\d\D][\w\W][\s\S][^] 中任何的一个。

输入只能是汉字?

使用 [\u4e00-\u9fa5] 匹配

参考资料