JavaScript 正则表达式完全指南

小飞兽 JavaScript 344 次阅读 2026-07-07

JavaScript 正则表达式完全指南

Introduction

正则表达式(Regular Expression,常简写为 RegExp 或 regex)是处理字符串的利器。无论是表单验证、文本搜索、数据提取还是批量替换,正则表达式都能以极其简洁的方式完成复杂操作。对于 JavaScript 开发者而言,掌握正则表达式是必备的基础技能,因为它能大幅提升处理字符串的效率。

然而,正则表达式的语法较为密集,符号众多,学习曲线陡峭。本文将从零开始,系统讲解 JavaScript 中正则表达式的创建方式、核心语法、常用方法以及实战技巧,帮助你建立完整的正则表达式知识体系,并在实际开发中灵活运用。

---

一、正则表达式基础

1. 创建正则表达式

在 JavaScript 中有两种方式创建正则表达式:

字面量语法(推荐)

const pattern = /正则内容/标志位;
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

构造函数语法(动态构建时使用)

const pattern = new RegExp('正则内容', '标志位');
const dynamicPattern = new RegExp('^' + input + '$', 'i');

两种方式等价,但字面量语法在正则内容固定时性能更好;构造函数适合动态拼接正则场景。

2. 正则表达式的组成

一个正则表达式由以下部分组成:

  • <strong>字面量字符</strong>:<code>a</code>、<code>3</code> 等,匹配自身

  • <strong>元字符</strong>:<code>.</code>、<code>\d</code>、<code>\w</code> 等,具有特殊含义

  • <strong>量词</strong>:<code>+</code>、<code>*</code>、<code>?</code>、<code>{n,m}</code> 等,指定匹配次数

  • <strong>锚点</strong>:<code>^</code>、<code>$</code>、<code>\b</code> 等,定位匹配位置

  • <strong>字符类</strong>:<code>[abc]</code>、<code>[^abc]</code>、<code>[a-z]</code> 等,匹配一类字符

  • <strong>分组与引用</strong>:<code>()</code>、<code>(?:)</code>、<code>(?&lt;name&gt;)</code> 等

  • <strong>断言</strong>:<code>(?=)</code>、<code>(?!)</code>、<code>(?&lt;=)</code>、<code>(?&lt;!)</code> 等,零宽断言

---

二、字符类与字面量

1. 基本字符类

// 匹配任意单个字符(换行符除外)
/./.test('abc');        // true

// 匹配数字
/\d/.test('2026');      // true(等价于 /[0-9]/)
/\D/.test('abc');        // true(非数字)

// 匹配字母、数字、下划线
/\w/.test('user_name');  // true(等价于 /[a-zA-Z0-9_]/)
/\W/.test('!');          // true(非词字符)

// 匹配空白字符(空格、制表符、换行)
/\s/.test('hello world'); // true
/\S/.test('hello');      // true(非空白)

2. 自定义字符类

// 匹配元音字母
/[aeiou]/i.test('Hello'); // true

// 匹配非元音
/[^aeiou]/i.test('xyz');  // true

// 匹配字母(不区分大小写)
/[a-zA-Z]/.test('a');     // true
/[a-zA-Z]/.test('Z');     // true

// 组合:匹配小写字母或数字
/[a-z0-9]/.test('a1');   // true

3. 转义字符

正则中的特殊字符(如 .*? 等)在字面量匹配时需要转义:

// 匹配IP地址中的点号(注意转义)
const ipPattern = /\d+\.\d+\.\d+\.\d+/;
ipPattern.test('192.168.1.1'); // true
ipPattern.test('192X168X1X1');  // false

// 使用 RegExp 构造函数时,字符串中的 \ 需要双重转义
const pattern = new RegExp('\\d+\\.\\d+\\.\\d+\\.\\d+');

---

三、量词

1. 基本量词

| 量词 | 含义 | 示例 |
|------|------|------|
| * | 0次或多次 | /ab*c/ 匹配 acabcabbc |
| + | 1次或多次 | /ab+c/ 匹配 abcabbc,但不匹配 ac |
| ? | 0次或1次 | /colou?r/ 匹配 colorcolour |
| {n} | 恰好n次 | /^\d{4}$/ 匹配 2026 |
| {n,} | 至少n次 | /^\d{2,}$/ 匹配 121231234 |
| {n,m} | n到m次 | /^\d{2,4}$/ 匹配 121234 |

