正则表达式

正则表达式在开发中很常用,但是很多人一般不会去纠结这玩意到底怎么写,大部分人呢都是从网上找一个符合要求的写上去就行。

这次就来详细的探讨一下正则表达式到底该如何写,有哪些坑点。我看网上很多的文章都是讲解正则表达式有哪些字符,但是不去讲解它的相关属性方法,这就导致很多人在实操的时候会出现一些大大小小的问题,然后又去查半天的资料,结果是相关属性方法的问题,白白浪费大半天时间。


一、正则表达式的创建方式

在JavaScript中,创建正则表达式有两种方式:字面量方式构造函数方式,两者的使用场景略有不同。

1. 字面量方式(推荐)

/正则表达式/修饰符 的格式创建,适用于正则规则固定的场景。

javascript 复制代码
// 匹配字符串中的"cat"(忽略大小写)
const reg1 = /cat/i;

2. 构造函数方式

new RegExp(规则字符串, 修饰符) 创建,适用于正则规则需要动态生成的场景(如规则包含变量)。

javascript 复制代码
// 动态生成匹配规则:匹配变量word的值
const word = "cat";
const reg2 = new RegExp(word, "i"); // 等价于 /cat/i

两种方式的区别

特性 字面量方式 构造函数方式
语法格式 /pattern/flags new RegExp(pattern, flags)
转义字符 需转义1次(如 /\d/ 需转义2次(如 new RegExp("\\d")
动态规则 不支持(规则固定) 支持(规则可含变量)
性能 解析时缓存,性能略优 每次创建都重新解析

三、正则表达式的核心语法

正则的语法由「原子」「量词」「边界符」「修饰符」「分组与引用」等部分组成,我们逐一拆解。

1. 原子:正则的"最小匹配单位"

原子是正则中不可再分割的基本单位,用于匹配单个字符,主要分为3类:

(1)普通原子

直接匹配字符本身,如字母(a-z)、数字(0-9)、符号(!@#)等。

  • 示例:/abc/ 匹配字符串中的"abc"(如"abc123"会匹配"abc")。
(2)特殊原子(元字符)

具有特殊含义的字符,需牢记常用元字符:

元字符 含义 示例
. 匹配除换行符(\n)外的任意字符 /a.b/ 匹配"aab""acb"
\d 匹配任意数字(等价于 [0-9] /\d{3}/ 匹配"123"
\D 匹配非数字(等价于 [^0-9] /\D/ 匹配"a""#"
\w 匹配字母、数字、下划线(等价于 [a-zA-Z0-9_] /\w/ 匹配"a""5""_"
\W 匹配非字母、数字、下划线 /\W/ 匹配"@""空格"
\s 匹配空白字符(空格、制表符\t、换行符\n等) /a\sb/ 匹配"a b"
\S 匹配非空白字符 /\S/ 匹配"a""1"
\n 匹配换行符 /a\nb/ 匹配"a换行b"
\t 匹配制表符 /a\tb/ 匹配"a b"
(3)自定义原子(字符组)

[] 包裹多个字符,匹配其中任意一个字符,支持范围表示。

  • 示例:
    • [abc] 匹配"a""b"或"c"(等价于 a|b|c);
    • [a-z] 匹配任意小写字母;
    • [A-Za-z0-9] 匹配任意字母或数字(等价于 \w 去掉下划线);
    • [^abc] 匹配除"a""b""c"外的任意字符(^ 在字符组中表示"排除")。

2. 量词:控制原子的匹配次数

量词用于指定前面的原子(或原子组)需要匹配的次数,常用量词如下:

量词 含义 示例
* 匹配0次或多次(等价于 {0,} /a*/ 匹配"""a""aa"
+ 匹配1次或多次(等价于 {1,} /a+/ 匹配"a""aa"
? 匹配0次或1次(等价于 {0,1} /a?/ 匹配"""a"
{n} 精确匹配n次 /a{3}/ 匹配"aaa"
{n,} 匹配n次或更多次 /a{2,}/ 匹配"aa""aaa"
{n,m} 匹配n到m次(含n和m) /a{2,4}/ 匹配"aa""aaa""aaaa"
贪婪与非贪婪模式(重点)

默认情况下,量词是贪婪模式 ------即尽可能多地匹配字符;在量词后加 ? 可切换为非贪婪模式------即尽可能少地匹配字符。

  • 示例:用 /a.*b/(贪婪)和 /a.*?b/(非贪婪)匹配字符串"aabab"
    • 贪婪模式:a.*b 会从第一个"a"匹配到最后一个"b",结果为"aabab";
    • 非贪婪模式:a.*?b 会从第一个"a"匹配到最近的"b",结果为"aab"。

这是正则中最容易踩坑的点之一,比如提取HTML标签时,贪婪模式可能会匹配到多余的内容。

3. 边界符:控制匹配的位置

边界符用于指定匹配的"位置"(而非字符),常用边界符如下:

边界符 含义 示例
^ 匹配字符串的开头(多行模式下匹配每行开头) /^abc/ 匹配"abc123"中的"abc",不匹配"123abc"
$ 匹配字符串的结尾(多行模式下匹配每行结尾) /abc$/ 匹配"123abc"中的"abc",不匹配"abc123"
\b 匹配单词边界(单词与非单词的交界处) /\bcat\b/ 匹配"cat""cat123""123cat",不匹配"category"
\B 匹配非单词边界 /\Bcat\B/ 匹配"category"中的"cat",不匹配"cat123"
  • 常见误区:^$ 在默认模式(单行模式)下只匹配整个字符串的开头和结尾;若开启多行模式(修饰符 m),则会匹配每一行的开头和结尾。

4. 修饰符:调整正则的匹配规则

修饰符用于全局调整正则的匹配行为,写在正则的末尾(字面量方式)或构造函数的第二个参数(构造函数方式),常用修饰符如下:

修饰符 含义 示例
i 忽略大小写(ignore case) /cat/i 匹配"cat""Cat""CAT"
g 全局匹配(global) /a/g 匹配字符串中所有的"a"
m 多行模式(multiline) /^a/m 匹配"a\nabc"中的两个"a"(每行开头)
s 单行模式(dotall):让 . 匹配换行符 /a.b/s 匹配"a\nb"
u Unicode模式:支持匹配Unicode字符(如中文、 emoji) /[\u4e00-\u9fa5]/u 匹配中文
y 粘性匹配(sticky):只从 lastIndex 位置开始匹配 见下文属性部分

5. 分组与引用:处理复杂匹配

当需要将多个原子视为一个整体(或捕获匹配结果)时,需要用到分组与引用。

(1)普通分组(()

() 将多个原子包裹,视为一个"原子组",可配合量词使用。

  • 示例:/(ab)+/ 匹配"ab""abab""ababab"(将"ab"视为一个整体,匹配1次或多次)。
(2)捕获分组与引用

普通分组会"捕获"匹配到的内容,后续可通过「反向引用」或「正则方法」获取捕获结果:

  • 反向引用:用 \n(n为分组序号,从1开始)引用前面捕获的内容;

  • 示例:匹配重复的两个字符,如"aa""bb""cc":

    javascript 复制代码
    const reg = /(\w)\1/;
    console.log(reg.test("aa")); // true
    console.log(reg.test("ab")); // false
  • 捕获结果获取:通过 exec()match() 方法获取分组捕获的内容(见下文方法部分)。

(3)非捕获分组((?:)

若只需将多个原子视为整体,无需捕获结果,可用 (?:) 减少性能开销。

  • 示例:/(?:ab)+/ 匹配"ab""abab",但不会捕获"ab"(比普通分组更高效)。
(4)正向预查与负向预查(断言)

预查(断言)用于判断"当前位置的前后是否符合特定规则",但不捕获结果(零宽度匹配),分为4种:

预查类型 语法 含义 示例
正向肯定预查 (?=pattern) 后面必须匹配pattern /a(?=b)/ 匹配"ab"中的"a",不匹配"ac"中的"a"
正向否定预查 (?!pattern) 后面不能匹配pattern /a(?!b)/ 匹配"ac"中的"a",不匹配"ab"中的"a"
反向肯定预查 (?<=pattern) 前面必须匹配pattern /(?<=a)b/ 匹配"ab"中的"b",不匹配"cb"中的"b"
反向否定预查 (?<!pattern) 前面不能匹配pattern /(?<!a)b/ 匹配"cb"中的"b",不匹配"ab"中的"b"
  • 实战场景:给手机号中间4位加*(如"138****1234"):

    javascript 复制代码
    const phone = "13812341234";
    const result = phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
    console.log(result); // "138****1234"

    这里 (\d{3})(\d{4}) 是捕获分组,$1$2 引用分组结果。

四、正则表达式的属性

正则对象(RegExp 实例)有一些内置属性,用于获取正则的配置或状态,常用属性如下:

属性名 含义 只读/可写 示例
source 返回正则的"规则字符串"(不含修饰符) 只读 /cat/i.source → "cat"
global 判断是否有 g 修饰符(全局匹配) 只读 /cat/g.global → true
ignoreCase 判断是否有 i 修饰符(忽略大小写) 只读 /cat/i.ignoreCase → true
multiline 判断是否有 m 修饰符(多行模式) 只读 /^cat/m.multiline → true
lastIndex 下一次匹配的起始位置(仅 gy 修饰符生效) 可写 见下文示例
sticky 判断是否有 y 修饰符(粘性匹配) 只读 /cat/y.sticky → true

重点属性:lastIndex

这玩意很重要,反正我是被这玩意坑过😓,我去学正则表达式的属性也是因为这玩意。
lastIndex 是正则中最特殊的属性,仅在正则有 g(全局匹配)或 y(粘性匹配)修饰符时生效,用于记录"下一次匹配的起始位置"。就是这个下一次匹配的起始位置 ,就可能导致你的匹配结果出错,要重点注意

示例1:g 修饰符与 lastIndex
javascript 复制代码
const reg = /a/g;
const str = "aaa";

// 第一次匹配:从 index 0 开始,匹配到 "a",lastIndex 变为 1
console.log(reg.exec(str)); // ["a", index: 0]
console.log(reg.lastIndex); // 1

// 第二次匹配:从 index 1 开始,匹配到 "a",lastIndex 变为 2
console.log(reg.exec(str)); // ["a", index: 1]
console.log(reg.lastIndex); // 2

// 第三次匹配:从 index 2 开始,匹配到 "a",lastIndex 变为 3
console.log(reg.exec(str)); // ["a", index: 2]
console.log(reg.lastIndex); // 3

// 第四次匹配:从 index 3 开始,无匹配,lastIndex 重置为 0
console.log(reg.exec(str)); // null
console.log(reg.lastIndex); // 0
示例2:y 修饰符与 lastIndex(粘性匹配)

y 修饰符比 g 更严格------仅从 lastIndex 位置开始匹配,若该位置不匹配则直接返回 nullg 会继续向后查找)。

javascript 复制代码
const regG = /a/g;
const regY = /a/y;
const str = "a1a2a3";

// g修饰符:从 index 0 匹配 "a",lastIndex 变为 1;下一次从 1 开始,跳过 "1" 匹配 index 2 的 "a"
console.log(regG.exec(str)); // ["a", index: 0]
console.log(regG.exec(str)); // ["a", index: 2]

// y修饰符:从 index 0 匹配 "a",lastIndex 变为 1;下一次从 1 开始(字符 "1"),不匹配,返回 null
console.log(regY.exec(str)); // ["a", index: 0]
console.log(regY.exec(str)); // null

五、正则表达式的常用方法

正则的方法分为两类:正则对象的方法test()exec())和字符串的方法match()replace() 等),我们重点讲解常用方法。

1. 正则对象的方法

(1)test(str):判断字符串是否匹配
  • 作用:检查 str 是否符合正则规则,返回 true(匹配)或 false(不匹配)。

  • 特点:

    • g 修饰符时,会更新 lastIndex(下次匹配从 lastIndex 开始);
    • g 修饰符时,lastIndex 始终为 0(每次都从开头匹配)。
  • 示例:

    javascript 复制代码
    const reg = /cat/;
    console.log(reg.test("cat123")); // true
    console.log(reg.test("dog123")); // false
(2)exec(str):获取匹配结果(含分组)
  • 作用:在 str 中查找符合正则的内容,返回一个数组(匹配成功)或 null(匹配失败)。

  • 返回数组的结构:

    • 索引 0:完整的匹配结果;
    • 索引 1~n:各捕获分组的匹配结果;
    • index:匹配结果在字符串中的起始位置;
    • input:原始字符串。
  • 示例(含分组):

    javascript 复制代码
    const reg = /(\w+)@(\w+)\.(\w+)/; // 匹配邮箱,捕获用户名、域名、后缀
    const str = "my email is test@example.com";
    const result = reg.exec(str);
    
    console.log(result);
    // [
    //   "test@example.com",  // 完整匹配结果(索引0)
    //   "test",              // 分组1:用户名(索引1)
    //   "example",           // 分组2:域名(索引2)
    //   "com",               // 分组3:后缀(索引3)
    //   index: 12,           // 匹配起始位置
    //   input: "my email is test@example.com" // 原始字符串
    // ]
  • 注意:有 g 修饰符时,可循环调用 exec() 获取所有匹配结果(直到返回 null)。

2. 字符串的方法

(1)str.match(reg):获取匹配结果
  • 作用:与 exec() 类似,但返回结果格式因 reg 是否有 g 修饰符而异:

    • g 修饰符:返回与 exec() 相同的数组(含分组、index、input);
    • g 修饰符:返回一个数组,包含所有完整匹配结果(不含分组)。
  • 示例:

    javascript 复制代码
    const reg1 = /(\w+)@(\w+)/; // 无g
    const reg2 = /(\w+)@(\w+)/g; // 有g
    const str = "a@b, c@d";
    
    console.log(str.match(reg1)); 
    // ["a@b", "a", "b", index: 0, input: "a@b, c@d"](含分组)
    
    console.log(str.match(reg2)); 
    // ["a@b", "c@d"](仅完整匹配结果)
(2)str.replace(reg, replacement):替换匹配内容
  • 作用:用 replacement 替换 str 中符合 reg 的内容,返回新字符串(不修改原字符串)。

  • replacement 的两种形式:

    • 字符串:可使用 $n 引用分组结果($0 是完整匹配,$1 是分组1,以此类推);
    • 函数:参数依次为"完整匹配结果、分组1、分组2、...、index、input",返回值为替换后的内容。
  • 示例1:用字符串替换(手机号中间4位加*)

    javascript 复制代码
    const phone = "13812341234";
    const newPhone = phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
    console.log(newPhone); // "138****1234"
  • 示例2:用函数替换(首字母大写)

    javascript 复制代码
    const str = "hello world";
    const newStr = str.replace(/\b\w/g, (match) => match.toUpperCase());
    console.log(newStr); // "Hello World"
(3)str.search(reg):查找匹配的起始位置
  • 作用:返回 str 中第一个符合 reg 的子串的起始位置,若不匹配则返回 -1

  • 特点:忽略 g 修饰符(始终只找第一个匹配),不更新 lastIndex

  • 示例:

    javascript 复制代码
    const str = "abc123abc";
    console.log(str.search(/abc/)); // 0(第一个"abc"的起始位置)
    console.log(str.search(/123/)); // 3("123"的起始位置)
(4)str.split(reg):按匹配结果分割字符串
  • 作用:用 reg 匹配的内容作为分隔符,将 str 分割为数组,返回分割后的数组。

  • 示例:按任意空白字符分割字符串

    javascript 复制代码
    const str = "a  b\tc\nd"; // 包含空格、制表符、换行符
    const arr = str.split(/\s+/); // 按1个或多个空白字符分割
    console.log(arr); // ["a", "b", "c", "d"]

六、常见的正则表达式场景

掌握语法后,我们结合实际开发场景,看看正则如何解决问题。

1. 表单验证

(1)手机号验证(中国大陆手机号)

规则:11位数字,以13/14/15/17/18/19开头。

javascript 复制代码
const reg = /^1[3-9]\d{9}$/;
console.log(reg.test("13812341234")); // true
console.log(reg.test("12345678901")); // false(开头不是13-19)
(2)邮箱验证(简化版)

规则:包含 @.@ 前有字符,@. 之间有字符,. 后有2~6位字符。

javascript 复制代码
const reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\.[a-zA-Z]{2,6}$/;
console.log(reg.test("test@example.com")); // true
console.log(reg.test("test@.com")); // false(@和.之间无字符)
(3)身份证号验证(18位)

规则:17位数字 + 1位数字/字母X(不区分大小写)。

javascript 复制代码
const reg = /^\d{17}[\dXx]$/;
console.log(reg.test("110101199001011234")); // true
console.log(reg.test("11010119900101123X")); // true

2. 字符串处理

(1)提取URL中的参数

示例:从"https://example.com?name=张三\&age=20"中提取 nameage 的值。

javascript 复制代码
const url = "https://example.com?name=张三&age=20";
const reg = /([^?&=]+)=([^&]+)/g; // 匹配"key=value"格式
const params = {};
let result;
while ((result = reg.exec(url)) !== null) {
  const key = decodeURIComponent(result[1]); // 解码(处理中文)
  const value = decodeURIComponent(result[2]);
  params[key] = value;
}
console.log(params); // { name: "张三", age: "20" }
(2)替换敏感词

示例:将字符串中的"敏感词1""敏感词2"替换为"***"。

javascript 复制代码
const str = "这是敏感词1,那是敏感词2";
const sensitiveWords = ["敏感词1", "敏感词2"];
const reg = new RegExp(sensitiveWords.join("|"), "g"); // 动态生成正则
const newStr = str.replace(reg, "***");
console.log(newStr); // "这是***,那是***"
(3)格式化日期

示例:将"20240520"格式化为"2024-05-20"。

javascript 复制代码
const dateStr = "20240520";
const reg = /(\d{4})(\d{2})(\d{2})/;
const formattedDate = dateStr.replace(reg, "$1-$2-$3");
console.log(formattedDate); // "2024-05-20"

七、小结

总的来说,正则表达式并不难理解,就是它的字符有点多,但是我觉得这个是次要的,主要是重点理解它的属性,字符不记得直接查就能写出来,但是属性不记得的话,那错误一查就是大半天!!!!

参考资料

正则表达式:MDN官网------正则表达式

RegExp:MDN官网------RegExp

相关推荐
崔庆才丨静觅6 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
KYGALYX1 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法2 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端