正则表达式实战指南:从手机号验证到模板引擎,5 个案例彻底搞懂 RegExp

16 阅读7分钟

正则表达式实战指南:从手机号验证到模板引擎,5 个案例彻底搞懂 RegExp

正则表达式是前端面试的高频考点,也是实际开发中处理字符串的利器。这篇文章通过 5 个真实案例,带你从零掌握正则的核心语法。

前言

今天课程从一道拼多多笔试题出发,系统学习了 JavaScript 正则表达式(RegExp)。

从手机号验证到字符串提取,从驼峰命名转换到简易模板引擎——每个案例都对应正则的一个核心用法。这篇文章将完整复盘学习过程。


一、正则表达式是什么?

1.1 一句话理解

正则表达式(Regular Expression)是一种描述字符串匹配规则的工具。它用一种特殊的语法,精确地定义"我要找什么样的字符串"。

1.2 核心语法速查

正则表达式核心语法

/  /          ← 正则的字面量写法
[]            ← 字符范围,匹配括号内的任意一个字符
\d            ← 数字(等价于 [0-9])
\w            ← 字母/数字/下划线(等价于 [a-zA-Z0-9_])
{}            ← 匹配次数
^             ← 以什么开头
$             ← 以什么结尾
g             ← 全局匹配修饰符
()            ← 分组(捕获)
$1            ← 引用第 1 个分组

1.3 JS 中的正则类型

console.log(typeof /abc/);  // "object"
console.log(typeof {});     // "object"

// 如何区分正则和普通对象?
console.log(
    Object.prototype.toString.call(/abc/)
);  // "[object RegExp]"

💡 typeof 无法区分正则和普通对象(都是 object),需要用 Object.prototype.toString.call() 来精确判断。


二、案例一:手机号验证(拼多多笔试题)

2.1 需求分析

验证用户输入的手机号是否合法:

规则说明
11 位数字手机号固定 11 位
以 1 开头中国手机号首位固定为 1
第二位 3-9第二位不能为 0、1、2
后 9 位数字剩余 9 位为任意数字

2.2 正则拆解

const reg = /^1[3-9]\d{9}$/;
正则逐字符解析

/^1[3-9]\d{9}$/
 │ │  │  │  │ │
 │ │  │  │  │ └── $ 字符串结尾(精确匹配)
 │ │  │  │  └─── {9} 前面的 \d 重复 9 次
 │ │  │  └────── \d 任意数字 [0-9]
 │ │  └───────── [3-9] 第二位 39
 │ └──────────── 1 第一位固定为 1
 └─────────────── ^ 字符串开头(精确匹配)

2.3 完整代码

let str = '13888888888';
let reg = /^1[3-9]\d{9}$/;

console.log(reg.test(str));  // true

reg.test('12345678901');  // false(第二位是 2)
reg.test('1388888888');   // false(只有 10 位)
reg.test('23888888888');  // false(不以 1 开头)

🎯 永远不要相信用户的输入——把用户当小白,所有输入都要验证。


三、案例二:提取字符串中的数字

3.1 需求

从一段文本中提取所有数字。

3.2 核心语法:match + g 全局匹配

const str = '价格是100元,进价是90元,赚了10元';
const reg = /\d+/g;
const res = str.match(reg);
console.log(res);  // ['100', '90', '10']
正则解析

/\d+/g
 │  │ └── g 全局匹配(不找到第一个就停,找所有)
 │  └─── + 匹配一次或多次(贪婪模式,尽可能多匹配)
 └────── \d 匹配任意数字

匹配过程:
'价格是100元,进价是90元,赚了10元'
         ^^^         ^^^        ^^
        100         90         10

3.3 match vs test

方法返回值用途
reg.test(str)true / false判断是否匹配
str.match(reg)匹配结果的数组提取匹配内容

💡 + 量词\d 只匹配一个数字,\d+ 匹配一个或多个数字。+ 是贪婪模式,会尽可能多地匹配。


四、案例三:字符串替换(驼峰命名转换)

4.1 需求

hello-world 转换为 helloWorld(短横线命名 → 小驼峰命名)。

4.2 核心语法:replace + () 分组