2. 贪婪 vs 非贪婪

const text = 'aaaaaa';

// 贪婪:尽可能多匹配
/a+/.exec(text)[0];      // 'aaaaaa'(全部6个a)

// 非贪婪:尽可能少匹配(加 ?)
/a+?/.exec(text)[0];     // 'a'(只匹配1个a)

// 贪婪量词的实战:提取标签内容
'<h1>标题</h1><p>段落</p>'.match(/<.+?>/g);
// 结果:['<h1>', '</h1>', '<p>', '</p>']

// 对比贪婪版本
'<h1>标题</h1><p>段落</p>'.match(/<.+>/g);
// 结果:['<h1>标题</h1><p>段落</p>'](匹配了一大段)

---

四、锚点与边界

1. 位置锚点

// ^ 匹配字符串开头(多行模式下匹配行首)
/^Hello/.test('Hello World');  // true
/^World/.test('Hello World');  // false

// $ 匹配字符串结尾(多行模式下匹配行尾)
/World$/.test('Hello World');  // true
/Hello$/.test('Hello World');  // false

// 严格匹配手机号(从头到尾完整匹配)
/^1[3-9]\d{9}$/.test('13812345678'); // true
/^1[3-9]\d{9}$/.test('2123456789');   // false(少了1位)

2. 单词边界

// \b 匹配单词边界
/\bcat\b/.test('a cat here');     // true(完整单词cat)
/\bcat\b/.test('a category here'); // false(category包含cat)
/\bcat/.test('category');          // true(cat开头)

// \B 匹配非单词边界
/\Bcat/.test('category');          // true(cat在词内)

3. 实战:提取单词

// 提取一句话中所有单词
'JavaScript is a powerful language!'.match(/\b[a-zA-Z]+\b/g);
// ['JavaScript', 'is', 'a', 'powerful', 'language']

---

五、分组与引用

1. 捕获分组

括号 () 不仅用于分组,还创建捕获组,可以在正则内部或匹配结果中引用:

// 提取日期中的年、月、日
const dateStr = '2026-07-26';
const datePattern = /(\d{4})-(\d{2})-(\d{2})/;
const match = dateStr.match(datePattern);

console.log(match[0]);  // '2026-07-26'(整体匹配)
console.log(match[1]);  // '2026'(第一个捕获组:年)
console.log(match[2]);  // '07'(第二个捕获组:月)
console.log(match[3]);  // '26'(第三个捕获组:日)

2. 在替换中使用捕获组

// 把 "2026-07-26" 转换为 "2026年07月26日"
'2026-07-26'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$1年$2月$3日');
// 结果:'2026年07月26日'

// 交换名字顺序:Last, First → First Last
'Doe, John'.replace(/(\w+), (\w+)/, '$2 $1');
// 结果:'John Doe'

// 使用命名捕获组(ES2018+)
'2026-07-26'.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/, '$<day>/$<month>/$<year>');
// 结果:'26/07/2026'

3. 非捕获分组 <code>(?:)</code>

如果不需要捕获,使用 (?:) 可以提升性能:

// 匹配 "gr" 后面跟 "e" 或 "ae",但不单独捕获 e/ae
/(?:gr)ea/.test('greata');  // true

// 对比:捕获 vs 非捕获的性能差异(大量匹配时)
const pattern1 = /(\d+)-(\d+)/g;  // 创建两个捕获组
const pattern2 = /(?:\d+)-(?:\d+)/g; // 无捕获组,性能更好

4. 反向引用

在正则内部引用之前的捕获组:

// 匹配重复的单词(如 "the the")
/\b(\w+)\s+\1\b/.test('the the');     // true
/\b(\w+)\s+\1\b/.test('the other');   // false
// \1 引用第一个括号中匹配到的内容

// 匹配 HTML 标签(如 <div>...</div>)
/<(\w+)[^>]*>.*?<\/\1>/.test('<div>内容</div>');  // true
/<(\w+)[^>]*>.*?<\/\1>/.test('<span>内容</span>'); // false(标签名不匹配)

---

六、零宽断言(Lookahead & Lookbehind)

1. 正向先行断言 <code>(?=)</code> 和负向先行断言 <code>(?!)</code>

// 正向先行断言:后面紧跟特定内容(不含在匹配中)
/\d+(?=元)/.exec('100元')[0];  // '100'(100后面是元)

