前言
本人参考了
前端胖头鱼
的文章,或者说搬运的大部分内容,但也自己做了部分补充和修改。
原文地址 - 作者 前端胖头鱼
原文地址 - 作者 老姚
请心理默念三遍
- 正则表达式是
匹配模式
,要么匹配字符
,要么匹配位置
- 正则表达式是
匹配模式
,要么匹配字符
,要么匹配位置
- 正则表达式是
匹配模式
,要么匹配字符
,要么匹配位置
位置匹配
此部分内容都是在
确定或者寻找位置
,常用位置^
、$
、\b
、\B
、?=p
、(?!p)
、(?<=p)
、(?<!p)
脱字符 ^
,匹配行的开头
在 hello 的开头塞一个 😍
const str = "hello";
str.replace(/^/, "😍");
// 输出: 😍hello
匹配 he
开头的字符串
const re = /^he/;
re.test("he"); // 输出: true
re.test("hello"); // 输出: true
re.test(" he"); // 输出: false
美元符 $
,匹配行的结尾
在 hello 的结尾塞一个 😍
const str = "hello";
str.replace(/$/, "😍");
// 输出: hello😍
匹配 lo
结尾的字符串
const re = /lo$/;
re.test("lo"); // 输出: true
re.test("hello"); // 输出: true
re.test("lo "); // 输出: false
单词边界 \b ,匹配单词边界
匹配规则
- \w 和\W 之间的位置
- ^与\w 之间的位置
- \w 与 $ 之间的位置
\w
匹配字母、数字、下划线。等价于[A-Za-z0-9_]
\W
匹配非单词字符。等价于[^A-Za-z0-9_]
"[xxx]_xxx.mp4".replace(/\b/g, "🆚");
// 输出: 🆚xxx_xxx🆚.🆚mp4🆚
- 第一个插入为 ^与\w 之间的位置
- 第二个与第三个插入为 \w 和\W 之间的位置
- 第四个插入为 \w 与 $ 之间的位置
非单词边界 \B
匹配规则
\w
与\w
之间的位置\W
与\W
之间的位置^
与\W
之间的位置\W
与$
之间的位置
"@!xxx_xxx.mp4!@".replace(/\B/g, "🆚");
// 输出: 🆚@🆚!x🆚x🆚x🆚_🆚x🆚x🆚x.m🆚p🆚4!🆚@🆚
正向肯定断言 (?=p)
匹配 p
前面
的位置
// 在 abc 与 123 中间加下划线 '_'
"abc123".replace(/(?=123)/g, "_");
// 输出: abc_123
// -------------------------------
// 在 === 号前加 '!'
"123==='123'".replace(/(?=\={3})+/g, "!");
// 输出: 123!==='123'
正向否定断言 (?!p)
匹配
(?=p)
匹配到的位置之外的位置都是属于(?!p)
的
// 在除字符串`开头`之外的位置加下划线
"abc123".replace(/(?!^)/g, "_");
// 输出: a_b_c_1_2_3_
// 在除字符串`结尾`之外的位置加下划线
"abc123".replace(/(?!$)/g, "_");
// 输出: _a_b_c_1_2_3
// 在除字符串`开头` 与 `结尾` 之外的位置加下划线
"abc123".replace(/(?!^)(?!$)/g, "_");
// 输出: a_b_c_1_2_3
(反向|负向)肯定断言 (?<=p)
匹配 p
后面
的位置
// 在 abc 与 123 中间加下划线 '_'
"abc123".replace(/(?<=abc)/g, "_");
// 输出: abc_123
(反向|负向)否定断言 (?<=p)
匹配
(?<!p)
匹配到的位置之外的位置都是属于(?<!p)
的
// 在除abc与123之外的位置加下划线
"abc123".replace(/(?<!abc)/g, "_");
// 输出: _a_b_c1_2_3_
题目练习
题目 1:数字的千分位分割法
将 123456789 转化为 123,456,789
// 匹配字符串结尾三个数字前的位置
str.replace(/(?=\d{3}$)/g, ",");
// 输出: 123456,789
// 已三个数字为一组,每组之间用逗号隔开
str.replace(/(?=(\d{3})+$)/g, ",");
// 输出: ,123,456,789
// 去除开始的逗号
str.replace(/(?=(\d{3})+$)(?!^)/g, ",");
// 输出: 123,456,789
题目 2:手机号 3-4-4 分割
将手机号 18379836654 转化为 183-7983-6654
str.replace(/(?=(\d{4})+$)/g, "-");
// 输出: 183-7983-6654
题目 3:验证密码的合法性
密码长度是 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符
// 1. 密码由数字、小写字母和大写字母组成,并且长度在 6-12 位之间
const re = /^[a-zA-Z\d]{6,12}$/;
re.test("123456"); // true
re.test("abcdefg"); // true
re.test("ABCDEFG"); // true
re.test("123"); // false
re.test("1234567890123"); // false
// 2. 数字出现的位置紧接着是小写字母;
const re = /(?=.*\d[a-zA-z])/;
re.test("1a"); // true
re.test("1A"); // true
re.test("11"); // false
// 3.小写字母出现的位置紧接着是大写字母或数字;
const re = /(?=.*[a-z][A-Z\d])/;
re.test("a1"); // true
re.test("aA"); // true
re.test("aa"); // false
// 4. 大写字母出现的位置紧接着是小写字母或数字;
const re = /(?=.*[A-Z][a-z\d])/;
re.test("A1"); // true
re.test("Aa"); // true
re.test("AA"); // false
// 5. 将1-4的正则表达式组合成一个大的正则表达式,并且验证密码的合法性
const re =
/((?=.*\d[a-zA-z])|(?=.*[a-z][A-Z\d])|(?=.*[A-Z][a-z\d]))^[a-zA-Z\d]{6,12}$/;
re.test("1aA"); // false 不满足条件1
re.test("123456"); // false 不满足条件2
re.test("abcdef"); // false 不满足条件3
re.test("ABCDEF"); // false 不满足条件4
re.test("12345a"); // true
re.test("12345A"); // true
re.test("a12345"); // true
re.test("aA2345"); // true
re.test("A12345"); // true
re.test("Aa2345"); // true
字符串匹配
此部分内容都是在
寻找字符串
来匹配
横向查找
一个正则可匹配的字符串的长度不是固定的,可以是多种情况
常用量词
字符 | 描述 | 字符 | 描述 |
---|---|---|---|
\* | 匹配 0 个或多个字符 | ? | 匹配 0 个或 1 个字符 |
+ | 匹配 1 个或多个字符 | {m,n} | 匹配 m~n 个字符 |
\*
匹配 0 个或多个字符/^\d*$/.test(""); // true /^\d*$/.test("1"); // true
+
匹配 1 个或多个字符/^\d+$/.test(""); // false /^\d+$/.test("1"); // true
?
匹配 0 个或 1 个字符/^\d?$/.test(""); // true /^\d?$/.test("1"); // true
{m,n}
匹配 m~n 个字符// {n} 匹配 3个1 /^1{3}/.test("111"); // true // {m,n} 匹配 1~2 个字符 /^\d{1,2}$/.test(""); // false /^\d{1,2}$/.test("1"); // true /^\d{1,2}$/.test("12"); // true /^\d{1,2}$/.test("123"); // false // {m,} 匹配 1到多个字符 等同于 `/^\d+$/` /^\d{1,}$/.test("1"); // true
纵向查找
一个正则匹配的字符串,具体到某一位字符时,可以不是某个确定的字符串,可以有多种可能,实现方式是字符组( 其实多选分支|也可以实现 )
字符组 []
-
范围表示法
[123456abcdefABCDEF] => [1-6a-fA-F]
// 匹配 a,b,c,d 中的任意一个 /^[abcd]$/.test("a") // true // 匹配 a,b,c,d 中的任意一个 /^[a-d]$/.test("a") // true // 匹配 0 - 9 中的任意一个 /^[0-9]$/.test("1") // true
-
排除字符组
// 不匹配 a,b,c,d 中的任意一个 /^[^e]$/.test("a"); // false
贪婪匹配
正则本身是贪婪的,会尽可能的多匹配符合模式的字符
"1234".replace(/\d+/, "*"); // "****"
非贪婪匹配
在量词后面加上
?
可以实现非贪婪匹配
"1234".replace(/\d+?/, "*"); // "*234"
多选分支
一个模式可以实现横向和纵向的模糊匹配,而多选分支可以支持多个子模式任选其一,形式是(p1|p2|p3)
let regex = /good|nice/g;
let string = "good idea, nice try.";
string.match(regex); // [ 'good', 'nice' ]
注意,用
/good|goodbye/
去匹配goodbye
匹配到的是good
因为分支结构是惰性的,前面的匹配上了,后面的就不再尝试了
案例分析
-
匹配 id
// 贪婪模式 '<div id="container" class="main"></div>'.match(/id=".*"/gi); // 输出 [ 'id="container" class="main"' ] // 非贪婪模式 '<div id="container" class="main"></div>'.match(/id=".*?"/gi); // 输出 [ 'id="container"' ]
-
匹配 16 进制的颜色值
// 要求匹配如下颜色 /* * #ffbbad #Fc01DF #FFF #ffE */ let regex = /#([a-fA-F\d]{6}|[a-fA-F\d]{3})/g; let string = "#ffbbad #Fc01DF #FFF #ffE"; string.match(regex); // ["#ffbbad", "#Fc01DF", "#FFF", "#ffE"]
-
校验小数与整数
-
校验正整数与负整数
.字符串开头可能存在
+
或者-
,且-0
为非法,若是多个字符,则第一个字符必须是[1-9]
,若是一个字符必须是[0-9]
const re = /^[-+]?([1-9]\d{0,}|0)$/; re.test("1"); // true re.test("12"); // true re.test("+12"); // true re.test("-12"); // true re.test("+0"); // true re.test("-0"); // true re.test("01"); // false re.test("+01"); // false re.test("-01"); // false
-
增加小数校验
在整数正则后增加小数判断,
.
与数字必须同时存在且只能存在一个.
。.
与数字可能存在可能不存在const re = /^(-?[1-9]{1}\d{0,}|0)(\.\d+)?$/; re1.test("1.2"); // true re1.test("1.2.3.4"); // false
-
-
匹配 24 小时制时间
// 第一位:可以是0、1、2 // 第二位:当第一位位0或者1的时候,可以是0到9、第一位是2的时候,只可以是0到3 // 第三位:固定是冒号: // 第四位:可以是0到5 // 第五位:0到9 const re = /([0-1]\d|2[0-3]):[0-5]\d/; console.log(re.test("00:00")); // true console.log(re.test("10:00")); // true console.log(re.test("23:59")); // true console.log(re.test("24:00")); // false // 衍生题,可以小时首位为0可省略 const re = /(0?\d|[0-1]\d|2[0-3]):[0-5]\d/; console.log(re.test("7:00")); // true console.log(re.test("07:00")); // true
-
括号的作用
括号的作用是提供了分组(括号内的正则是一个整体,即提供子表达式),便于我们引用它
分组
让量词作用于一个整体
const reg = /(ab)+/g;
const string = "ababa abbb ababab";
string.match(reg); // ["abab", "ab", "ababab"]
分支结构
分支结构有点像编程里面或的概念||
/*
匹配
I love JavaScript
I love Regular Expression
*/
const reg = /I love (JavaScript|Regular Expression)/;
reg.test("I love JavaScript"); // true
reg.test("I love Regular Expression"); // true
分组的引用
将分组的内容引用到另一个分组中
2021-03-31
转换为 2021.03.31
-
第一种方法
const reg = /(\d{4})-(\d{2})-(\d{2})/; const string = "2021-03-31"; string.replace(reg, "$1.$2.$3"); // 2021.03.31
-
第二种方法
const reg = /(\d{4})-(\d{2})-(\d{2})/; const string = "2021-03-31"; string.replace(reg, () => { return RegExp.$1 + "." + RegExp.$2 + "." + RegExp.$3; }); // 2021.03.31
-
第三种方法
const reg = /(\d{4})-(\d{2})-(\d{2})/; const string = "2021-03-31"; string.replace(reg, ($0, $1, $2, $3) => { return $1 + "." + $2 + "." + $3; }); // 2021.03.31
反向引用
除了通过 js 引用分组的内容,也可以通过正则来引用分组内容
- 注意
- 引用不存在的分组会怎样?
- 即匹配的就是
\1
\2
本身
- 即匹配的就是
- 分组后面有量词会怎样
- 分组后面如果有量词,分组最终(注意是分组,不是说整体)捕获的数据是最后一次的匹配
/(\d)+ \1/.test('12345 1') // false /(\d)+ \1/.test('12345 5') // true
- 引用不存在的分组会怎样?
-
匹配
AAAA
// 匹配 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, 9999 // 1.获取匹配的第一个分组 // 2.将匹配到的第一个分组引用并且增加3次匹配 /(\d)\1{3}/.test("1111");
-
匹配
AABB
// 匹配 1122, 2233 // 1.匹配第一个分组,然后直接引用 // 2.第二个分组中不应该存在 分组1中的数字。 过滤掉 AAAA const re = /(\d)\1((?!\1)\d)\2/; re.test("1122"); // true re.test("1111"); // false
-
多格式时间匹配
// 2021-03-31 2021/03/31 2021.03.31 const re = /\d{4}([-\/-])\d{2}\1\d{2}/; re.test("2021-03-31"); // true re.test("2021/03/31"); // true re.test("2021.03.31"); // true re.test("2021.03-31"); // false
非捕获性括号
匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用
或
字符(|)
来组合一个模式的各个部分是很有用。
// 示例
const re1 = /industr(?:y|ies)/;
re1.test("industry"); // true
re1.test("industries"); // true
const re2 = /industry|industries/;
re2.test("industry"); // true
re2.test("industries"); // true