const str = 'hello-world';
const reg = /-(\w)/;

// match 查看分组捕获
console.log(str.match(reg));
// ['h-w', 'w', index: 5, ...]
//  ↑     ↑
//  完整匹配  第1个分组(括号内的内容)

4.3 分组(Group)详解

/-(\w)/
 │  │
 │  └── (\w) 分组:用括号包裹,捕获匹配的内容
 └───── - 匹配连字符

match 返回值结构:
['-w', 'w', index: 5, input: 'hello-world']
  ↑     ↑
  完整  第1个分组($1)
  匹配  括号内的内容

4.4 replace 的回调函数

const str = 'hello-world';
const reg = /-(\w)/;

const res = str.replace(reg, (_, c) => {
    console.log(_, c);  // '-w'  'w'
    // _ = 完整匹配  c = 第1个分组
    return c.toUpperCase();
});

console.log(res);  // 'helloWorld'
replace 回调函数参数

str.replace(reg, (完整匹配, 分组1, 分组2, ..., 偏移量, 原字符串) => {
    // 完整匹配 = _(通常用 _ 表示不使用)
    // 分组1 = $1
    // 偏移量 = 匹配在原字符串中的位置
    // 原字符串 = 原始输入
});

4.5 多个连字符的处理

// 只替换了第一个 -w
'hello-world'.replace(/-(\w)/, (_, c) => c.toUpperCase())
// 'helloWorld' ✅

// 如果有多个连字符?
'hello-my-world'.replace(/-(\w)/, (_, c) => c.toUpperCase())
// 'helloMy-world' ❌ 只替换了第一个!

📌 注意:不加 g 修饰符,replace 只替换第一个匹配。要替换所有匹配,需要加 g


五、案例四:replace 回调函数深入理解

5.1 回调函数的参数结构

"hello-world".replace(
    /-(\w)/,
    (...args) => {
        console.log(args);
        // args[0] = '-w'     完整匹配
        // args[1] = 'w'      第1个分组
        // args[2] = 5        匹配位置的偏移量
        // args[3] = 'hello-world'  原始字符串
        return '';
    }
);

5.2 参数对照表

参数位置含义示例值
args[0] / _完整匹配的子串'-w'
args[1] / $1第 1 个分组'w'
args[2] / $2第 2 个分组(如有)
args[n-2]偏移量(匹配位置)5
args[n-1]原始字符串'hello-world'

💡 _ 是约定俗成的写法,表示"这个参数我不关心,用下划线占位"。这是 JS 开发中的常见惯例。


六、案例五:简易模板引擎(递归 + 正则)

6.1 需求

实现一个简易模板引擎,将 {{name}} 替换为实际数据。

let template = `我是{{name}},今年{{age}}岁,性别{{sex}}`;
let person = {
    name: '张三',
    age: 18,
    sex: '男'
};

6.2 核心思路

模板引擎工作流程

"我是{{name}},今年{{age}}岁"
         │
         ▼
找到第一个 {{name}}
         │
         ▼
替换为 data['name'] → '张三'
         │
         ▼
"我是张三,今年{{age}}岁"
         │
         ▼
找到下一个 {{age}}
         │
         ▼
替换为 data['age'] → 18
         │
         ▼
"我是张三,今年18岁,性别{{sex}}"
         │
         ▼
... 递归直到没有 {{}} 为止

6.3 完整代码

let template = `我是{{name}},今年{{age}}岁,性别{{sex}}`;
let person = {
    name: '张三',
    age: 18,
    sex: '男'
};

function render(template, data) {
    // {} 在正则中有特殊含义,需要转义为 \{\{
    const reg = /\{\{(\w+)\}\}/;
    
    if (reg.test(template)) {
        // exec 返回匹配结果数组,[1] 是第一个分组
        const name = reg.exec(template)[1];
        // 用 data 中的值替换模板变量
        template = template.replace(reg, data[name]);
        // 递归:继续处理下一个 {{}}
        return render(template, data);
    }
    
    return template;
}

console.log(render(template, person));
// "我是张三,今年18岁,性别男"

6.4 正则拆解

