我将为您完善正则表达式语法详解的文档,包括各种语法元素、模式标志和实际应用示例。
---
title: 正则表达式语法详解
icon: javascript
order: 1
---
# 正则表达式语法详解
正则表达式有自己的语法规则,包括字符类、量词、分组、断言等。本文将详细介绍JavaScript正则表达式的语法元素和模式标志,帮助您构建有效的正则表达式。
## 创建正则表达式
在JavaScript中,有两种方式创建正则表达式:
### 1. 正则表达式字面量
使用斜杠 `/` 包围模式:
```javascript
// 基本语法:/pattern/flags
const regex1 = /hello/;
const regex2 = /world/i; // 带有忽略大小写标志
2. RegExp构造函数
// 基本语法:new RegExp(pattern[, flags])
const regex1 = new RegExp('hello');
const regex2 = new RegExp('world', 'i');
// 当模式需要动态生成时特别有用
const searchTerm = 'dynamic';
const regex3 = new RegExp(searchTerm, 'g');
基本字符匹配
普通字符
大多数字符(字母、数字)在正则表达式中表示它们自身:
const regex = /hello/;
console.log(regex.test('hello world')); // true
console.log(regex.test('Hello world')); // false (区分大小写)
特殊字符
某些字符在正则表达式中有特殊含义,如果要匹配这些字符本身,需要使用反斜杠 \
进行转义:
// 特殊字符: . * + ? ^ $ \ | ( ) [ ] { }
const regex = /\./; // 匹配句点字符
console.log(regex.test('hello.')); // true
console.log(regex.test('hello')); // false
通配符
.
点字符匹配除换行符外的任何单个字符:
const regex = /h.llo/;
console.log(regex.test('hello')); // true
console.log(regex.test('hallo')); // true
console.log(regex.test('h-llo')); // true
console.log(regex.test('hllo')); // false (需要一个字符)
字符类
字符类允许匹配一组字符中的任意一个。
方括号表示法 [...]
const regex = /[aeiou]/; // 匹配任何一个小写元音字母
console.log(regex.test('apple')); // true
console.log(regex.test('sky')); // false
// 范围表示法
const digitRegex = /[0-9]/; // 匹配任何一个数字
console.log(digitRegex.test('abc123')); // true
console.log(digitRegex.test('abc')); // false
// 组合多个范围
const alphaNumRegex = /[a-zA-Z0-9]/; // 匹配任何字母或数字
否定字符类 [^...]
匹配不在指定集合中的任何字符:
const regex = /[^aeiou]/; // 匹配任何非小写元音字母的字符
console.log(regex.test('a')); // false
console.log(regex.test('b')); // true
console.log(regex.test('abc')); // true (包含非元音字母)
预定义字符类
JavaScript提供了一些常用字符类的简写形式:
// \d - 匹配任何数字,等价于[0-9]
console.log(/\d/.test('abc123')); // true
// \D - 匹配任何非数字,等价于[^0-9]
console.log(/\D/.test('123')); // false
console.log(/\D/.test('abc123')); // true
// \w - 匹配任何字母、数字或下划线,等价于[a-zA-Z0-9_]
console.log(/\w/.test('abc_123')); // true
console.log(/\w/.test('!@#')); // false
// \W - 匹配任何非字母、数字或下划线,等价于[^a-zA-Z0-9_]
console.log(/\W/.test('abc_123')); // false
console.log(/\W/.test('abc!123')); // true
// \s - 匹配任何空白字符(空格、制表符、换行符等)
console.log(/\s/.test('hello world')); // true
console.log(/\s/.test('hello')); // false
// \S - 匹配任何非空白字符
console.log(/\S/.test(' \t\n')); // false
console.log(/\S/.test(' a ')); // true
量词
量词用于指定匹配模式的重复次数。
基本量词
// * - 匹配前面的模式零次或多次
console.log(/a*/.test('')); // true
console.log(/a*/.test('a')); // true
console.log(/a*/.test('aaa')); // true
// + - 匹配前面的模式一次或多次
console.log(/a+/.test('')); // false
console.log(/a+/.test('a')); // true
console.log(/a+/.test('aaa')); // true
// ? - 匹配前面的模式零次或一次
console.log(/a?/.test('')); // true
console.log(/a?/.test('a')); // true
console.log(/a?/.test('aa')); // true (匹配第一个'a',第二个不匹配)
精确量词
// {n} - 精确匹配n次
console.log(/a{3}/.test('aa')); // false
console.log(/a{3}/.test('aaa')); // true
console.log(/a{3}/.test('aaaa')); // true (匹配前三个'a')
// {n,} - 匹配至少n次
console.log(/a{2,}/.test('a')); // false
console.log(/a{2,}/.test('aa')); // true
console.log(/a{2,}/.test('aaa')); // true
// {n,m} - 匹配至少n次,最多m次
console.log(/a{1,3}/.test('')); // false
console.log(/a{1,3}/.test('a')); // true
console.log(/a{1,3}/.test('aaa')); // true
console.log(/a{1,3}/.test('aaaa')); // true (匹配前三个'a')
贪婪与非贪婪匹配
默认情况下,量词是贪婪的,会尽可能多地匹配字符:
// 贪婪匹配
const greedyRegex = /<.*>/;
console.log(greedyRegex.exec('<h1>Title</h1>'));
// 匹配整个 "<h1>Title</h1>"
// 非贪婪匹配 (使用 ? 后缀)
const nonGreedyRegex = /<.*?>/;
console.log(nonGreedyRegex.exec('<h1>Title</h1>'));
// 只匹配 "<h1>"
分组和捕获
捕获组 (...)
圆括号用于创建捕获组,可以提取匹配的子字符串:
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const match = regex.exec('2023-05-15');
console.log(match[0]); // 完整匹配: "2023-05-15"
console.log(match[1]); // 第一个捕获组: "2023" (年)
console.log(match[2]); // 第二个捕获组: "05" (月)
console.log(match[3]); // 第三个捕获组: "15" (日)
非捕获组 (?:...)
当只需要分组但不需要捕获结果时使用:
const regex = /(?:\d{4})-(\d{2})-(\d{2})/;
const match = regex.exec('2023-05-15');
console.log(match[0]); // 完整匹配: "2023-05-15"
console.log(match[1]); // 第一个捕获组: "05" (月)
console.log(match[2]); // 第二个捕获组: "15" (日)
// 年份部分不会被捕获
命名捕获组 (?<name>...)
ES2018引入了命名捕获组,可以通过名称而不是索引访问捕获的内容:
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = regex.exec('2023-05-15');
console.log(match.groups.year); // "2023"
console.log(match.groups.month); // "05"
console.log(match.groups.day); // "15"
反向引用
在正则表达式内部引用之前的捕获组:
// 使用数字引用
const regex1 = /(\w+)\s+\1/; // 匹配重复的单词
console.log(regex1.test('hello hello')); // true
console.log(regex1.test('hello world')); // false
// 使用命名引用 (ES2018+)
const regex2 = /(?<word>\w+)\s+\k<word>/;
console.log(regex2.test('hello hello')); // true
边界匹配
边界匹配器不匹配字符,而是匹配位置。
// ^ - 匹配字符串开头
console.log(/^hello/.test('hello world')); // true
console.log(/^hello/.test('say hello')); // false
// $ - 匹配字符串结尾
console.log(/world$/.test('hello world')); // true
console.log(/world$/.test('world tour')); // false
// \b - 匹配单词边界
console.log(/\bcat\b/.test('the cat sat')); // true
console.log(/\bcat\b/.test('category')); // false
console.log(/\bcat\b/.test('the concatenate')); // false
// \B - 匹配非单词边界
console.log(/\Bcat\B/.test('the cat sat')); // false
console.log(/\Bcat\B/.test('category')); // false
console.log(/\Bcat\B/.test('concatenate')); // true (在单词中间)
前瞻和后顾断言
断言匹配某个位置前面或后面的内容,但不消耗字符。
前瞻断言
// 正向前瞻 (?=...) - 匹配后面跟着指定模式的位置
console.log(/\d+(?=%)/.exec('100% complete')); // ["100"]
console.log(/\d+(?=%)/.exec('100 dollars')); // null
// 负向前瞻 (?!...) - 匹配后面不跟着指定模式的位置
console.log(/\d+(?!%)/.exec('100% complete')); // null (因为100后面是%)
console.log(/\d+(?!%)/.exec('100 dollars')); // ["100"]
后顾断言 (ES2018+)
// 正向后顾 (?<=...) - 匹配前面有指定模式的位置
console.log(/(?<=\$)\d+/.exec('Price: $100')); // ["100"]
console.log(/(?<=\$)\d+/.exec('Price: 100')); // null
// 负向后顾 (?<!...) - 匹配前面没有指定模式的位置
console.log(/(?<!\$)\d+/.exec('Price: $100')); // null
console.log(/(?<!\$)\d+/.exec('Price: 100')); // ["100"]
模式标志
正则表达式可以有一个或多个标志,用于控制匹配行为:
// i - 忽略大小写
console.log(/hello/i.test('Hello')); // true
// g - 全局搜索,查找所有匹配项而非在第一个匹配后停止
const regex1 = /a/g;
const str = 'banana';
console.log(str.match(regex1)); // ["a", "a", "a"]
// m - 多行模式,^和$匹配每一行的开头和结尾
const multiline = 'First line\nSecond line';
console.log(/^Second/.test(multiline)); // false
console.log(/^Second/m.test(multiline)); // true
// s - dotAll模式,允许.匹配换行符 (ES2018+)
console.log(/./.test('\n')); // false
console.log(/./s.test('\n')); // true
// u - Unicode模式,正确处理Unicode字符 (ES6+)
console.log(/\u{1F600}/u.test('😀')); // true
// y - 粘性搜索,只从正则表达式的lastIndex属性指定的位置开始匹配 (ES6+)
const regex2 = /abc/y;
regex2.lastIndex = 4;
console.log(regex2.test('xxxx abc')); // false (因为从索引4开始,但"abc"在索引5)
常用正则表达式模式
1. 验证电子邮件
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailRegex.test('user@example.com')); // true
console.log(emailRegex.test('invalid-email')); // false
2. 验证URL
const urlRegex = /^(https?:\/\/)?(www\.)?[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/[^\s]*)?$/;
console.log(urlRegex.test('https://example.com')); // true
console.log(urlRegex.test('www.example.com')); // true
console.log(urlRegex.test('example.com/path?query=1')); // true
console.log(urlRegex.test('invalid url')); // false
3. 验证手机号码(中国)
const phoneRegex = /^1[3-9]\d{9}$/;
console.log(phoneRegex.test('13812345678')); // true
console.log(phoneRegex.test('12345678901')); // false
4. 验证身份证号(中国)
// 简化版,不包含校验码验证
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}[\dX]$/;
console.log(idCardRegex.test('110101199001011234')); // true
console.log(idCardRegex.test('11010119900101123')); // false
5. 提取HTML标签
const htmlTagRegex = /<([a-z]+)([^<]+)*(?:>(.*?)<\/\1>|\s+\/>)/gi;
const html = '<div class="container">Content</div><img src="image.jpg" />';
console.log(Array.from(html.matchAll(htmlTagRegex)));
// 输出包含匹配的标签信息
6. 密码强度验证
// 至少8位,包含大小写字母、数字和特殊字符
const strongPasswordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$/;
console.log(strongPasswordRegex.test('Passw0rd!')); // true
console.log(strongPasswordRegex.test('password')); // false
7. 日期格式验证
// YYYY-MM-DD 格式
const dateRegex = /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
console.log(dateRegex.test('2023-05-15')); // true
console.log(dateRegex.test('2023-13-15')); // false (月份无效)
8. IP地址验证
// IPv4地址
const ipv4Regex = /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){2}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
console.log(ipv4Regex.test('192.168.1.1')); // true
console.log(ipv4Regex.test('256.168.1.1')); // false
正则表达式方法
JavaScript提供了多种使用正则表达式的方法:
RegExp对象方法
const regex = /hello/i;
const str = 'Hello, world!';
// test() - 检查字符串是否匹配模式
console.log(regex.test(str)); // true
// exec() - 执行搜索匹配,返回结果数组或null
const result = regex.exec(str);
console.log(result); // ["Hello", index: 0, input: "Hello, world!", groups: undefined]
String对象方法
const str = 'Hello, world! Hello, JavaScript!';
const regex = /hello/gi;
// match() - 返回匹配结果数组
console.log(str.match(regex)); // ["Hello", "Hello"]
// matchAll() - 返回所有匹配结果的迭代器 (ES2020+)
const matches = [...str.matchAll(regex)];
console.log(matches); // 包含详细匹配信息的数组
// search() - 返回第一个匹配的索引,没有则返回-1
console.log(str.search(/world/i)); // 7
// replace() - 替换匹配的文本
console.log(str.replace(regex, 'Hi')); // "Hi, world! Hi, JavaScript!"
// replaceAll() - 替换所有匹配的文本 (ES2021+)
console.log(str.replaceAll(regex, 'Hi')); // "Hi, world! Hi, JavaScript!"
// split() - 使用正则表达式分割字符串
console.log('a,b;c|d'.split(/[,;|]/)); // ["a", "b", "c", "d"]
使用捕获组进行替换
// 交换名字和姓氏的顺序
const name = 'Smith, John';
const swapped = name.replace(/(\w+), (\w+)/, '$2 $1');
console.log(swapped); // "John Smith"
// 使用命名捕获组
const date = '2023-05-15';
const formatted = date.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/, '$<month>/$<day>/$<year>');
console.log(formatted); // "05/15/2023"
使用函数进行替换
// 将数字转换为其平方
const numbers = 'Numbers: 1, 4, 9';
const squared = numbers.replace(/\d+/g, match => Math.pow(parseInt(match), 2));
console.log(squared); // "Numbers: 1, 16, 81"
// 将日期格式从YYYY-MM-DD转换为DD/MM/YYYY
const dates = 'Dates: 2023-05-15, 2023-06-20';
const formattedDates = dates.replace(/(\d{4})-(\d{2})-(\d{2})/g, (match, year, month, day) => {
return `${day}/${month}/${year}`;
});
console.log(formattedDates); // "Dates: 15/05/2023, 20/06/2023"
正则表达式性能优化
1. 避免过度使用贪婪量词
// 低效的正则表达式
const inefficientRegex = /<.*>/;
// 更高效的正则表达式
const efficientRegex = /<[^>]*>/;
2. 避免不必要的捕获组
// 使用非捕获组提高性能
const betterRegex = /(?:https?:\/\/)(?:www\.)?example\.com/;
3. 使用适当的锚点
// 使用^和$可以提前结束不匹配的情况
const anchoredRegex = /^prefix/;
// 如果字符串不以"prefix"开头,匹配会立即失败
4. 避免回溯过多
// 可能导致灾难性回溯的正则表达式
const badRegex = /^(a+)+$/;
// 对于输入"aaaaX",会导致指数级回溯
// 更好的替代方案
const goodRegex = /^a+$/;
5. 使用适当的字符类
// 低效
const inefficient = /[0123456789]/;
// 高效
const efficient = /\d/;
正则表达式调试技巧
1. 使用在线工具
有许多在线工具可以帮助可视化和测试正则表达式,如Regex101、RegExr等。
2. 分步构建
// 从简单开始,逐步添加复杂性
let regex = /\d+/; // 匹配数字
regex = /\d{4}/; // 匹配四位数字
regex = /\d{4}-\d{2}/; // 匹配年份-月份
regex = /\d{4}-\d{2}-\d{2}/; // 匹配完整日期
3. 使用注释和命名捕获组
// 使用x标志允许在正则表达式中添加注释和空白 (ES2018+)
const dateRegex = /(?<year>\d{4})- # 年份
(?<month>\d{2})- # 月份
(?<day>\d{2}) # 日期
/x;
常见陷阱和解决方案
1. 特殊字符转义
// 错误:点字符在正则表达式中是特殊字符
const wrongRegex = /example.com/; // 会匹配"exampleXcom"
// 正确:转义点字符
const correctRegex = /example\.com/;
2. Unicode字符处理
// 不使用u标志时可能无法正确处理Unicode
console.log(/^.$/.test('😀')); // false
// 使用u标志正确处理Unicode
console.log(/^.$/u.test('😀')); // true
3. 处理多行文本
// 不使用m标志时^和$只匹配整个字符串的开始和结束
const multiline = 'Line 1\nLine 2';
console.log(/^Line \d$/.test(multiline)); // false
// 使用m标志匹配每行的开始和结束
console.log(/^Line \d$/m.test(multiline)); // true
4. 处理HTML
正则表达式不是解析HTML的理想工具,对于复杂的HTML处理,应使用专门的解析器。
// 简单的HTML提取可以使用正则表达式
const simpleHtml = '<div>Content</div>';
const content = simpleHtml.match(/<div>(.*?)<\/div>/)[1];
console.log(content); // "Content"
// 但对于复杂的HTML,应使用DOM API
// document.querySelector('div').textContent
练习
编写一个正则表达式,匹配有效的中国邮政编码(6位数字)。
创建一个正则表达式,从文本中提取所有的电话号码(假设格式为XXX-XXX-XXXX)。
编写一个函数,使用正则表达式验证用户名是否有效(只允许字母、数字和下划线,长度在6-20之间,必须以字母开头)。
创建一个正则表达式,匹配HTML中的所有图片标签,并提取src属性的值。
编写一个函数,使用正则表达式将驼峰命名法转换为下划线命名法(例如,"camelCase" → "camel_case")。
总结
正则表达式是处理文本的强大工具,掌握其语法和使用方法可以大大提高文本处理效率。本文详细介绍了JavaScript正则表达式的语法元素、模式标志和常用方法,并提供了实际应用示例和性能优化建议。
虽然正则表达式功能强大,但也应当谨慎使用,特别是在处理复杂文本结构(如HTML)或需要高性能的场景时。在这些情况下,可能需要考虑使用专门的解析器或其他更适合的工具。
通过练习和实践,您可以逐步提高正则表达式的编写和使用能力,使其成为您文本处理工具箱中的得力助手。