正则表达式基础
正则表达式基础
正则表达式是处理字符串的强大工具,可用于模式匹配、搜索和替换等操作。本文将介绍JavaScript正则表达式的基本语法和常用方法,为后续深入学习打下基础。
正则表达式概述
正则表达式(Regular Expression,简称RegExp)是一种用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象,可以用于执行模式匹配、搜索和替换等操作。
创建正则表达式
在JavaScript中,有两种方式创建正则表达式:
1. 字面量方式
使用斜杠(/)包围模式:
// 基本语法:/pattern/flags
const regex1 = /hello/;
const regex2 = /hello/i; // 带有忽略大小写标志
2. RegExp构造函数
使用RegExp构造函数创建正则表达式对象:
// 基本语法:new RegExp(pattern[, flags])
const regex1 = new RegExp('hello');
const regex2 = new RegExp('hello', 'i'); // 带有忽略大小写标志
// 动态创建正则表达式
const searchTerm = 'hello';
const regex3 = new RegExp(searchTerm, 'i');
// 注意:使用构造函数时,字符串中的特殊字符需要额外转义
const regex4 = new RegExp('\\d+'); // 匹配一个或多个数字
正则表达式标志
标志用于修改正则表达式的匹配行为:
标志 | 描述 |
---|---|
g | 全局匹配 - 查找所有匹配项,而不是在第一个匹配后停止 |
i | 忽略大小写 - 不区分大小写进行匹配 |
m | 多行匹配 - 使^和$匹配每一行的开头和结尾 |
s | 点号匹配所有字符 - 让.匹配包括换行符在内的所有字符 |
u | Unicode匹配 - 启用Unicode支持 |
y | 粘性匹配 - 从正则表达式的lastIndex属性指定的位置开始匹配 |
// 示例
const text = 'Hello World\nHello JavaScript';
// 全局匹配
console.log(text.match(/hello/gi));
// ["Hello", "Hello"]
// 多行匹配
console.log(text.match(/^hello/gim));
// ["Hello", "Hello"]
// 不使用多行标志
console.log(text.match(/^hello/gi));
// ["Hello"]
正则表达式模式
基本字符匹配
// 精确匹配
const regex1 = /hello/;
console.log(regex1.test('hello world')); // true
console.log(regex1.test('Hello world')); // false(区分大小写)
// 忽略大小写
const regex2 = /hello/i;
console.log(regex2.test('Hello world')); // true
特殊字符
字符 | 描述 |
---|---|
. | 匹配除换行符外的任何单个字符 |
\d | 匹配任何数字字符(等价于[0-9]) |
\D | 匹配任何非数字字符(等价于[^0-9]) |
\w | 匹配任何字母、数字或下划线字符(等价于[A-Za-z0-9_]) |
\W | 匹配任何非字母、数字或下划线字符(等价于[^A-Za-z0-9_]) |
\s | 匹配任何空白字符(空格、制表符、换行符等) |
\S | 匹配任何非空白字符 |
\b | 匹配单词边界 |
\B | 匹配非单词边界 |
// 示例
const text = 'Hello123 World!';
// 匹配数字
console.log(text.match(/\d+/g)); // ["123"]
// 匹配单词
console.log(text.match(/\b\w+\b/g)); // ["Hello123", "World"]
// 匹配非单词字符
console.log(text.match(/\W/g)); // [" ", "!"]
字符类
字符类允许匹配指定集合中的任何一个字符。
// 匹配a、b或c
const regex1 = /[abc]/;
console.log(regex1.test('apple')); // true
console.log(regex1.test('dog')); // false
// 匹配a到z之间的任何字符
const regex2 = /[a-z]/;
console.log(regex2.test('Apple')); // true
console.log(regex2.test('123')); // false
// 匹配除了a、b、c之外的任何字符
const regex3 = /[^abc]/;
console.log(regex3.test('abc')); // false
console.log(regex3.test('abcd')); // true
量词
量词指定匹配的次数。
量词 | 描述 |
---|---|
* | 匹配前面的表达式0次或多次 |
+ | 匹配前面的表达式1次或多次 |
? | 匹配前面的表达式0次或1次 |
{n} | 精确匹配前面的表达式n次 |
{n,} | 匹配前面的表达式至少n次 |
{n,m} | 匹配前面的表达式至少n次,最多m次 |
// 示例
const text = 'color colour';
// 匹配color或colour
console.log(text.match(/colou?r/g)); // ["color", "colour"]
// 匹配数字
console.log('123 1234 12345'.match(/\d{2,4}/g)); // ["123", "1234", "1234"]
// 匹配单词
console.log('hi hello hi'.match(/\bhi\b/g)); // ["hi", "hi"]
分组和捕获
圆括号用于分组和捕获匹配的文本。
// 基本分组
const regex1 = /(hi|hello) world/;
console.log(regex1.test('hi world')); // true
console.log(regex1.test('hello world')); // true
// 捕获组
const match = 'hello world'.match(/(hello) (world)/);
console.log(match[0]); // "hello world"(整个匹配)
console.log(match[1]); // "hello"(第一个捕获组)
console.log(match[2]); // "world"(第二个捕获组)
// 命名捕获组(ES2018)
const namedMatch = 'hello world'.match(/(?<greeting>hello) (?<target>world)/);
console.log(namedMatch.groups.greeting); // "hello"
console.log(namedMatch.groups.target); // "world"
// 非捕获组
const nonCapture = 'hello world'.match(/(?:hello) (world)/);
console.log(nonCapture[0]); // "hello world"
console.log(nonCapture[1]); // "world"(只有一个捕获组)
边界匹配
// ^ 匹配字符串开头
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 hello')); // false
// \b 匹配单词边界
console.log(/\bcat\b/.test('the cat sat')); // true
console.log(/\bcat\b/.test('category')); // false
前瞻和后顾
前瞻和后顾断言允许匹配取决于其后或其前的内容,但不包括这些内容。
// 正向前瞻:匹配后面跟着指定模式的内容
console.log('hello world'.match(/hello(?= world)/)); // ["hello"]
// 负向前瞻:匹配后面不跟着指定模式的内容
console.log('hello JavaScript hello world'.match(/hello(?! world)/g)); // ["hello"]
// 正向后顾:匹配前面有指定模式的内容(ES2018)
console.log('world hello'.match(/(?<=world )hello/)); // ["hello"]
// 负向后顾:匹配前面没有指定模式的内容(ES2018)
console.log('JavaScript hello world hello'.match(/(?<!world )hello/g)); // ["hello", "hello"]
正则表达式方法
RegExp对象方法
test()
测试字符串是否匹配正则表达式模式。
const regex = /hello/i;
console.log(regex.test('Hello World')); // true
console.log(regex.test('Hi World')); // false
exec()
在字符串中执行匹配搜索,返回结果数组或null。
const regex = /(\d{2})-(\d{2})-(\d{4})/;
const result = regex.exec('Date: 25-12-2023');
console.log(result[0]); // "25-12-2023"(完整匹配)
console.log(result[1]); // "25"(第一个捕获组)
console.log(result[2]); // "12"(第二个捕获组)
console.log(result[3]); // "2023"(第三个捕获组)
console.log(result.index); // 6(匹配的起始位置)
// 使用全局标志时的行为
const globalRegex = /\d+/g;
let match;
while ((match = globalRegex.exec('123 456 789')) !== null) {
console.log(`Found ${match[0]} at ${match.index}`);
}
// Found 123 at 0
// Found 456 at 4
// Found 789 at 8
String对象方法
match()
返回字符串匹配正则表达式的结果。
const str = 'Hello World! Hello JavaScript!';
// 不使用全局标志
const match1 = str.match(/Hello/);
console.log(match1[0]); // "Hello"
console.log(match1.index); // 0
// 使用全局标志
const match2 = str.match(/Hello/g);
console.log(match2); // ["Hello", "Hello"]
matchAll()
返回一个包含所有匹配结果的迭代器(ES2020)。
const str = 'Hello World! Hello JavaScript!';
const matches = str.matchAll(/Hello/g);
for (const match of matches) {
console.log(match[0], match.index);
}
// Hello 0
// Hello 13
search()
返回字符串中匹配正则表达式的第一个子串的索引,如果没有匹配则返回-1。
const str = 'Hello World!';
console.log(str.search(/World/)); // 6
console.log(str.search(/JavaScript/)); // -1
replace()
使用替换字符串替换匹配的子串。
const str = 'Hello World! Hello JavaScript!';
// 替换第一个匹配项
console.log(str.replace(/Hello/, 'Hi'));
// "Hi World! Hello JavaScript!"
// 替换所有匹配项
console.log(str.replace(/Hello/g, 'Hi'));
// "Hi World! Hi JavaScript!"
// 使用函数作为替换值
console.log(str.replace(/Hello/g, match => match.toUpperCase()));
// "HELLO World! HELLO JavaScript!"
// 使用捕获组引用
console.log('John Doe'.replace(/(\w+) (\w+)/, '$2, $1'));
// "Doe, John"
replaceAll()
替换所有匹配的子串(ES2021)。
const str = 'Hello World! Hello JavaScript!';
console.log(str.replaceAll('Hello', 'Hi'));
// "Hi World! Hi JavaScript!"
// 使用正则表达式时必须带有全局标志
console.log(str.replaceAll(/Hello/g, 'Hi'));
// "Hi World! Hi JavaScript!"
split()
使用正则表达式分割字符串。
const str = 'Hello World! Hello JavaScript!';
// 按空格分割
console.log(str.split(/\s+/));
// ["Hello", "World!", "Hello", "JavaScript!"]
// 按标点符号分割
console.log('apple,orange;banana'.split(/[,;]/));
// ["apple", "orange", "banana"]
正则表达式的常见应用
1. 表单验证
// 电子邮件验证
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
console.log(isValidEmail('user@example.com')); // true
console.log(isValidEmail('invalid-email')); // false
// 密码强度验证
function checkPasswordStrength(password) {
// 至少8个字符,包含大小写字母、数字和特殊字符
const strongRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()]).{8,}$/;
return strongRegex.test(password);
}
console.log(checkPasswordStrength('Passw0rd!')); // true
console.log(checkPasswordStrength('password')); // false
// 手机号码验证(中国大陆)
function isValidPhoneNumber(phone) {
const phoneRegex = /^1[3-9]\d{9}$/;
return phoneRegex.test(phone);
}
console.log(isValidPhoneNumber('13812345678')); // true
console.log(isValidPhoneNumber('12345678901')); // false
2. 数据提取
// 提取URL中的参数
function getURLParameters(url) {
const params = {};
const regex = /[?&]([^=#]+)=([^&#]*)/g;
let match;
while ((match = regex.exec(url)) !== null) {
params[match[1]] = decodeURIComponent(match[2]);
}
return params;
}
const url = 'https://example.com/search?query=javascript&page=1&limit=10';
console.log(getURLParameters(url));
// { query: "javascript", page: "1", limit: "10" }
// 提取HTML标签
function extractTags(html) {
const tagRegex = /<([a-z]+)([^<]+)*(?:>(.*?)<\/\1>|\s+\/>)/gi;
const tags = [];
let match;
while ((match = tagRegex.exec(html)) !== null) {
tags.push(match[1]); // 提取标签名
}
return tags;
}
const html = '<div class="container"><p>Hello</p><img src="image.jpg" /></div>';
console.log(extractTags(html)); // ["div", "p", "img"]
3. 字符串处理
// 驼峰命名转换
function toCamelCase(str) {
return str.replace(/[-_\s]([a-z])/g, (_, letter) => letter.toUpperCase());
}
console.log(toCamelCase('background-color')); // "backgroundColor"
console.log(toCamelCase('user_profile_data')); // "userProfileData"
// 格式化数字(添加千位分隔符)
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
console.log(formatNumber(1234567)); // "1,234,567"
console.log(formatNumber(9876543210)); // "9,876,543,210"
// 删除多余空白
function trimExtraSpaces(str) {
return str.replace(/\s+/g, ' ').trim();
}
console.log(trimExtraSpaces(' Hello World ! ')); // "Hello World !"
4. 内容过滤
// 敏感词过滤
function filterSensitiveWords(text, sensitiveWords) {
const pattern = new RegExp(sensitiveWords.join('|'), 'gi');
return text.replace(pattern, match => '*'.repeat(match.length));
}
const text = 'This content contains bad words like badword1 and badword2.';
const sensitiveWords = ['badword1', 'badword2'];
console.log(filterSensitiveWords(text, sensitiveWords));
// "This content contains bad words like ******** and ********."
// 脱敏处理(隐藏部分信息)
function maskSensitiveInfo(text) {
// 隐藏电话号码中间部分
const phoneRegex = /(\d{3})(\d{4})(\d{4})/g;
const maskedPhone = text.replace(phoneRegex, '$1****$3');
// 隐藏邮箱用户名部分
const emailRegex = /([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g;
return maskedPhone.replace(emailRegex, (match, username, domain) => {
const maskedUsername = username.slice(0, 3) + '***';
return `${maskedUsername}@${domain}`;
});
}
const userInfo = '联系方式:13812345678,邮箱:user123@example.com';
console.log(maskSensitiveInfo(userInfo));
// "联系方式:138****5678,邮箱:use***@example.com"
正则表达式优化技巧
1. 避免回溯过多
// 不推荐:可能导致灾难性回溯
const badRegex = /^(a+)+$/;
// 对于输入 "aaaaX" 会导致指数级回溯
// 推荐:限制重复次数
const betterRegex = /^(a{1,10})+$/;
2. 使用非捕获组
// 不推荐:使用捕获组但不需要捕获结果
const slowRegex = /(https?|ftp):\/\/(.*)/;
// 推荐:使用非捕获组提高性能
const fastRegex = /(?:https?|ftp):\/\/(.*)/;
3. 预编译正则表达式
// 不推荐:在循环中创建正则表达式
function slowSearch(texts, pattern) {
return texts.filter(text => {
const regex = new RegExp(pattern, 'i'); // 每次迭代都创建新的正则表达式
return regex.test(text);
});
}
// 推荐:预编译正则表达式
function fastSearch(texts, pattern) {
const regex = new RegExp(pattern, 'i'); // 只创建一次
return texts.filter(text => regex.test(text));
}
4. 使用适当的量词
// 不推荐:贪婪量词可能导致过度匹配
const greedyRegex = /<div>.*<\/div>/;
// 推荐:使用非贪婪量词
const lazyRegex = /<div>.*?<\/div>/;
常见错误和陷阱
1. 特殊字符未转义
// 错误:点号是特殊字符,匹配任意字符
const wrongRegex = /example.com/;
console.log(wrongRegex.test('exampleXcom')); // true(非预期)
// 正确:转义特殊字符
const correctRegex = /example\.com/;
console.log(correctRegex.test('exampleXcom')); // false
console.log(correctRegex.test('example.com')); // true
2. 忽略全局标志的副作用
// 使用全局标志的正则表达式会记住上次匹配位置
const regex = /\d+/g;
console.log(regex.test('123')); // true
console.log(regex.test('123')); // false(第二次调用从上次匹配结束位置开始)
// 重置lastIndex属性
regex.lastIndex = 0;
console.log(regex.test('123')); // true
3. 错误的字符类范围
// 错误:字符范围顺序错误
const wrongRegex = /[z-a]/; // 无效范围
// 正确:正确的字符范围
const correctRegex = /[a-z]/;
4. 过度依赖正则表达式
// 不推荐:使用复杂正则表达式解析HTML
const htmlRegex = /<([a-z]+)(?:\s+[a-z]+="[^"]*")*>.*?<\/\1>/gi;
// 推荐:使用DOM解析HTML
function parseHTML(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
// 使用DOM API处理
}
正则表达式调试工具
开发复杂正则表达式时,可以使用以下工具进行测试和调试:
在线正则表达式测试工具:
- RegExr (https://regexr.com/)
- Regex101 (https://regex101.com/)
- RegExper (https://regexper.com/)
JavaScript中的调试技巧:
// 分步调试复杂正则表达式 function debugRegex(regex, str) { console.log(`Testing: "${str}" against ${regex}`); const result = regex.exec(str); if (result) { console.log('Match found:', result[0]); for (let i = 1; i < result.length; i++) { console.log(`Group ${i}:`, result[i]); } console.log('Index:', result.index); } else { console.log('No match found'); } } debugRegex(/(\d{2})-(\d{2})-(\d{4})/, 'Date: 25-12-2023');
总结
正则表达式是处理字符串的强大工具,在JavaScript中有广泛的应用:
- 创建方式:字面量方式和RegExp构造函数
- 基本语法:字符匹配、特殊字符、字符类、量词、分组和边界
- 高级特性:前瞻和后顾断言、命名捕获组
- 方法:test(), exec(), match(), matchAll(), search(), replace(), replaceAll(), split()
- 应用场景:表单验证、数据提取、字符串处理、内容过滤
掌握正则表达式可以大大提高字符串处理的效率,但也需要注意性能优化和常见陷阱。
练习
编写一个正则表达式,匹配有效的中国身份证号码(18位,最后一位可能是数字或X)。
实现一个函数,将文本中的URL转换为HTML链接标签。
创建一个正则表达式,从文本中提取所有的日期(格式如:YYYY-MM-DD)。
编写一个函数,验证输入的字符串是否为有效的IPv4地址。
实现一个简单的模板引擎,使用正则表达式替换模板中的变量(如:
{{variable}}
)。