JS-正则表达式
前言
距离上一次更新已经是五个月以前了,时光荏苒,已经到2024年了。由于自己会的是Python,所以不太清楚自己能否找到合适的工作,思来想去还是准备学习一下前端的知识,希望能在当前艰难的大环境下生存下来。
正则表达式其实就是字符串的一个匹配规则,能方便匹配一系列符合规则的字符串,让开发效率变高,这次也是趁着学完了来做个笔记记录一下,毕竟如果后续不常用还是容易忘记。以下部分均以Javascript作为示范语言,Python语言实际也大差不差。
以下是一个匹配24小时时间的例子:
1 | const reg4 = /^(([01][0-9])|([2][0123])):[0-5][0-9]$/; |
其中/^(([01][0-9])|([2][0123])):[0-5][0-9]$/
即为一个正则表达式。由//
包裹,在第二个/之后可添加修饰符来约束正则中执行的某些细节,如//g
,表示全局匹配。
常见的字符串匹配函数
在学习正则表达式之前,我们需要了解js中一些常用的字符串匹配方法,这样才能方便后续学习时的测试。
本段落使用的测试字符串为:"前端开发,必须掌握正则表达式,前端就业前景好,前端工资高"
,固定的正则表达式为:/前端/g
,即在字符串中匹配前端这两个字符,g的含义为全局匹配,这里不必深究。
test
正则表达式的一个方法(并非字符串的方法,后续学习记得区分一下)。用来检测字符串是否符合正则表达式的规则,如果符合返回true,否则返回false。
1 | const reg = /前端/g; |
这边有一个注意点。
test 方法在被连续调用时会从上一次匹配的下一个位置开始查找,而不是每次都从第一个位置开始匹配,如果不注意可能会出现反常的结果,如下的一个例子:
1 | reg.test('前端开发,用js') |
这是因为test方法在被连续调用时会从上一次匹配的下一个位置开始查找,当reg.test('前端开发,用js')
执行后,第二次调用 test 方法时将从字符串的第3个字符开始匹配,而不是从第 0 个字符开始,因此出现了反常的错误。
exec
是正则表达式的一个方法,作用是查找符合规则的字符串。
1 | const reg = /前端/g; |
输出为:[ '前端', index: 0, input: '前端开发', groups: undefined ]
返回值为数组,如果匹配失败则返回NULL
,匹配成功的返回值有三个参数:
- 第一项为匹配到的字符串 ‘前端’
- 第二项为匹配到的字符串的索引 index:0
- 第三项为原字符串 input: ‘前端开发’
需要注意的是exec方法也会从上一次匹配的下一个位置开始查找,这点和text一样。
replace
是字符串的方法(不是正则表达式的方法),替换字符串中符合规则的字符串。
1 | const reg = /前端/g; |
输出为后端开发,必须掌握正则表达式,后端就业前景好,后端工资高
match
是字符串的方法,查找符合规则的字符串
1 | const reg = /前端/g; |
输出为:[ '前端', '前端', '前端' ]
匹配成功返回一个数组,匹配失败返回None。
更多关注返回值是否为None,与test的区别在于方法调用对象不同,且match方法明显比test安全,也更符合一般代码逻辑。
修饰符
放在第二个/之后,作用是约束正则中执行的某些细节,常见的有三个取值。
sign | function |
---|---|
i | 执行对大小写不敏感的匹配 |
g | 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止) |
m | 执行多行匹配(应用场景较少) |
大小写脱敏
1
2
3
4
5const reg1 = /a/i;
const reg2 = /a/;
const str = "A";
console.log(reg1.test(str)); // true
console.log(reg2.test(str)); // false全局匹配
1
2
3
4
5const reg1 = /前端/;
const reg2 = /前端/g;
const str = "前端开发,必须掌握正则表达式,前端就业前景好,前端工资高";
console.log(str.match(reg1));
console.log(str.match(reg2));- reg1:
[ '前端', index: 0, input: '前端开发,必须掌握正则表达式,前端就业前景好,前端工资高', groups: undefined ]
- reg2:
[ '前端', '前端', '前端' ]
- reg1:
使用多个修饰符
1
2
3const reg3 = /Java/gi;
const message = "学Java,java工资高,java前景好";
console.log(message.replace(reg3, "Python"));输出为:
学Python,Python工资高,Python前景好
元字符
即具有特殊含义的字符,给与正则表达式更加多样灵活的组合。
边界符
即为规定表达式的边界部分,常见有单词边界、字符串边界、精确匹配三种情况。
单词边界
在字符串头尾加上
\b
构成单词的部分,如下面这个例子:1
2
3
4
5
6
7// 加上\b 构成单词的部分
const reg1 = /cat/g;
const reg2 = /\bcat\b/g;
const message = "The cat scattered his food all over the room.";
console.log(message.replace(reg1, "dog"));
console.log(message.replace(reg2, "dog"));- reg1:
The dog sdogtered his food all over the room.
- reg2:
The dog scattered his food all over the room.
可以看到由于reg1并未添加单词边界,导致scattered中的“cat”部分也会被匹配上,出现了大相径庭的结果。
- reg1:
字符串边界
使用
^
作为字符串的开头,$
作为字符串的结尾。1
2
3
4
5
6
7
8
9const reg5 = /^a/i;
console.log(reg5.test("a")); // true
console.log(reg5.test("abc")); // true
console.log(reg5.test("bcd")); // false
const reg6 = /c$/i;
console.log(reg6.test("c")); // true
console.log(reg6.test("cb")); // false
console.log(reg6.test("C")); // true精确匹配
如果同时在开头与结尾加上
^ $
在一块,那么就编程了精确匹配,即字符串必须完全满足正则的全部要求。1
2
3const reg7 = /^a$/;
console.log(reg7.test("a")); // true
console.log(reg7.test("aaa")); // false
量词
顾名思义,即规定字符出现的频率。
sign | function |
---|---|
* | 表示0次或者多次 |
+ | 表示一次或者多次 |
? | 表示零次与一次 |
{n} | 只能有n次 |
{n,m} | 表示从n到m次(包括n与m) |
1
2
3const reg8 = /^a*$/i
console.log(reg8.test("aaa")); // true
console.log(reg8.test("")); // true1
2
3const reg8 = /^a+$/i
console.log(reg8.test("aaa")); // true
console.log(reg8.test("")); // false?
1
2
3const reg_1 = /^a?$/i
console.log(reg_1.test("")); //true
console.log(reg_1.test("aaa")); //false{n}
1
2
3
4const reg9 = /^a{3}$/
console.log(reg9.test("a")); //false
console.log(reg9.test("aaa")); // true
console.log(reg9.test("")); // false{n,m}
1
2
3
4const reg9 = /^a{1,3}$/
console.log(reg9.test("a")); //true
console.log(reg9.test("aaa")); // true
console.log(reg9.test("")); // false
字符类
日常生活中经常出现一些字符串不同但是等效的情况,如Python
与python
,color
与colour
,广义来说即为多个字符都符合匹配要求,可以出现替换的情况。
如:/[abc]/
,意味着我要匹配的字符串其中有a或b或c;/[Pp]thon/
为匹配Python
与python
。
常见的字符类情况为:
sign | function |
---|---|
[a-z] | 表示a到z,26个小写的英文字母 |
[A-Z] | 表示A到Z,26个大写的英文字母 |
[0-9] | 表示0-9十个数字 |
. | 匹配除了换行符(\n与\r)外的任意一个字符 |
[^] | 表示取反,与[]合并使用,注意要写在[]里面 |
以下是一些例子:
密码匹配(6-16位字母、数字或者下划线组成)
1
2
3
4const reg = /[a-zA-Z0-9_]{6,16}/;
console.log(reg.test("123456")); // true
console.log(reg.test("1234567")); // true
console.log(reg.test("12345")); // false替换Python为Java
1
2
3
4
5const reg = /[Pp]ython/;
const str1 = "人生苦短,我学Python"
const str2 = "人生苦短,我学Python"
console.log(str1.replace(reg,"Java"));
console.log(str2.replace(reg,"Java"));输出都为:
人生苦短,我学Java
匹配爱后面没有你的数据
1
2
3
4const reg = /爱[^你]/;
console.log(reg.test("不爱你")); // false
console.log(reg.test("爱你一万年")); // false
console.log(reg.test("我爱我")); // true.匹配的例子
1
2
3
4const reg14 = /./;
console.log(reg14.test("")); //false
console.log(reg14.test("\n")) // false
console.log(reg14.test("\r")) // false
预定义
对于一些既定的字符类来说,还有简化的写法,这些简化的写法就是预定义。
sign | Same meaning | function |
---|---|---|
\d | [0-9] | 小写数字0-9 |
\D | [^0-9] | 除了数字的部分 |
\w | [a-zA-Z0-9_] | 匹配任意的字母、数字、下划线 |
\W | [^a-zA-Z0-9_] | 匹配任意非字母、数字、下划线 |
\s | [\t\r\n\v\f] | 匹配空格(包含换行、空格、制表符) |
\S | [^\t\r\n\v\f] | 匹配非空格 |
大写其实就是小写的取反效果。
分组与选择
分组
分组就是将数据的不同部分分开以适应不同的规则,使用()
,每一个小括号就是一个分组。
一个经典的案例为:将日期2020-10-10
格式转化为10/10/2020
的格式。
此时我们就需要将匹配成功的年、月、日部分单独拿出来使用,这时候就可以使用分组的方法,匹配代码如下:
1 | const reg = /^(\d{4})-(\d{2})-(\d{2})$/ |
输出为:[ '2020-10-10', '2020', '10', '10', index: 0, input: '2020-10-10', groups: undefined ]
可以看到实际上match按照正则表达式分组的想法(一个小括号一组)将匹配结果分为了三组(年、月、日),其中将整个日期作为一个group匹配起来,我们称其为group0。
2020-10-10 group0
2020 group1
10 group2
10 group3
在replace中支持使用$n
来获取其中的分组数据
1 | const reg = /^(\d{4})-(\d{2})-(\d{2})$/ |
输出为:2020年10月10日
分支
使用|
来表示或者,如:
1 | const reg17 = /^good|nice$/ |
与[\bnice\b\bgood\b]
、/[(nice)(good)]/
效果相同。
分支与可选[]效果实际相同,但可选一般用于两者之间,而选择一般用于多者之间
非分组匹配
在正则表达式中用于将多个表达式组合成一个单元,但是与普通的括号不同,它不会保存匹配的内容用于后续的引用。这意味着你可以使用非捕获组来应用量词(如 +
或 *
)到整个组合的模式上,而不会捕获该组的匹配结果。
我的理解是帮助你找出成对或组合的模式出现的次数,但不需要具体的捕获内容。
以下是一个捕获HTML标签的例子:
1 | const text = "This is a <b>bold<b> word and this is an <i>italic</i> word." |
输出为[ '<b>', '<b>', '<i>' ]
个人感觉这个东西没什么用
一些零碎知识
可选字符
有时,我们可能想要匹配一个单词的不同写法,比如color和colour,或者honor与honour
1
2
3const reg19 = /colou?r/
console.log(reg19.test("color")) // true
console.log(reg19.test("colour")) // true这个正则表达式中?代表u可有可无。
重复区间
算是一些细节的补充,因为正则表达式默认是贪婪模式,即尽可能的匹配更多字符,于是会出现以下情况:
{M,} - 表示至少M次
{,N} - 表示至多N次
{M} - 表示恰好M次
而要使用非贪婪模式,我们要在表达式后面加上
?
号。
案例
密码匹配
密码匹配(6-16位字母、数字或者下划线组成)
1 | const reg = /[a-zA-Z0-9_]{6,16}/; |
匹配16进制颜色值
如#ffffff
,#fff
。
可以看出数字的个数为6个或者3个,可以使用分支结构,另外,每个字符的取值范围在0-9与a-f(包括大写)之间,这可以使用选择[]。
1 | const reg3 = /^#([0-9a-fA-F]{3}|[0-9A-Fa-f]{6})$/; |
匹配24小时时间
这个分为小时与分钟两部分进行分析。
小时部分:
- 当第一个数字为0或1的时候,第二个数字为0-9
- 当第一个数字为2时,第二个数字为0-3
分钟部分:
- 第一个数字为0-5
- 第二个数字为0-9
1 | const reg4 = /^(([01][0-9])|([2][0123])):[0-5][0-9]$/; |
手机号脱敏
将11位手机号中间4位替换为*,例如13812341234
替换为*138****1234*
,这里就可以把头三个数与尾四个数各分为一组,后续就可以使用$
获取数据。
1 | const str = "13812341234"; |
匹配电话号码
假设电话号码可以有下列两种方式:
0XX-XXXXXXX,例如020-8810456;
0XXXXXXXXX,例如0208810456
020 代表区号,8810456是电话号码,区号第一个数字必须是0,电话号码的第一个数字必须大于等于1。
1 | const reg9 = /^0[0-9]{2}-?[1-9]\d{6}$/ |
总结
正则表达式功能强大,虽然在平时大部分的时候都可以直接靠搜索引擎解决需求(如:手机号,身份证等),VsCode中也有名为any-rule
的正则插件,几乎涵盖了开发中大部分的需求,但是如果遇到一些特殊情况要求重新编写一个正则表达匹配,那就需要自己动手实现了,比如匹配特定的网站链接是否合法。
最后找到两个非常好用的网站:
- 编程胶囊:编程胶囊-打造学习编程的最好系统 (codejiaonang.com)
- 正则可视化:Regexper