正则表达式实战指南:从入门到精通
正则表达式(Regular Expression,简称 RegEx)是程序员必备的技能之一。它像一把瑞士军刀,能够优雅地解决各种文本匹配、搜索和替换问题。本文将通过大量实例,帮助您从零开始掌握正则表达式。
为什么要学正则表达式?
强大的文本处理能力
一行正则表达式能完成几十行代码才能实现的功能:
// ❌ 不用正则:验证邮箱地址(复杂且不完整) function isEmailWithoutRegex(str) { if (!str.includes('@')) return false; const parts = str.split('@'); if (parts.length !== 2) return false; if (parts[0].length === 0 || parts[1].length === 0) return false; if (!parts[1].includes('.')) return false; // ... 还有很多条件要检查 return true; } // ✅ 使用正则:一行搞定 const isEmail = (str) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
实际应用场景
- 📧 表单验证:邮箱、手机号、身份证
- 🔍 数据提取:从文本中提取 URL、日期、价格
- 🔄 文本替换:批量修改代码、格式化数据
- 📊 日志分析:提取关键信息、统计数据
- 🛠️ 代码重构:批量重命名变量、更新API调用
正则表达式基础
基本语法
1. 字符匹配
// 精确匹配 /hello/.test("hello world") // true /hello/.test("Hello world") // false (区分大小写) // 不区分大小写 /hello/i.test("Hello world") // true // 匹配任意字符(除换行符) /./.test("a") // true /./.test("1") // true /./.test("\n") // false
2. 字符类
// [abc] - 匹配 a、b 或 c /[aeiou]/.test("hello") // true (匹配到 'e') // [^abc] - 不匹配 a、b、c /[^aeiou]/.test("hello") // true (匹配到 'h') // [a-z] - 范围匹配 /[a-z]/.test("Hello") // true (匹配到 'e') /[A-Z]/.test("Hello") // true (匹配到 'H') /[0-9]/.test("abc123") // true (匹配到 '1') // 组合使用 /[a-zA-Z0-9]/.test("Hello123") // true
3. 预定义字符类
| 符号 | 等价于 | 含义 |
|---|---|---|
\d | [0-9] | 任意数字 |
\D | [^0-9] | 任意非数字 |
\w | [a-zA-Z0-9_] | 字母、数字、下划线 |
\W | [^a-zA-Z0-9_] | 非单词字符 |
\s | [ \t\n\r\f\v] | 空白字符 |
\S | [^ \t\n\r\f\v] | 非空白字符 |
// 实例 /\d/.test("abc123") // true /\d+/.test("abc123") // true (匹配 "123") /\w+/.test("hello_123") // true (匹配整个字符串) /\s/.test("hello world") // true (匹配空格)
4. 量词
| 量词 | 含义 | 示例 |
|---|---|---|
* | 0次或多次 | /a*/ 匹配 "", "a", "aa" |
+ | 1次或多次 | /a+/ 匹配 "a", "aa",不匹配 "" |
? | 0次或1次 | /a?/ 匹配 "", "a" |
{n} | 恰好n次 | /a{3}/ 匹配 "aaa" |
{n,} | 至少n次 | /a{2,}/ 匹配 "aa", "aaa" |
{n,m} | n到m次 | /a{2,4}/ 匹配 "aa", "aaa", "aaaa" |
// 实例 /\d{3}/.test("123") // true /\d{3}/.test("12") // false /\d{3,}/.test("12345") // true /[a-z]{2,5}/.test("hello") // true
5. 位置匹配
// ^ - 开头 /^hello/.test("hello world") // true /^hello/.test("say hello") // false // $ - 结尾 /world$/.test("hello world") // true /world$/.test("world hello") // false // \b - 单词边界 /\bcat\b/.test("cat") // true /\bcat\b/.test("category") // false /\bcat\b/.test("the cat sat") // true // 组合使用:完整匹配 /^\d{3}$/.test("123") // true /^\d{3}$/.test("1234") // false
实战案例
案例 1:验证手机号
// 中国大陆手机号:1开头,第二位3-9,共11位 const phoneRegex = /^1[3-9]\d{9}$/; phoneRegex.test('13812345678'); // true phoneRegex.test('12812345678'); // false (第二位是2) phoneRegex.test('138123456789'); // false (12位) // 更严格:指定运营商 const mobileRegex = /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
案例 2:验证邮箱
// 基础版本 const basicEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; // 更严格的版本 const strictEmail = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; strictEmail.test('user@example.com'); // true strictEmail.test('user.name@example.com'); // true strictEmail.test('user@sub.example.com'); // true strictEmail.test('user@example'); // false strictEmail.test('user name@example.com'); // false
案例 3:提取 URL
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g; const text = '访问 https://toolforge.com 和 http://example.com 查看更多'; const urls = text.match(urlRegex); console.log(urls); // ["https://toolforge.com", "http://example.com"]
案例 4:密码强度验证
// 至少8位,包含大小写字母、数字和特殊字符 function validatePassword(password) { const minLength = /.{8,}/; // 至少8位 const hasUpper = /[A-Z]/; // 包含大写 const hasLower = /[a-z]/; // 包含小写 const hasNumber = /\d/; // 包含数字 const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/; // 包含特殊字符 return ( minLength.test(password) && hasUpper.test(password) && hasLower.test(password) && hasNumber.test(password) && hasSpecial.test(password) ); } validatePassword('Abc123!@'); // true validatePassword('abc123'); // false (无大写和特殊字符) validatePassword('Abcdefgh'); // false (无数字和特殊字符) // 一个正则实现(使用前瞻) const strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
案例 5:身份证号验证
// 中国大陆身份证:18位 // 前6位地区码 + 8位生日 + 3位顺序码 + 1位校验码 const idCardRegex = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/; idCardRegex.test('110101199001011234'); // true idCardRegex.test('11010119900101123X'); // true idCardRegex.test('110101199013011234'); // false (13月)
案例 6:价格提取
// 提取文本中的价格 const priceRegex = /¥?\s?\d+(\.\d{2})?/g; const text = '商品A:¥99.99,商品B:¥199,商品C:59.90元'; const prices = text.match(priceRegex); console.log(prices); // ["¥99.99", "¥199", "59.90"] // 更精确:带货币符号 const cnPriceRegex = /¥\s?\d+(?:\.\d{2})?/g;
案例 7:日期格式化
// 匹配 YYYY-MM-DD 格式 const dateRegex = /^(\d{4})-(\d{2})-(\d{2})$/; const match = '2026-01-03'.match(dateRegex); if (match) { const [, year, month, day] = match; console.log(`年:${year},月:${month},日:${day}`); } // 转换为 MM/DD/YYYY const convertDate = (dateStr) => { return dateStr.replace(/^(\d{4})-(\d{2})-(\d{2})$/, '$2/$3/$1'); }; convertDate('2026-01-03'); // "01/03/2026"
案例 8:HTML 标签移除
// 移除所有 HTML 标签 const stripHtml = (html) => html.replace(/<[^>]*>/g, ''); stripHtml('<p>Hello <strong>World</strong></p>'); // "Hello World" // 移除特定标签,保留内容 const removeTag = (html, tag) => { const regex = new RegExp(`<${tag}[^>]*>|</${tag}>`, 'gi'); return html.replace(regex, ''); }; removeTag('<p>Hello <strong>World</strong></p>', 'strong'); // "<p>Hello World</p>"
高级技巧
1. 捕获组
// 普通捕获组 () const nameRegex = /(\w+)\s(\w+)/; const match = 'John Doe'.match(nameRegex); console.log(match[1]); // "John" (第一个捕获组) console.log(match[2]); // "Doe" (第二个捕获组) // 命名捕获组 (?<name>) const nameRegex2 = /(?<first>\w+)\s(?<last>\w+)/; const match2 = 'John Doe'.match(nameRegex2); console.log(match2.groups.first); // "John" console.log(match2.groups.last); // "Doe"
2. 非捕获组
// (?:...) - 分组但不捕获 const urlRegex = /https?:\/\/(?:www\.)?example\.com/; 'https://www.example.com'.match(urlRegex); // 匹配但不捕获 www.
3. 前瞻和后顾
// 正向前瞻 (?=...) // 匹配后面是数字的单词 /\w+(?=\d)/.exec("abc123") // "abc" // 负向前瞻 (?!...) // 匹配后面不是数字的单词 /\w+(?!\d)/.exec("abc def") // "abc" // 正向后顾 (?<=...) // 匹配前面是 $ 的数字 /(?<=\$)\d+/.exec("$100") // "100" // 负向后顾 (?<!...) // 匹配前面不是 $ 的数字 /(?<!\$)\d+/.exec("100") // "100"
4. 贪婪 vs 非贪婪
// 贪婪模式(默认):匹配尽可能多 /<.*>/.exec("<div>Hello</div>") // "<div>Hello</div>" // 非贪婪模式:加 ? 匹配尽可能少 /<.*?>/.exec("<div>Hello</div>") // "<div>" // 实际应用:提取 HTML 标签内容 const extractTags = (html) => { return html.match(/<(\w+)>.*?<\/\1>/g); }; extractTags("<p>Text1</p><div>Text2</div>") // ["<p>Text1</p>", "<div>Text2</div>"]
5. 反向引用
// \1 引用第一个捕获组 const duplicateRegex = /(\w+)\s+\1/; duplicateRegex.test('hello hello'); // true duplicateRegex.test('hello world'); // false // 实际应用:查找重复单词 const text = 'the the cat sat on the mat mat'; const duplicates = text.match(/\b(\w+)\s+\1\b/g); console.log(duplicates); // ["the the", "mat mat"]
性能优化
1. 避免回溯
// ❌ 可能导致灾难性回溯 const badRegex = /(a+)+b/; badRegex.test('aaaaaaaaaaaaaaaaaaaaac'); // 可能卡死 // ✅ 优化版本 const goodRegex = /a+b/;
2. 使用具体字符类
// ❌ 性能较差 const slow = /.*@.*/; // ✅ 性能更好 const fast = /[^@]+@[^@]+/;
3. 提前锚定
// ❌ 需要扫描整个字符串 const slow = /\d{3}/; // ✅ 明确位置,更快 const fast = /^\d{3}$/;
常见错误和陷阱
错误 1:忘记转义特殊字符
// ❌ 错误:. 匹配任意字符 /example.com/.test("exampleXcom") // true (不期望的) // ✅ 正确:转义 . /example\.com/.test("example.com") // true
错误 2:不恰当的全局标志
const regex = /\d+/g; // 第一次调用 console.log(regex.test('123')); // true console.log(regex.lastIndex); // 3 // 第二次调用(从上次位置继续) console.log(regex.test('123')); // false! console.log(regex.lastIndex); // 0 (重置了) // ✅ 每次创建新正则 const test = (str) => /\d+/.test(str);
错误 3:字符类中的特殊规则
// 在 [] 中,大部分特殊字符失去意义 /[.]/.test(".") // true (. 不需要转义) /[*]/.test("*") // true (* 不需要转义) // 但有例外 /[\]]/.test("]") // true (] 需要转义) /[^abc]/.test("d") // true (^ 在开头有特殊含义)
调试技巧
1. 使用在线工具
推荐工具:
- ToolsForge 正则测试器
- Regex101.com
- RegExr.com
2. 分步构建
// 从简单开始 let regex = /\d/; // 匹配数字 regex = /\d{3}/; // 3个数字 regex = /\d{3}-\d{4}/; // 添加分隔符 regex = /^\d{3}-\d{4}$/; // 完整匹配 // 最终:美国邮编 const zipCode = /^\d{5}(-\d{4})?$/;
3. 使用注释(verbose 模式)
// JavaScript 不支持 verbose 模式,但可以拼接 const complexRegex = new RegExp( '^' + // 开始 '(?=.*[a-z])' + // 至少一个小写 '(?=.*[A-Z])' + // 至少一个大写 '(?=.*\\d)' + // 至少一个数字 '.{8,}' + // 至少8个字符 '$' // 结束 );
实用代码片段
验证工具函数
const validators = { // 邮箱 email: (str) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str), // 手机号(中国) phone: (str) => /^1[3-9]\d{9}$/.test(str), // URL url: (str) => /^https?:\/\/.+/.test(str), // IPv4 ipv4: (str) => /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/.test(str), // 十六进制颜色 hexColor: (str) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(str), // 用户名(字母数字下划线,4-16位) username: (str) => /^[a-zA-Z0-9_]{4,16}$/.test(str), }; // 使用 console.log(validators.email('test@example.com')); // true console.log(validators.phone('13812345678')); // true
文本处理工具
const textUtils = { // 驼峰转下划线 camelToSnake: (str) => str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`), // 下划线转驼峰 snakeToCamel: (str) => str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()), // 移除多余空格 trimSpaces: (str) => str.replace(/\s+/g, ' ').trim(), // 提取数字 extractNumbers: (str) => str.match(/\d+/g) || [], // 高亮关键词 highlight: (text, keyword) => { const regex = new RegExp(`(${keyword})`, 'gi'); return text.replace(regex, '<mark>$1</mark>'); }, }; // 使用 console.log(textUtils.camelToSnake('userName')); // "user_name" console.log(textUtils.snakeToCamel('user_name')); // "userName" console.log(textUtils.extractNumbers('abc123xyz456')); // ["123", "456"]
总结
正则表达式是强大的工具,但也需要谨慎使用:
✅ 何时使用正则
- 文本模式匹配
- 数据验证
- 文本搜索替换
- 数据提取
❌ 何时避免使用
- 解析 HTML/XML(用专门的解析器)
- 复杂的语法分析
- 性能关键的代码
- 可读性要求高的场景
学习建议
- 从基础开始,逐步深入
- 多写多练,积累经验
- 使用在线工具调试
- 收藏常用正则表达式
- 注意性能和可读性
需要测试正则表达式?试试我们的 正则表达式测试器!
相关阅读:
相关阅读
其他
正则表达式完全教程:从入门到精通
系统学习正则表达式,从基础语法到高级应用,包含大量实战案例和最佳实践,帮助开发者快速掌握这一强大的文本处理工具
2026-01-04
开发教程
JSON 格式化完全指南:从入门到精通
深入了解 JSON 格式化的原理、最佳实践和实用技巧,帮助开发者高效处理 JSON 数据
2026-01-03
其他
TypeScript 类型体操:什么时候值,什么时候在炫技
务实的 TypeScript 高级类型指南 — mapped types、conditional types、template literal types 真正能给你什么,什么时候用,什么时候应该退回到朴素代码。
2026-05-21