06-正则表达式详解
分类:01-JavaScript基础核心
发布于:
阅读时间:92 分钟
正则表达式详解
📋 学习目标
- 掌握正则表达式的基本语法和元字符
- 学会创建和使用正则表达式
- 理解正则表达式的匹配模式和标志
- 掌握常见正则表达式模式和应用场景
🎯 正则表达式基础
1. 什么是正则表达式?
正则表达式(Regular Expression,简称RegExp)是一种用于匹配字符串模式的强大工具。它使用特殊的字符序列来描述和匹配字符串的规则。
// 基本概念
const pattern = /hello/; // 字面量方式创建
const pattern2 = new RegExp('hello'); // 构造函数方式创建
const text = 'hello world';
console.log(pattern.test(text)); // true
2. 创建正则表达式
// 方法1:字面量(推荐)
const pattern1 = /ab+c/;
// 方法2:构造函数(动态模式)
const pattern2 = new RegExp('ab+c');
// 方法3:构造函数(从字符串创建正则)
const str = 'ab+c';
const pattern3 = new RegExp(str);
// 方法4:使用模板变量
const word = 'hello';
const pattern4 = new RegExp(`\\b${word}\\b`); // \bhello\b
3. 正则表达式的方法
const pattern = /hello/g;
const text = 'hello world, hello everyone';
// test() - 测试是否匹配
console.log(pattern.test(text)); // true
// exec() - 执行匹配并返回详细信息
let match = pattern.exec(text);
console.log(match);
// ['hello', index: 0, input: 'hello world, hello everyone', groups: undefined]
match = pattern.exec(text);
console.log(match);
// ['hello', index: 13, input: 'hello world, hello everyone', groups: undefined]
match = pattern.exec(text);
console.log(match); // null (没有更多匹配)
// 重置lastIndex
pattern.lastIndex = 0;
🔧 字符类和元字符
1. 基本字符类
// \d - 数字字符 (0-9)
const digitPattern = /\d/;
console.log(digitPattern.test('123')); // true
console.log(digitPattern.test('abc')); // false
// \D - 非数字字符
const nonDigitPattern = /\D/;
console.log(nonDigitPattern.test('123')); // false
console.log(nonDigitPattern.test('abc')); // true
// \w - 单词字符 (字母、数字、下划线)
const wordPattern = /\w/;
console.log(wordPattern.test('abc123')); // true
console.log(wordPattern.test('hello_world')); // true
console.log(wordPattern.test('hello-world')); // false
// \W - 非单词字符
const nonWordPattern = /\W/;
console.log(nonWordPattern.test('hello-world')); // true
console.log(nonWordPattern.test('hello_world')); // false
// \s - 空白字符 (空格、制表符、换行符等)
const spacePattern = /\s/;
console.log(spacePattern.test('hello world')); // true
console.log(spacePattern.test('helloworld')); // false
// \S - 非空白字符
const nonSpacePattern = /\S/;
console.log(nonSpacePattern.test('hello world')); // true (h是非空白)
console.log(nonSpacePattern.test(' ')); // false
// . - 任意字符(除换行符外)
const anyPattern = /./;
console.log(anyPattern.test('a')); // true
console.log(anyPattern.test('1')); // true
console.log(anyPattern.test('!')); // true
console.log(anyPattern.test('\n')); // false (默认情况下)
2. 自定义字符类
// [abc] - 匹配a、b或c中的任意一个
const abcPattern = /[abc]/;
console.log(abcPattern.test('apple')); // true (包含a)
console.log(abcPattern.test('banana')); // true (包含a)
console.log(abcPattern.test('xyz')); // false
// [a-z] - 匹配小写字母
const lowercasePattern = /[a-z]/;
console.log(lowercasePattern.test('Hello')); // false
console.log(lowercasePattern.test('hello')); // true
// [A-Z] - 匹配大写字母
const uppercasePattern = /[A-Z]/;
console.log(uppercasePattern.test('Hello')); // true
console.log(uppercasePattern.test('hello')); // false
// [0-9] - 匹配数字
const numberPattern = /[0-9]/;
console.log(numberPattern.test('abc123')); // true
console.log(numberPattern.test('abc')); // false
// [a-zA-Z0-9] - 匹配字母和数字
const alnumPattern = /[a-zA-Z0-9]/;
console.log(alnumPattern.test('abc123')); // true
console.log(alnumPattern.test('!@#')); // false
// [^abc] - 匹配除a、b、c外的任意字符
const notAbcPattern = /[^abc]/;
console.log(notAbcPattern.test('apple')); // true (包含p、l、e)
console.log(notAbcPattern.test('abc')); // false
3. 预定义字符类
// Unicode属性类(ES2018+)
const letterPattern = /\p{Letter}/u; // 任何语言的字母
const numberPattern = /\p{Number}/u; // 任何语言的数字
const chinesePattern = /\p{Script=Han}/u; // 汉字
console.log(chinesePattern.test('你好')); // true
console.log(chinesePattern.test('hello')); // false
// 更多Unicode属性
const emojiPattern = /\p{Emoji}/u; // 表情符号
const currencyPattern = /\p{Currency_Symbol}/u; // 货币符号
console.log(emojiPattern.test('😀')); // true
console.log(currencyPattern.test('$')); // true
📊 量词
1. 基本量词
// * - 0次或多次
const starPattern = /ab*/;
console.log(starPattern.test('a')); // true (b出现0次)
console.log(starPattern.test('ab')); // true (b出现1次)
console.log(starPattern.test('abbb')); // true (b出现3次)
// + - 1次或多次
const plusPattern = /ab+/;
console.log(plusPattern.test('a')); // false (b需要至少1次)
console.log(plusPattern.test('ab')); // true (b出现1次)
console.log(plusPattern.test('abbb')); // true (b出现3次)
// ? - 0次或1次
const questionPattern = /ab?/;
console.log(questionPattern.test('a')); // true (b出现0次)
console.log(questionPattern.test('ab')); // true (b出现1次)
console.log(questionPattern.test('abb')); // false (b出现2次)
2. 精确量词
// {n} - 恰好n次
const exactPattern = /ab{3}/;
console.log(exactPattern.test('ab')); // false
console.log(exactPattern.test('abbb')); // true
console.log(exactPattern.test('abbbb')); // false
// {n,} - 至少n次
const atLeastPattern = /ab{2,}/;
console.log(atLeastPattern.test('ab')); // false
console.log(atLeastPattern.test('abb')); // true
console.log(atLeastPattern.test('abbbb')); // true
// {n,m} - n到m次
const rangePattern = /ab{2,4}/;
console.log(rangePattern.test('ab')); // false
console.log(rangePattern.test('abb')); // true
console.log(rangePattern.test('abbb')); // true
console.log(rangePattern.test('abbbb')); // true
console.log(rangePattern.test('abbbbb')); // false (超过4次)
3. 贪婪与惰性匹配
// 贪婪匹配(默认)- 尽可能多地匹配
const greedyPattern = /<.*>/;
const text = '<div>content</div>';
console.log(text.match(greedyPattern)); // ['<div>content</div>']
// 惰性匹配 - 尽可能少地匹配
const lazyPattern = /<.*?>/;
console.log(text.match(lazyPattern)); // ['<div>']
// 实际应用:提取HTML标签内容
const htmlText = '<p>First paragraph</p><p>Second paragraph</p>';
// 贪婪匹配(可能不是期望的结果)
const greedyMatch = htmlText.match(/<p>.*<\/p>/);
console.log(greedyMatch); // ['<p>First paragraph</p><p>Second paragraph</p>']
// 惰性匹配(期望的结果)
const lazyMatch = htmlText.match(/<p>.*?<\/p>/g);
console.log(lazyMatch); // ['<p>First paragraph</p>', '<p>Second paragraph</p>']
🎯 锚点和边界
1. 位置锚点
// ^ - 字符串开始
const startPattern = /^Hello/;
console.log(startPattern.test('Hello world')); // true
console.log(startPattern.test('Say Hello')); // false
// $ - 字符串结束
const endPattern = /world$/;
console.log(endPattern.test('Hello world')); // true
console.log(endPattern.test('world peace')); // false
// 组合使用:精确匹配
const exactPattern = /^Hello world$/;
console.log(exactPattern.test('Hello world')); // true
console.log(exactPattern.test('Hello world!')); // false
2. 单词边界
// \b - 单词边界
const wordBoundaryPattern = /\bcat\b/;
console.log(wordBoundaryPattern.test('The cat sat')); // true
console.log(wordBoundaryPattern.test('concatenate')); // false
// 单词边界的位置示例
const text = 'Hello world! 123';
const boundaries = text.match(/\b/g);
console.log(boundaries); // 在每个单词边界处匹配
// \B - 非单词边界
const nonWordBoundaryPattern = /\Bcat\B/;
console.log(nonWordBoundaryPattern.test('concatenate')); // true
console.log(nonWordBoundaryPattern.test('The cat sat')); // false
3. 多行模式
// 多行模式下的^和$
const multilineText = `First line
Second line
Third line`;
// 普通模式
const normalPattern = /^Second/;
console.log(normalPattern.test(multilineText)); // false
// 多行模式
const multilinePattern = /^Second/m;
console.log(multilinePattern.test(multilineText)); // true
// 匹配每行的开始
const lineStartPattern = /^/gm;
const lines = multilineText.match(lineStartPattern);
console.log(lines); // ['', '', '', ''] (匹配每行的开始)
🔄 分组和捕获
1. 基本分组
// () - 分组
const groupPattern = /(ab)+/;
console.log(groupPattern.test('ab')); // true
console.log(groupPattern.test('abab')); // true
console.log(groupPattern.test('aba')); // false
// 捕获组内容
const capturePattern = /(\d{4})-(\d{2})-(\d{2})/;
const date = '2023-12-25';
const match = date.match(capturePattern);
console.log(match);
// [
// '2023-12-25', // 完整匹配
// '2023', // 第1个捕获组
// '12', // 第2个捕获组
// '25', // 第3个捕获组
// index: 0,
// input: '2023-12-25',
// groups: undefined
// ]
// 访问捕获组
console.log(match[1]); // '2023' (年份)
console.log(match[2]); // '12' (月份)
console.log(match[3]); // '25' (日期)
2. 非捕获组
// (?:...) - 非捕获组
const nonCapturePattern = /(?:ab)+/;
const match = nonCapturePattern.exec('abab');
console.log(match); // ['abab'] (没有子组)
// 实际应用:重复模式但不捕获
const phonePattern = /(?:\d{3}-){2}\d{4}/;
console.log(phonePattern.test('123-456-7890')); // true
3. 命名捕获组
// (?<name>...) - 命名捕获组
const namedGroupPattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const date = '2023-12-25';
const match = date.match(namedGroupPattern);
console.log(match.groups);
// { year: '2023', month: '12', day: '25' }
// 访问命名组
console.log(match.groups.year); // '2023'
console.log(match.groups.month); // '12'
console.log(match.groups.day); // '25'
// 在replace中使用命名组
const formatted = date.replace(
namedGroupPattern,
'$<day>/$<month>/$<year>'
);
console.log(formatted); // '25/12/2023'
🔍 选择和分支
1. 或操作符
// | - 或操作符
const orPattern = /cat|dog/;
console.log(orPattern.test('I have a cat')); // true
console.log(orPattern.test('I have a dog')); // true
console.log(orPattern.test('I have a bird')); // false
// 组合使用
const animalPattern = /(cat|dog|bird)s?/;
console.log(animalPattern.test('cat')); // true
console.log(animalPattern.test('cats')); // true
console.log(animalPattern.test('dogs')); // true
2. 分支优先级
// 注意优先级问题
const problematicPattern = /cat|doggy/;
console.log(problematicPattern.test('doggy')); // false (只匹配cat或dog)
// 使用分组解决优先级
const fixedPattern = /cat|dog(gy)?/;
console.log(fixedPattern.test('doggy')); // true
// 或者在整个模式上加分组
const betterPattern = /(cat|doggy)/;
console.log(betterPattern.test('doggy')); // true
🎨 正则表达式标志
1. 常用标志
// g - 全局匹配
const globalPattern = /hello/g;
const text = 'hello world, hello everyone';
const matches = text.match(globalPattern);
console.log(matches); // ['hello', 'hello']
// i - 忽略大小写
const caseInsensitivePattern = /hello/i;
console.log(caseInsensitivePattern.test('Hello')); // true
console.log(caseInsensitivePattern.test('HELLO')); // true
// m - 多行模式
const multilinePattern = /^hello/m;
const multilineText = 'first line\nhello world';
console.log(multilinePattern.test(multilineText)); // true
// u - Unicode模式
const unicodePattern = /\u{1F600}/u; // 😀
console.log(unicodePattern.test('😀')); // true
// y - 粘性模式
const stickyPattern = /hello/y;
const stickyText = 'hello world';
stickyPattern.lastIndex = 6;
console.log(stickyPattern.test(stickyText)); // false (必须从指定位置开始匹配)
2. 组合使用标志
// 组合多个标志
const combinedPattern = /hello/gi;
const text = 'Hello world, hello everyone, HELLO!';
const matches = text.match(combinedPattern);
console.log(matches); // ['Hello', 'hello', 'HELLO']
🚀 实际应用场景
1. 表单验证
// 邮箱验证
function validateEmail(email) {
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return emailPattern.test(email);
}
console.log(validateEmail('user@example.com')); // true
console.log(validateEmail('user.name@example.co.uk')); // true
console.log(validateEmail('invalid-email')); // false
// 手机号验证(中国)
function validateChinesePhone(phone) {
const phonePattern = /^1[3-9]\d{9}$/;
return phonePattern.test(phone);
}
console.log(validateChinesePhone('13812345678')); // true
console.log(validateChinesePhone('12345678901')); // false
// 密码强度验证
function validatePassword(password) {
// 至少8位,包含大小写字母、数字和特殊字符
const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
return passwordPattern.test(password);
}
console.log(validatePassword('Password123!')); // true
console.log(validatePassword('weak')); // false
2. 数据提取
// 提取URL参数
function extractUrlParams(url) {
const paramPattern = /[?&]([^=]+)=([^&]*)/g;
const params = {};
let match;
while ((match = paramPattern.exec(url)) !== null) {
params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
}
return params;
}
const url = 'https://example.com/search?q=javascript&lang=en&page=1';
console.log(extractUrlParams(url));
// { q: 'javascript', lang: 'en', page: '1' }
// 提取Markdown链接
function extractMarkdownLinks(text) {
const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
const links = [];
let match;
while ((match = linkPattern.exec(text)) !== null) {
links.push({
text: match[1],
url: match[2]
});
}
return links;
}
const markdownText = 'Visit [Google](https://google.com) and [GitHub](https://github.com)';
console.log(extractMarkdownLinks(markdownText));
// [
// { text: 'Google', url: 'https://google.com' },
// { text: 'GitHub', url: 'https://github.com' }
// ]
3. 数据清洗
// 清理空白字符
function cleanWhitespace(str) {
// 移除开头和结尾的空白,并将多个空白字符替换为单个空格
return str.replace(/^\s+|\s+$/g, '').replace(/\s+/g, ' ');
}
console.log(cleanWhitespace(' hello world ')); // 'hello world'
// 移除HTML标签
function stripHtml(html) {
return html.replace(/<[^>]*>/g, '');
}
console.log(stripHtml('<p>Hello <strong>world</strong>!</p>')); // 'Hello world!'
// 格式化电话号码
function formatPhoneNumber(phone) {
// 移除非数字字符,然后格式化
const cleaned = phone.replace(/\D/g, '');
const phonePattern = /(\d{3})(\d{4})(\d{4})/;
return cleaned.replace(phonePattern, '$1-$2-$3');
}
console.log(formatPhoneNumber('13812345678')); // '138-1234-5678'
console.log(formatPhoneNumber('(138) 1234-5678')); // '138-1234-5678'
4. 文本搜索和替换
// 高亮搜索关键词
function highlightKeywords(text, keywords) {
const pattern = new RegExp(`(${keywords.join('|')})`, 'gi');
return text.replace(pattern, '<mark>$1</mark>');
}
const searchText = 'JavaScript is a programming language. JavaScript is popular.';
console.log(highlightKeywords(searchText, ['JavaScript', 'programming']));
// '<mark>JavaScript</mark> is a <mark>programming</mark> language. <mark>JavaScript</mark> is popular.'
// 批量替换
function multipleReplacements(text, replacements) {
return replacements.reduce((result, [pattern, replacement]) => {
return result.replace(new RegExp(pattern, 'g'), replacement);
}, text);
}
const originalText = 'Hello world! This is a test. Hello again!';
const result = multipleReplacements(originalText, [
['Hello', 'Hi'],
['world', 'universe'],
['test', 'example']
]);
console.log(result); // 'Hi universe! This is a example. Hi again!'
🛠️ 高级技巧
1. 环视断言
// 正向先行断言 (?=...) - 匹配后面跟着特定内容的位置
const positiveLookahead = /Windows(?= XP|7|10)/;
console.log(positiveLookahead.test('Windows 10')); // true
console.log(positiveLookahead.test('Windows 98')); // false
// 负向先行断言 (?!) - 匹配后面不跟着特定内容的位置
const negativeLookahead = /Windows(?! XP|7|10)/;
console.log(negativeLookahead.test('Windows 98')); // true
console.log(negativeLookahead.test('Windows 10')); // false
// 正向后行断言 (?<=...) - 匹配前面有特定内容的位置
const positiveLookbehind = /(?<=\$)\d+/;
console.log(positiveLookbehind.exec('Price: $100')); // ['100']
// 负向后行断言 (?<!...) - 匹配前面没有特定内容的位置
const negativeLookbehind = /(?<!\$)\d+/;
console.log(negativeLookbehind.exec('Price: 100')); // ['100']
2. 条件匹配
// 使用条件匹配(ES2018+)
const conditionalPattern = /(a)?b(?(1)c|d)/;
console.log(conditionalPattern.test('abc')); // true (有a时必须有c)
console.log(conditionalPattern.test('abd')); // true (没有a时必须有d)
console.log(conditionalPattern.test('ab')); // false
3. 性能优化
// 避免灾难性回溯
// ❌ 容易导致回溯的模式
const badPattern = /^(a+)+b$/;
console.time('bad');
console.log(badPattern.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac')); // false
console.timeEnd('bad');
// ✅ 优化后的模式
const goodPattern = /^a+b$/;
console.time('good');
console.log(goodPattern.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac')); // false
console.timeEnd('good');
// 使用原子组防止回溯
const atomicPattern = /^(?>a+)+b$/;
📝 常用正则表达式模式
1. 用户名验证
const usernamePattern = /^[a-zA-Z0-9_]{3,20}$/;
// 3-20位,只能包含字母、数字和下划线
2. IP地址验证
const ipv4Pattern = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
3. URL验证
const urlPattern = /^https?:\/\/(?:[-\w.])+(?:\:[0-9]+)?(?:\/(?:[\w\/_.])*(?:\?(?:[\w&=%.])*)?(?:\#(?:[\w.])*)?)?$/;
4. 颜色值验证
const hexColorPattern = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
const rgbColorPattern = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/;
⚠️ 常见陷阱
1. 忘记转义特殊字符
// ❌ 错误:. 被当作通配符
const wrongPattern = /example.com/;
console.log(wrongPattern.test('examplexcom')); // true (不期望的结果)
// ✅ 正确:转义特殊字符
const correctPattern = /example\.com/;
console.log(correctPattern.test('example.com')); // true
console.log(correctPattern.test('examplexcom')); // false
2. 贪婪匹配问题
const html = '<div>content1</div><div>content2</div>';
// ❌ 贪婪匹配可能匹配过多
const greedyMatch = html.match(/<div>.*<\/div>/);
console.log(greedyMatch[0]); // '<div>content1</div><div>content2</div>'
// ✅ 使用惰性匹配
const lazyMatch = html.match(/<div>.*?<\/div>/g);
console.log(lazyMatch); // ['<div>content1</div>', '<div>content2</div>']
3. 全局标志下的lastIndex问题
const pattern = /test/g;
const str = 'test test';
console.log(pattern.test(str)); // true
console.log(pattern.lastIndex); // 4
console.log(pattern.test(str)); // true
console.log(pattern.lastIndex); // 9
console.log(pattern.test(str)); // false
console.log(pattern.lastIndex); // 0 (重置)
// 解决方案:不使用全局标志或重置lastIndex
pattern.lastIndex = 0;
📝 最佳实践
- 使用字面量创建静态正则表达式:更简洁且性能更好
- 转义特殊字符:在动态构建正则时使用RegExp.escape
- 合理使用捕获组:非必要时不捕获,使用非捕获组
- 避免回溯:避免嵌套量词导致的性能问题
- 添加注释:复杂正则表达式需要详细注释
- 测试边界情况:测试各种输入,包括极端情况
🎯 小结
- 掌握了正则表达式的基本语法和元字符
- 学会了创建和使用正则表达式
- 理解了正则表达式的匹配模式和标志
- 掌握了常见正则表达式模式和应用场景
- 了解了性能优化和常见陷阱
本模块学习完成! 🎉