// 负向先行断言:后面不能是特定内容
/\d+(?!元)/.exec('100美元')[0]; // '100'(100后面不是元)

// 实战:密码验证——必须包含数字但不能全为数字
function isValidPassword(str) {
  return /^\d+$/.test(str) === false && str.length >= 6;
}

2. 正向后行断言 <code>(?&lt;=)</code> 和负向后行断言 <code>(?&lt;!)</code>

// 正向后行断言:前面是特定内容
/(?<=\$)\d+/.exec('$100')[0];  // '100'($符号后面的数字)

// 负向后行断言:前面不是特定内容
/(?<!\$)\d+/.exec('100')[0];   // '100'(前面不是$)

// 实战:提取货币符号后面的金额
'价格:$100, ¥200, €300'.match(/(?<=[$¥€])\d+/g);
// ['100', '200', '300']

---

七、常用正则表达式实战

1. 表单验证

// 手机号(中国大陆)
const isPhone = phone => /^1[3-9]\d{9}$/.test(phone);
console.log(isPhone('13812345678'));  // true
console.log(isPhone('12345678901'));  // false

// 电子邮箱
const isEmail = email => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
console.log(isEmail('user@example.com'));   // true
console.log(isEmail('user+tag@example.co.uk')); // true

// 身份证号(18位,简单验证)
const isIDCard = id => /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(id);
console.log(isIDCard('110101199003072418')); // true

// URL 验证
const isURL = url => /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/.test(url);

2. 文本处理

// 提取所有电话号码
const text = '联系电话:138-1234-5678 或 139-0000-1111';
text.match(/\d{3}-\d{4}-\d{4}/g);
// ['138-1234-5678', '139-0000-1111']

// 敏感词脱敏
'我的手机号是13812345678'.replace(/1[3-9]\d{9}/g, '***-****-****');
// '我的手机号是***-****-****'

// 去除多余空格
'  多个   空格  这里  '.replace(/\s+/g, ' ').trim();
// '多个 空格 这里'

// 驼峰转下划线
'firstName'.replace(/([A-Z])/g, '_$1').toLowerCase();
// 'first_name'

// 下划线转驼峰
'first_name'.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
// 'firstName'

3. 数据提取

// 提取 JSON 中的 key
const jsonStr = '{"name": "张三", "age": 28, "city": "北京"}';
jsonStr.match(/"([^"]+)":/g);
// ['"name":', '"age":', '"city":']

// 从 HTML 中提取所有链接
const html = '<a href="https://example.com">链接</a> and <a href="/path">内部链接</a>';
html.match(/href="([^"]+)"/g);
// ['href="https://example.com"', 'href="/path"']

---

八、JavaScript 正则方法

1. <code>RegExp</code> 方法

const pattern = /^1[3-9]\d{9}$/;

// test():返回布尔值(适合快速验证)
pattern.test('13812345678'); // true

// exec():返回匹配信息数组(包含索引和捕获组)
const result = /\d{4}-(\d{2})-(\d{2})/.exec('2026-07-26');
// result[0] = '2026-07-26', result[1] = '07', result[2] = '26'
// result.index = 0, result.input = '2026-07-26'

2. <code>String</code> 方法配合正则

const str = 'JavaScript 和 javascript 是不同的。';

// search():返回匹配位置(无匹配返回 -1)
str.search(/javascript/i); // 0(忽略大小写)

// match():返回匹配数组
str.match(/javascript/gi); // ['JavaScript', 'javascript']

// replace():替换(支持正则和回调函数)
str.replace(/javascript/gi, 'JS');
// 'JS 和 JS 是不同的。'

// split():用正则分割字符串
'2026年7月26日'.split(/[年日月]/); // ['2026', '7', '26', '']

3. 标志位(Flags)

| 标志 | 含义 | 示例 |
|------|------|------|
| g | 全局匹配 | /\d/g.exec('a1b2') 返回所有匹配 |
| i | 忽略大小写 | /abc/i 匹配 ABC |
| m | 多行模式 | ^$ 匹配每行首尾 |
| s | dotAll 模式 | . 匹配换行符 |
| u | Unicode 模式 | 支持 Unicode 完整匹配 |
| y | 粘性匹配 | 从上一次匹配位置继续 |

// 提取所有数字(全局匹配)
'订单A100和订单B200'.match(/\d+/g); // ['100', '200']

// 多行模式
const multiline = `第一行
第二行
第三行`;
multiline.match(/^\w+$/gm); // ['第一行', '第二行', '第三行']

