正则表达式
正则表达式是在处理字符串时,非常便利的工具
而正则表达式本身是由一些特殊的字符组成特定的规则, 去匹配字符串, 可以做到 验证
, 替换
, 获取
的功能, 对应一些复杂的字符串处理更是得心应手
菜单
参考文档
正则表达式: https://www.baidufe.com/item/eb10deb92f2c05ca32cf.html
replace 解析: https://www.cnblogs.com/lvmylife/p/9079297.html
分组: https://blog.csdn.net/zcdyx88/article/details/54016551
这些也是我在学习过程中,自己总结的内容, 如果有缺失或错误, 欢迎指出
使用方式
最开始先熟悉 使用方式, 这样在后续学习中就可以 边做边学了
正则表达式在 js
环境中, 是有以下几种 声明方式
和 使用方式
的
//声明方式
1. let reg = /正则表达式/修饰符; // /[a-z]/g
2. let reg = new RegExp("正则表达式","修饰符"); // new RegExp("[a-z]","g")
3. let reg = new RegExp();
reg.compile("正则表达式","修饰符"); // 重新定义正则表达式
//使用方式
1. reg.test("字符串"); // 返回 true,false
2. reg.exec("字符串"); // 返回 匹配内容的信息
3. "字符串".match(reg); // 返回 匹配字符的数组 或 内容的信息
4. "字符串".matchAll(reg); // reg必须带修饰符g, 返回 匹配内容信息的数组
每个使用方式 的结果都不一样, 但最后的目的是一样的, 就是
验证
或获取
匹配字符串的信息
刚开始如果对这些概念不熟悉可以先记忆
额外说明
使用方式
的exec
和match
在没有修饰符的情况下, 作用是一样的
声明方式
的2
3
其实是没区别的, 要说区别的话, 可能第3
种在循环遍历 不同的表达式 的情况下会快一些, 因为不需要重新声明new RegExp
, 就可以改变正则表达式
的内容
声明方式
时修饰符
是可以省略的, 但matchAll
必须带 g
替换
替换的 使用方式和 上面大同小异, 用的是 replace()
例子
//格式
"字符串".replace(正则表达式 / RegExp, "替换文本");
//例子
"abc".replace(/[a-z]/g, "b"); // "bbb"
"abc".replace(new RegExp("[a-z]", "g"), "b"); //bbb
替换
空字符
的话就可以实现删除的效果了, 替换常用的还有 正则的分组
功能, 熟悉一下即可
如果对该方法不熟悉或不懂, 可以浅尝即止(看看例子知道有什么作用就行), 然后去看 组成部分 的内容
分组替换
在替换字符串的时候, 有时会有这种需求.
比如: 只是想在 指定字符串 的附近加点东西,不想改变 原有的内容, 但用上面的 替换 就会导致原内容被替换 而无法实现需求
这时分组替换就 有用了
//例子
"abc".replace(/([a-z])/g, "_$1"); //_a_b_c
"abc".replace(/([a-z])([a-z])/g, "_$1*$2"); //_a*bc
"a_bc".replace(/\_([a-z])/g, (all, letter) => {
return letter.toUpperCase();
}); // aBc
"a_b_c".replace(/\_([a-z])\_([a-z])/g, (all, letter, letter2) => {
return letter.toUpperCase() + letter2.toUpperCase();
}); // aBC
其作用就是 使用
正则表达式
的分组, 来实现 单组匹配内容 的占位, 之后就可以使用占位符
来进行替换, 有多少分组,$数字
的占位符
数字就可以是多少, 而占位符
的地方 会嵌入原有的匹配内容 这样就实现了需求
而replace
的第二个参数 为函数, 其参数列表按顺序 分别是
- 匹配的内容
- 分组的匹配内容(无分组可省略, 有分组时则有几个是几个)
- 匹配字符串位置索引
- 原内容
额外说明
replace()
第二个参数 占位符 其实还有其他很多种,每种都有不同的作用
//$& 与正则相匹配的字符串
"abc".replace(/b/, "_$&_"); //a_b_c
//$` 匹配字符串左边的字符
"abc".replace(/b/, "_$`_"); // a_a_c
//$' 匹配字符串右边的字符
"abc".replace(/b/, "_$'_"); //a_c_c
这些替换是不需要分组就可以使用的, 和普通的替换一样, 只不过用法和分组
占位符
差不多
组成部分
正则表达式
的 组成是有规则的, 不同情况下 组合不同的规则写法, 就能实现各个功能
我会提供一些
例子
, 和对部分难理解的内容 进行额外说明
, 为了 结果直观明显, 例子我都会使用替换
的形式 进行操作
例子都可以直接复制到
F12
开发工具里面测试, 有疑问也可以进行调整
元字符
字符 | 对应说明 |
---|---|
[] | 匹配括号内部所有的单个字符, [0-9],[a-z],[A-Z],\ 转义字符 除外 |
. | 匹配除换行符之外的任意字符 |
\w | 匹配字母数字下划线,等同于:[a-zA-Z0-9_] |
\s | 匹配任意空白符 |
\d | 匹配数字,等同于[0-9] |
\b | 匹配单词边界 |
| | 或匹配,如 /x|y/ 正则可匹配 x 或 y 两个字符 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
例子
//[]
"1abcABC".replace(/[a-z]/g, "x"); //1xxxABC
"1abcABC".replace(/[0-9]/g, "x"); //xabcABC
"1abcABC".replace(/[A-Z]/g, "x"); //1abcxxx
"1abcABC".replace(/[0-9a-zA-Z]/g, "x"); //xxxxxxx
"1abcABC".replace(/[a-b]/g, "x"); //1xxcABC
"1.abcABC".replace(/[.]/g, "x"); //1xabcABC
"1\nabc".replace(/[\x0a]/g, "x"); //1xabc
// . (换行符是指\n)
"a\nbc".replace(/./g, "b"); //b\nbb
// \w
"abc123_".replace(/\w/g, "b"); //bbbbbbb
// \s
" abc ".replace(/\s/g, "b"); //babcb
// \d
"1abc2".replace(/\d/g, "b"); //babcb
// \b
"abc abc".replace(/\b/g, "b"); //babcb babcb
// |
"abcd".replace(/a|c/g, "b"); //bbbd
// ^
"abcd".replace(/^/g, "b"); //babcd
// $
"abcd".replace(/$/g, "b"); //abcdb 下面接着用
"abcdb".replace(/$/g, "b"); //abcdbb
通过上面的例子, 有人就会发现 有些规则并不只是匹配具体内容, 还会去匹配
位置
, 比如\b
,^
,$
, 而当你将其替换时, 就会在指定位置添加, 但不代表这个位置之后就匹配不了了, 像这种位置的替换
是可以重复使用的
注意
[]
内部所有的字符都会当做匹配内容处理,转义字符
(\
)除外 , 所以在括号内部无法使用规则, 而[0-9],[a-z],[A-Z]
这三个是 约定好的规则, 所以 其中的三个字符 不会被当做 匹配内容处理, 而且这三个规则可以同时使用[0-9a-zA-Z]
, 且范围也可以更改[1-3b-dB-D]
反义字符
字符 | 对应说明 |
---|---|
[^x] | 匹配除“x”之外的所有字符,其中“x”可以为任意字符 |
[^xyz] | 同上,匹配除“x、y、z”之外的任意字符 |
\W | 匹配除了字母、数字、下划线之外的所有字符,等同于:[^\w] |
\S | 匹配除空白符之外的任意字符,等同于:[^\s] |
\B | 匹配不是单词边界的字符,等同于:[^\b] |
\D | 匹配不是数字的所有字符,等同于:[^\d] |
反义字符 从字面意思 就是取反, 简括就是 除了这个,其他的都可以
反义字符 其实是有固定格式的
[^内容]
, 而元字符
的\小写字母
规则, 也可以通过\大写字母
来实现反义, 这样是不是容易记忆一点
额外说明
\大写字母
仅限于元字符
的反义,转义字符
可不是, 因为转义字符
本质上只是 特殊字符的 简写, 不算 匹配规则 比如 你在F12
输入 字符串 “\x0c”, 就会得到 “\f”
如果要使用
转义字符
的反义, 应该使用[^x]
, 比如[^\f]
或[^\x0c]
转义字符
字符 | 对应说明 |
---|---|
\f | 匹配换页符,等同于:\x0c |
\n | 匹配换行符,等同于:\x0a |
\r | 匹配回车符,等同于:\x0d |
\t | 匹配水平制表符,等同于:\x09 |
\v | 匹配垂直制表符,等同于:\x0b |
\unnnn | 匹配 Unicode 字符,如:\u00A0 |
\xnn | 匹配十六进制数 如: \x48 |
其中
n
代表任意数字字母, 写在这里的意思是\u
的后 4 个任意数字字母
都会匹配到一起,\x
同理
至于
Unicode 字符
,十六进制数
的意思就不在这里过多 赘述了, 有兴趣的可以查阅相关 文档
// \unnnn
"\u00A0".replace(/\u00A0/, "a"); // a
"\u00A0".replace(/\u00A01*/, "a"); // a
// \xnn
"\x48".replace(/\x48/, "a"); // a
"\x48".replace(/\x481*/, "a"); // a
一般来说 转义字符 用的情况会比较少, 只需记住即可, 记不住的在用到时也可以回来查阅
重复匹配
字符 | 对应说明 |
---|---|
* | 重复出现零次或多次 |
+ | 重复出现一次或多次 |
? | 重复出现零次或一次 |
{n} | 重复出现 n 次 |
{n,} | 至少重复出现 n 次 |
{n,m} | 重复重现 n 到 m 次,其中,n<m |
例子
// *
"abc".replace(/[a-z]*/g, "b"); //bb
// +
"abc".replace(/[a-z]+/g, "b"); //b
// ?
"abc".replace(/[a-z]?/g, "b"); //bbbb
// {n}
"abc".replace(/[a-z]{1}/g, "b"); //bbb
// {n,}
"abc".replace(/[a-z]{1,}/g, "b"); //b
// {n, m}
"abc".replace(/[a-z]{1,2}/g, "b"); //bb
重复匹配是很重要的 规则, 基本上复杂一点的 正则都会使用, 需要掌握熟练
贪婪与惰性
其实在使用 重复匹配
时, 会发现 匹配数量是有范围的, 且数量偏向于 大值 比如 {1,2}
优先 匹配 2 个的字符, 而不是 1 个, 而让匹配数量偏向于 小值 就要用到 贪婪与惰性
在 重复匹配
的规则后 加个 ?
就是 贪婪与惰性
了
"abc".replace(/[a-z]+?/g, "b_"); // b_b_b_
"abc".replace(/[a-z]{1,2}?/g, "b_"); // b_b_b_
//小值为0时
"abc".replace(/[a-z]{0,2}?/g, "b_"); // b_ab_bb_cb_
"123".replace(/[a-z]{0,2}?/g, "b_"); // b_1b_2b_3b_
当小值为 0 时, 就会去匹配字符的
间隙
位置, 而不会去匹配 规则内容 (因为匹配以 0 长度为准), 这也是一个特殊的使用方式,当你想在每一个字符之间插入某些内容时就 可以这么做
分组/捕获
字符 | 对应说明 |
---|---|
(exp) | 捕获分组, 匹配 exp |
(?<name>exp) | 捕获分组, 匹配 exp, 并将该匹配内容放到 name 的组里 |
(?:exp) | 不捕获分组, 匹配 exp 正则 |
exp1(?=exp2) | 不捕获分组, 匹配 exp1,但后面必须是 exp2 |
exp1(?!exp2) | 不捕获分组, 匹配 exp1,但后面不能是 exp2 |
(?<=exp2)exp1 | 不捕获分组, 匹配 exp1,但前面必须是 exp2 |
(?<!exp2)exp1 | 不捕获分组, 匹配 exp1,但前面不能是 exp2 |
分组是用于 分别处理多个匹配内容的最佳手段, 通常和 replace
进行搭配使用, 来实现一次匹配,不同修改
被分组的内容 会被当成一个整体, 整体受到其他规则的影响
不捕获分组 简单来讲是指不会产生
分组占位符
, 也就是$1,$2,$3...
或者$<name>
例子
//分组使用
"abc".replace(/([a-z])([a-z])/g, "_$1"); //_ac
"abc".replace(/([a-z])([a-z])/g, "_$1*$2"); //_a*bc
//(?:exp)
"abc".replace(/(?:[a-z])([a-z])/g, "_$1"); //_bc
//(?<name>exp)
"abc".replace(/(?<test>a)/g, "_$<test>"); //_abc
"abc".replace(/(?<test>a)/g, "_$1"); //_abc
//分组影响
"abbbc".replace(/ab{1,2}/g, "_c"); //_cbc
"abbbc".replace(/(?:ab){1,2}/g, "_c"); //_cbbc
"abbbc".replace(/(ab){1,2}/g, "_c"); //_cbbc
//断言
"1abc".replace(/1(?=a)/g, "_x"); //_xabc
"1abc".replace(/1(?=c)/g, "_x"); //1abc
"1abc".replace(/1(?!c)/g, "_x"); //_xabc
"1abc".replace(/1(?!a)/g, "_x"); //1abc
"1abc".replace(/(?<=a)b/g, "_x"); //1a_xc
"1abc".replace(/(?<=1)b/g, "_x"); //1abc
"1abc".replace(/(?<!1)b/g, "_x"); //1a_xc
"1abc".replace(/(?<!a)b/g, "_x"); //1abc
额外说明
(?:exp)
使用方式其实和(exp)
是一样的, 只是一个捕获分组 一个不捕获. 通常只会和(exp)
一起使用, 调整占位符的索引位置
exp1(?=exp2)
和exp1(?!exp2)
作用和字面意思一样, 匹配符合规则的字符串. 但注意
的是 它们最后都只会匹配exp1
内容, 和exp2
无关,exp2
只作为一个规则使用, 不会参与到匹配的结果当中
捕获分组时, 系统会自动给每个分组按照匹配顺序依次取名 就是
$1,$2...
, 哪怕你 使用了自定义取名$<name>
,$1,$2...
也是可以使用的
如果你错过了 分组替换, 现在不妨去看看, 你会有新的收获
修饰符
字符 | 对应说明 |
---|---|
i | ignoreCase 的缩写,表示忽略字母的大小写 |
m | multiline 的缩写,更改^和$的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,$的精确含意是:匹配\n 之前的位置以及字符串结束前的位置.) |
g | global 的缩写,进行全局匹配,即对字符串进行全文匹配,直到字符串遍历结束 |
例子
"abc".replace(/[A-Z]{2}/i, "x"); //xc
"ab\nabc\nabcd".replace(/^a/m, "x"); //xb\nabc\nabcd
"ab\nabc\nabcd".replace(/^a/gm, "x"); //xb\nxbc\nxbcd
"ab\nabc\nabcd".replace(/c$/gm, "x"); //ab\nabx\nabcd
// \n 的另一种表达形式
`ab
abc
abcd`.replace(/^a/gm, "x"); //xb\nxbc\nxbcd
"ab\nabc".replace(/^A/gim, "x"); //xb\nxbc
修饰符
可以说是 最常用的 字符了, 简单好用, 细心的朋友应该也看到了, 不同的修饰符
是可以同时使用的, 且不受顺序影响mg
,gm
,img
,gim
都可以
结语
正则表达式到这里就结束了, 剩下的就是自由发挥, 根据情况来组合不同规则实现复杂的字符串处理, 像现在的 vscode
idea
webStore
… 等等工具, 都有 正则替换内容的功能, 能掌握正则表达式 会使开发更加便利,轻松
如果忘记什么, 或者想推荐别人使用正则表达式都可以查看本文档, 我会查缺补漏,尽量使例子覆盖完整