/\{\{(\w+)\}\}/
 │  │  │  │ │
 │  │  │  │ └── } 匹配右花括号
 │  │  │  └─── \} 转义的右花括号
 │  │  └────── (\w+) 分组:捕获变量名
 │  └───────── \{ 转义的左花括号
 └──────────── \{ 匹配左花括号

为什么需要转义?
{} 在正则中表示"匹配次数",如 \d{3}
要匹配字面量的 {,需要写 \{

6.5 exec vs match

方法调用者返回值
reg.exec(str)正则对象匹配详情数组(含分组)
str.match(reg)字符串对象匹配结果数组
const reg = /\{\{(\w+)\}\}/;
const template = '我是{{name}}';

reg.exec(template);
// ['{{name}}', 'name', index: 2, input: '我是{{name}}']
//  ↑ 完整匹配    ↑ 分组1

template.match(reg);
// ['{{name}}', 'name', index: 2, input: '我是{{name}}']
// 结果相同(无 g 修饰符时)

🎯 这个简易模板引擎的原理,就是 Vue、React 等框架模板编译的基础思想!


七、JS 数据类型补充

7.1 两大类型阵营

JS 数据类型

基本类型(值类型)
├── Number    数值
├── String    字符串
├── Boolean   布尔值
├── Null      空值
└── Undefined 未定义

引用类型(对象类型)
├── Object    普通对象
├── Array     数组
├── Function  函数
├── Date      日期
├── RegExp    正则表达式 ← 今天的主角
├── Error     错误对象
├── Math      数学对象
└── JSON      JSON 对象

💡 RegExp 是引用类型typeof 返回 object。它是 JS 中专门处理字符串模式匹配的对象。


八、知识图谱

📚 正则表达式知识图谱

核心语法
├── /  /          字面量
├── []            字符范围
├── \d            数字
├── \w            字母/数字/下划线
├── {}            匹配次数
├── ^ / $         开头 / 结尾
├── g             全局匹配修饰符
├── ()            分组捕获
└── $1            引用分组

JS 正则方法
├── reg.test(str)      是否匹配 → boolean
├── str.match(reg)    提取匹配 → 数组
├── str.replace(reg, fn)  替换匹配 → 新字符串
└── reg.exec(str)     匹配详情 → 数组

实战案例
├── 手机号验证
│   └── /^1[3-9]\d{9}$/
├── 提取数字
│   └── /\d+/g + match
├── 驼峰命名转换
│   └── /-(\w)/ + replace 回调
└── 简易模板引擎
    └── /\{\{(\w+)\}\}/ + 递归

关键概念
├── 分组 ():捕获匹配内容
├── $1:引用第 1 个分组
├── g 修饰符:全局匹配
├── + 量词:一次或多次
├── 转义 \{:匹配字面量 {
└── 递归:重复处理直到无匹配

九、正则速查表

语法含义示例
.任意字符(除换行)/a.c/abc
\d数字 [0-9]/\d+/123
\w字母/数字/下划线/\w+/hello
\s空白字符/\s+/
^开头/^hello/
$结尾/world$/
*0 次或多次/ab*c/ac
+1 次或多次/ab+c/abc
?0 次或 1 次/colou?r/color
{n}恰好 n 次/\d{4}/2024
{n,m}n 到 m 次/\d{1,3}/42
[]字符范围/[a-z]/a
()分组捕获/(ab)/
|/cat|dog/
g全局匹配/\d+/g

结语

正则表达式看似"天书",但掌握了核心语法后,你会发现它处理字符串的能力非常强大。

今天的 5 个案例覆盖了正则最常用的场景:

  1. 验证test() + ^ $ 精确匹配
  2. 提取match() + g 全局匹配
  3. 替换replace() + () 分组
  4. 回调replace(fn) + $1 引用
  5. 递归exec() + 递归函数

记住:正则不是背出来的,是用出来的。遇到字符串处理的需求,先想想能不能用正则解决。

希望这篇文章对你有帮助!如果有任何问题,欢迎在评论区交流。


📌 参考资源


📌 文章标签 JavaScript 正则表达式 RegExp 前端面试 字符串处理 学习笔记


觉得有收获?点个赞鼓励一下吧!有问题欢迎评论区留言~ 👍