// 粘性模式:每次匹配必须从上次结束位置开始
const pattern = /\d+/y;
const str = 'a1b2';
console.log(pattern.exec(str)); // ['1'], lastIndex = 2
console.log(pattern.exec(str)); // ['2'], lastIndex = 4
console.log(pattern.exec(str)); // null(不是从 lastIndex 开始)

---

九、运行效果

// 综合演示
const testCases = [
  { pattern: /^1[3-9]\d{9}$/, input: '13812345678', desc: '手机号' },
  { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, input: 'test@example.com', desc: '邮箱' },
  { pattern: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/, input: 'Pass1234', desc: '强密码' },
  { pattern: /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/, input: 'https://shaoda.tech', desc: 'URL' },
];

testCases.forEach(({ pattern, input, desc }) => {
  console.log(`${desc}: "${input}" → ${pattern.test(input) ? '✓通过' : '✗失败'}`);
});

// 输出:
// 手机号: "13812345678" → ✓通过
// 邮箱: "test@example.com" → ✓通过
// 强密码: "Pass1234" → ✓通过
// URL: "https://shaoda.tech" → ✓通过

---

十、常见问题

Q1: 为什么 <code>\d</code> 匹配不了中文数字?

\d 只匹配 ASCII 数字字符 0-9,不匹配全角数字(如 123)或中文数字(如 一二三)。如果需要匹配更广义的数字:

// 匹配各种数字格式
/[\d0-9]+/.test('123');  // 匹配全角数字
/\d/.test('一二三');          // false,中文数字需要单独处理

Q2: 正则表达式性能问题

避免灾难性回溯(Catastrophic Backtracking):

// 危险:有嵌套量词时可能导致性能问题
/a+b+c+/.test('aaaabbbccc'); // OK
// /([^])+/.test(...) 在长字符串上可能极慢

// 优化:用更精确的字符类代替 .*
// 避免:/(.*?)(\d+)(.*)/  改为 /([^\d]*)(\d+)(.*)/

Q3: <code>g</code> 标志下 lastIndex 的坑

const pattern = /\d/y; // 粘性模式
pattern.lastIndex = 0;
console.log(pattern.exec('a1')); // null(a不是数字,从0开始匹配失败)
console.log(pattern.lastIndex);  // 0(未改变)

// 正确用法:手动推进 lastIndex
let result, count = 0;
while ((result = /\d/g.exec('a1b2c3')) !== null) {
  count++;
  console.log(`匹配${count}: ${result[0]} at ${result.index}`);
}
// 匹配1: 1 at 1
// 匹配2: 2 at 3
// 匹配3: 3 at 5

Q4: Unicode 问题

// 不带 u 标志:只能处理基本多语言平面字符
/^.+$/.test('𝌆');  // false(处理不了 emoji 和辅助平面字符)

// 带 u 标志:正确处理所有 Unicode 字符
/^.+$/u.test('𝌆'); // true

// 使用 u 标志后,\p{} Unicode 属性逃脱就可用
/\p{Emoji}/u.test('😀');  // true
/\p{Lowercase_Letter}/u.test('a'); // true

Q5: 如何调试复杂正则?

  • <a href="https://regexr.com/">RegExr</a>:可视化正则表达式构建和测试工具
  • <a href="https://regex101.com/">regex101</a>:支持多种语言正则实时测试
  • Chrome DevTools:<code>console.log</code> 配合 <code>JSON.stringify</code> 查看 <code>exec()</code> 返回的完整信息

---

十一、延伸阅读

  • <strong>MDN 正则表达式文档</strong>:<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_expressions">Regular Expressions</a> — JavaScript 正则最权威的官方参考
  • <strong>正则表达式可视化</strong>:<a href="https://jex.im/regulex/">Regulex</a> — 用图形化方式展示正则表达式结构
  • <strong>在线正则测试</strong>:<a href="https://regex101.com/">regex101</a> — 支持 JavaScript/PCRE 双模式,带详细解释
  • <strong>书籍推荐</strong>:《精通正则表达式(第3版)》Jeffrey Friedl 著 — 正则表达式领域的圣经,虽以 Perl 为主但原理通用
  • <strong>阮一峰正则教程</strong>:<a href="https://wangdoc.com/javascript/grammar/regexp.html">《JavaScript 标准参考教程 - RegExp》</a> — 中文入门佳作