引言
在面试中,正则堪称“经典考题”。掌握正则,一把拿下面试。😎
初识正则
1.什么是正则🤔
💡简单来说,就是处理字符串的一种规则。💡
📌下面是使用正则去验证某个字符串的例子:
function compile(){
let reg = /1[0-9]/; // 0-9表示取值范围 []表示取一个
console.log(reg.test('12'));
}
compile();//true
2. 编写正则表达式
2.1 创建方式
字面量创建方式
📌字面量创建方式是使用两个斜杠 /
将正则表达式包裹起来,内部包含用于描述规则的元字符。
let reg1 = /aa/;
// 匹配包含 "aa" 的字符串
console.log(reg.test('baab')); //true
console.log(reg1.test('banana')); // false
console.log(reg1.test('apple')); // false
构造函数模式创建
📌通过 RegExp
构造函数创建正则表达式,将规则以字符串形式传递给它。
let reg2 = new RegExp('aa');
// 匹配包含 "aa" 的字符串
console.log(reg.test('baab')); //true
console.log(reg2.test('banana')); // false
console.log(reg2.test('apple')); // false
2.2 正则表达式的组成
2.2.1 元字符
正则表达式中的元字符是其核心组成部分,用于定义复杂的匹配规则。元字符分为以下几类:
量词元字符:设置匹配次数
元字符 | 含义 | 示例 |
---|---|---|
* | 零次或多次 | /a*/ 匹配 "a" , "aaa" , "" |
+ | 一次或多次 | /a+/ 匹配 "a" , "aaa" |
? | 零次或一次 | /a?/ 匹配 "a" , "" |
{n} | 恰好 n 次 | /a{2}/ 匹配 "aa" |
{n,} | 至少 n 次 | /a{2,}/ 匹配 "aa" , "aaa" |
{n,m} | 至少 n 次,至多 m 次 | /a{1,3}/ 匹配 "a" , "aa" , "aaa" |
特殊元字符:具有特殊意义的字符或组合
元字符 | 含义 | 示例 |
---|---|---|
\ | 转义字符,用于转义其他元字符 | /\*/ 匹配 "*" |
. | 任意单个字符(除换行符 \n ) | /./ 匹配 "a" , "1" , "$" |
^ | 匹配字符串的开头 | /^a/ 匹配 "apple" 的开头 "a" |
$ | 匹配字符串的结尾 | /a$/ 匹配 "banana" 的结尾 "a" |
\n | 换行符 | /<br>\n 表示匹配换行符后的内容 |
\d | 匹配数字(0-9 ) | /\d+/ 匹配 "123" |
\D | 匹配非数字 | /\D+/ 匹配 "abc" |
\w | 匹配字母、数字、下划线([a-zA-Z0-9_] ) | /\w+/ 匹配 "hello_123" |
\s | 匹配空白字符(空格、制表符、换页符等) | /\s+/ 匹配 " " |
\t | 匹配制表符(TAB 键,通常为四个空格) | /\t/ 匹配 TAB 键 |
\b | 匹配单词边界 | /\bword\b/ 匹配 "word" 不匹配 "sword" |
x|y | 匹配 x 或 y | |
[xyz] | 匹配方括号中任意一个字符 | /[aeiou]/ 匹配 "a" , "e" , "i" |
[^xy] | 匹配除括号中字符外的任意字符 | /[^aeiou]/ 匹配非元音字母 |
[a-z] | 匹配指定范围的任意字符 | /[a-z]/ 匹配 "a" , "b" , ..., "z" |
[^a-z] | 匹配非指定范围的任意字符 | /[^a-z]/ 匹配数字或特殊字符 |
() | 分组捕获 | /(ab)+/ 捕获 "abab" |
(?:) | 仅匹配不捕获 | /(?:ab)+/ 匹配 "abab" 不捕获子组 |
(?=) | 正向预查 | /(?=\d)/ 匹配数字前的位置 |
(?! ) | 反向预查 | /(?!a)/ 匹配非 "a" 开头的位置 |
普通元字符:直接匹配字符本身
📌普通字符如 a
, 1
, #
等会直接匹配其自身。例如:
let reg = /name/;
console.log(reg.test("myname")); // true
2.2.2 修饰符
正则表达式的修饰符用于改变其匹配行为。修饰符位于表达式的末尾,例如 /abc/g
。
修饰符 | 含义 | 示例 |
---|---|---|
i (ignoreCase) | 忽略大小写匹配 | /abc/i 匹配 "abc" , "ABC" |
m (multiline) | 多行匹配,使 ^ 和 $ 匹配每行的开头和结尾 | /^abc/m 匹配多行中的 "abc" |
g (global) | 全局匹配,匹配所有可能的项 | /a/g 匹配 "a" , "a" in "aaa" |
u (Unicode) | 处理 Unicode 字符(大于 \uFFFF 的字符) | /\u{20BB7}/u 匹配特定 Unicode 字符 |
y (sticky) | 粘连模式,从 lastIndex 开始进行匹配 | /abc/y 必须从当前位置匹配 "abc" |
s (dotAll) | 让 . 匹配所有字符,包括换行符 \n 和回车符 \r | /./s 匹配 "a" , "\n" , "\r" |
这么多你肯定记不住,不如收藏一下慢慢看😁
2.3 常用元字符详解✍🏻
2.3.1 ^
和 $
^
开头匹配
📌表示匹配字符串的起始位置,规则写在 ^
后面。
let reg = /^1[0-9]a/;
console.log(reg.test('12a')); // true
console.log(reg.test('12ab')); // true
console.log(reg.test('a12a')); // false
$
结尾匹配
📌表示匹配字符串的结尾位置,规则写在 $
前面。
let reg = /1[0-9]a$/;
console.log(reg.test('12a')); // true
console.log(reg.test('12ab')); // false
console.log(reg.test('a12a')); // true
- 没有
^
和$
📌匹配字符串中包含规则的任意部分即可。
let reg = /1[0-9]a/;
console.log(reg.test('12a')); // true
console.log(reg.test('12ab')); // true
console.log(reg.test('a12a')); // true
- 有
^
和$
📌匹配字符串整体必须符合规则。
let reg = /^1[0-9]a$/;
console.log(reg.test('12a')); // true
console.log(reg.test('12ab')); // false
console.log(reg.test('a12a')); // false
2.3.2 转义字符\
- 作用
\
用于将有特殊意义的字符变为普通字符,或者将普通字符变为有特殊意义的字符。
let reg = /\{.*\}/; // 匹配 "{...}" 的形式
console.log(reg.test("{123}")); // true
2.3.3 x|y
- 含义:匹配
x
或y
注意优先级问题,通常使用小括号()
来调整规则逻辑。
let reg1 = /^18|29$/; // 匹配以 "18" 开头,或以 "29" 结尾
console.log(reg1.test('18')); // true
console.log(reg1.test('129')); // true
let reg2 = /^(18|29)$/; // 只匹配 "18" 或 "29"
console.log(reg2.test('18')); // true
console.log(reg2.test('129')); // false
2.3.4 中括号 []
-
含义:匹配括号内任意一个字符
- 中括号中的字符「一般」代表其本身意义。
- 范围匹配:
[a-z]
表示从a
到z
的任意一个字符。
let reg = /^[18]$/; // 匹配 "1" 或 "8"
console.log(reg.test('1')); // true
console.log(reg.test('8')); // true
console.log(reg.test('18')); // false
let reg = /^[10-29]$/; // 匹配 "1"、"9" 或者 "0-2"
console.log(reg.test('2')); // true
console.log(reg.test('3')); // false
2.3.5 {n,m}
- 含义:匹配前面的规则 n 到 m 次
注意:不加^
和$
时,会在字符串中找到符合条件的部分进行匹配。
let reg = /\d{2,4}/; // 匹配连续的 2 到 4 位数字
console.log(reg.test('123')); // true
console.log(reg.test('12345')); // true (只匹配前 4 位)
let regStrict = /^\d{2,4}$/; // 整体必须是 2 到 4 位数字
console.log(regStrict.test('12345')); // false
console.log(regStrict.test('1234')); // true
2.3.6 分组作用
-
改变优先级
let reg = /^18|29$/; // 不加括号:匹配以 "18" 开头或以 "29" 结尾 let regGroup = /^(18|29)$/; // 加括号:只匹配 "18" 或 "29"
-
分组捕获
捕获每一个分组的匹配内容。let reg = /^(\d{4})-(\d{2})-(\d{2})$/; let match = reg.exec('2025-01-08'); console.log(match[1]); // "2025" (分组 1) console.log(match[2]); // "01" (分组 2)
-
分组引用
在规则中重复引用之前捕获的分组内容。let reg = /^([a-z])\1$/; // 匹配两个相同的小写字母 console.log(reg.test('aa')); // true console.log(reg.test('ab')); // false
2.3.7 问号 ?
的五大作用
-
量词元字符:出现零次或一次
let reg = /a?b/; // "a" 可有可无 console.log(reg.test('b')); // true console.log(reg.test('ab')); // true
-
取消贪婪匹配
let reg = /a.+?b/; // 匹配最少字符 console.log("a123b456b".match(reg)); // ["a123b"]
-
只匹配不捕获 (?:)
let reg = /(?:ab)c/; // 匹配 "abc" 但不捕获 "ab"
-
正向预查 (?=)
let reg = /\d(?=px)/; // 匹配数字后面跟着 "px" 的部分 console.log(reg.test('100px')); // true
-
负向预查 (?!)
let reg = /\d(?!px)/; // 匹配数字后面不是 "px" 的部分 console.log(reg.test('100px')); // false console.log(reg.test('100em')); // true
2.4 常用的正则表达式📖
2.4.1 验证手机号
规则:
- 11 位
1
固定第一位数字[0-9]{10}
:表示匹配 10 个连续的0-9之间的数字,即手机号码的后 10 位。
let reg = /^1[0-9]{10}$/;
console.log(reg.test('1387018399'));//false 10位
console.log(reg.test('13870183991'));//true 11位
console.log(reg.test('138701839910'));//false 12位
2.4.2 验证密码
规则:
- 长度为 6-16 位。
- 必须由数字和字母组成。
正则表达式:
let reg = /^(?=.*[a-zA-Z])(?=.*\d)[A-Za-z\d]{6,16}$/;
console.log(reg.test('123456')); // false: 只有数字
console.log(reg.test('abcdef')); // false: 只有字母
console.log(reg.test('abc123')); // true: 字母和数字组合
console.log(reg.test('abc1234567890')); // true: 符合 6-16 位
console.log(reg.test('abc!123')); // false: 包含特殊字符
解释:
(?=.*[a-zA-Z])
:至少包含一个字母。(?=.*\d)
:至少包含一个数字。[A-Za-z\d]{6,16}
:6-16 位,由字母和数字组成。
2.4.3 验证真实姓名
规则:
- 必须是 汉字。
- 长度为 2-6 位。
正则表达式:
let reg = /^[\u4e00-\u9fa5]{2,6}$/;
console.log(reg.test('王林')); // true: 符合规则
console.log(reg.test('诸葛孔明')); // true: 符合规则
console.log(reg.test('齐天大圣孙悟空')); // false: 超过 6 位
console.log(reg.test('John')); // false: 非汉字
2.4.4 验证邮箱
规则:
- 邮箱用户名由数字、字母、下划线或
.
组成,但.
和-
不可连续,也不可作为开头。 @
后面由字母或数字组成。- 支持多级域名,如
.com
、.com.cn
等。
正则表达式:
let reg = /^\w+((-\w+)|(.\w+))*@[A-Za-z0-9]+((.[A-Za-z0-9]+)+)$/;
console.log(reg.test('example@gmail.com')); // true
console.log(reg.test('user.name@example.co.uk')); // true
console.log(reg.test('user-name@example.com')); // true
console.log(reg.test('.username@example.com')); // false: 以 `.` 开头
console.log(reg.test('username@@example.com')); // false: 多个 `@`
2.4.5 验证身份证号
规则:
- 总共 18 位,最后一位可以是数字或大写字母
X
。 - 前 6 位是地区代码。
- 接下来的 8 位表示出生日期,格式为
YYYYMMDD
。 - 最后 4 位是个人编号,其中倒数第二位表示性别(奇数为男性,偶数为女性)。
正则表达式:
let reg = /^([1-9]\d{5})((19|20)\d{2})(0[1-9]|1[0-2])(0[1-9]|[1-2]\d|30|31)\d{3}(\d|X)$/i;
console.log(reg.test('11010519900307211X')); // true: 符合规则
console.log(reg.test('110105199003072119')); // true: 符合规则
console.log(reg.test('110105199013072119')); // false: 月份不合法
console.log(reg.test('110105199002302119')); // false: 日期不合法
console.log(reg.test('11010519900307211x')); // true: 最后一位忽略大小写
解释:
([1-9]\d{5})
:前 6 位地区代码。((19|20)\d{2})
:出生年份,支持 1900-2099。(0[1-9]|1[0-2])
:出生月份,01-12。(0[1-9]|[1-2]\d|30|31)
:出生日期,01-31。\d{3}(\d|X)
:个人编号(3 位数字)和校验码(1 位数字或X
)。
3. 正则捕获
正则表达式不仅可以用于匹配,还可以用于提取和捕获字符串中的特定内容。要实现正则捕获,首先需要确保正则匹配成功(即 test
方法返回 true
后才能进行捕获操作)。
常用的涉及正则捕获的工具:
- 正则表达式对象方法:如
exec
和test
。 - 字符串方法:如
replace
、match
、split
等。
3.1 exec
方法(正则原型上的方法)
3.1.1 懒惰性:默认只捕获一个结果
-
exec
返回的结果:- 第一项:本次捕获到的完整匹配内容。
- 其余项:每个分组单独匹配到的内容(若正则中包含分组)。
index
属性:匹配内容在字符串中的起始索引位置。input
属性:原始字符串。
let str = 'name2020name2020name2020';
let reg = /\d+/;
console.log(reg.exec(str));
// ["2020", index: 4, input: "name2020name2020name2020", groups: undefined]
注意:exec
默认只捕获第一个匹配结果。
3.1.2 懒惰性的解决方法
默认情况下,lastIndex
(正则的下一次匹配起始位置)不会自动更新,因此 exec
每次从字符串开始捕获,导致只能捕获第一个匹配项。
解决方法:设置全局修饰符 g
。
let str = 'name2020name2020name2020';
let reg = /\d+/g;
let result;
while ((result = reg.exec(str)) !== null) {
console.log(result);
// 每次捕获一个结果
}
// ["2020", index: 4, input: "name2020name2020name2020", groups: undefined]
// ["2020", index: 12, input: "name2020name2020name2020", groups: undefined]
// ["2020", index: 20, input: "name2020name2020name2020", groups: undefined]
3.1.3 贪婪性
正则默认具有贪婪性:会尽可能多地匹配内容。
let str = 'name2020';
let reg = /\d+/g;
console.log(str.match(reg)); // ["2020"]
3.1.4 解决贪婪性问题
在量词元字符后添加 ?
,可将捕获变为非贪婪模式(匹配最短结果)。
let str = 'name2020';
let reg = /\d+?/g;
console.log(str.match(reg)); // ["2", "0", "2", "0"]
3.2 正则分组捕获
分组捕获通过小括号 ()
实现。使用 exec
或 match
方法,可以分别获取整个正则匹配的结果及每个分组的结果。
分组捕获规则
- 大正则:完整匹配的结果。
- 小分组:每个分组匹配的结果。
// 身份证号验证
let str = '130222195202303210';
let reg = /^([1-9]\d{5})((19|20)\d{2})(0[1-9]|1[0-2])(0[1-9]|[1-2]\d|30|31)\d{3}(\d|x)$/i;
console.log(reg.exec(str));
// [
// "130222195202303210", // 完整匹配
// "130222", // 地区码
// "1952", // 出生年份
// "19", // 年份前两位
// "02", // 月份
// "30", // 日期
// "0" // 校验位
// ]
console.log(str.match(reg));
// 效果类似 exec,返回完整匹配及分组结果。
注意:当正则设置了全局修饰符 g
时,match
方法不会捕获分组的内容,只会返回所有完整匹配项。
3.3 非捕获分组
有时我们需要分组仅用于调整优先级,而不需要捕获该分组的内容。此时可以使用 ?:
来定义非捕获分组。
let reg = /(?:\d{4})-(\d{2})-(\d{2})/;
let str = '2023-01-08';
console.log(reg.exec(str));
// ["2023-01-08", "01", "08"]
// 只有后两个分组内容被捕获
3.4 字符串的 match
方法
match
方法在不设置全局修饰符时,功能类似 exec
,捕获完整匹配及分组结果。
let str = '2020-01-01';
let reg = /(\d{4})-(\d{2})-(\d{2})/;
console.log(str.match(reg));
// [
// "2020-01-01", // 完整匹配
// "2020", // 年
// "01", // 月
// "01" // 日
// ]
设置全局修饰符时,match
返回所有完整匹配结果,但不包含分组。
let str = '2020-01-01 and 2021-12-31';
let reg = /\d{4}-\d{2}-\d{2}/g;
console.log(str.match(reg)); // ["2020-01-01", "2021-12-31"]
3.5 字符串原型上的replace
方法
实践:
let template = `我是{{name}},年龄{{age}},性别{{sex}}`;
let person = {
name:'王林',
age:12,
sex:'男'
}
如何让上面的字符串 我是{{name}},年龄{{age}},性别{{sex}}
替换成 我是王林,年龄12,性别男
呢?
方法一:while循环
function compile(template,data){
let reg = /\{\{([a-z]+)\}\}/;
while(reg.test(template)){
let key = reg.exec(template)[1];
let value = data[key];
template = template.replace(reg,value);
}
return template;
}
console.log(compile2(template,person));
成功!🥳🥳🥳
方法二:递归调用
function compile2(template,data){
const reg = /\{\{(\w+)\}\}/;
// console.log(Object.prototype.toString.call(reg))
if(reg.test(template)){
const name = reg.exec(template)[1];// 拿到分组的key ()
template = template.replace(reg,name in data ? data[name] : "");
return compile2(template,data)// 递归调用
}else{
return template;// 没有匹配到的情况返回结果
}
}
console.log(compile2(template,person));
成功!🥳🥳🥳
方法三:g
function compile3(template, data) {
let reg = /\{\{([a-z]+)\}\}/g;
return template.replace(reg, (match, key) => {
return data[key] !== undefined ? data[key] : match; // 如果 key 存在于 data 中,则替换
});
}
console.log(compile3(template, person));
成功!🥳🥳🥳
结语
看到这里,你一定累了吧🥱,整理一下,为拿下面试做准备吧!🎉🎉🎉
点赞哦~😘