正则表达式

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

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


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

在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

相关推荐
小咕聊编程3 小时前
【含文档+源码】基于SpringBoot的过滤协同算法之网上服装商城设计与实现
java·spring boot·后端
光影少年7 小时前
angular生态及学习路线
前端·学习·angular.js
迎風吹頭髮8 小时前
Linux内核架构浅谈8-Linux内核与UNIX的传承:设计思想与特性差异
linux·运维·架构
記億揺晃着的那天9 小时前
Vue + Element UI 表格自适应高度如何做?
javascript·vue.js·ui
无尽夏_10 小时前
HTML5(前端基础)
前端·html·html5
追逐时光者10 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_10 小时前
敏捷开发流程-精简版
前端·后端
FIN666810 小时前
昂瑞微冲刺科创板:创新驱动,引领射频芯片国产化新征程
前端·安全·前端框架·信息与通信·芯片
GISer_Jing10 小时前
ByteDance——jy真题
前端·javascript·面试