正则表达式
正则表达式(Regular Expressions,简称regex或regexp)是用于匹配字符串中字符组合的强大工具。它们被广泛应用于搜索、替换和验证操作中。本文将介绍JavaScript中的正则表达式,包括其基本字符、常用示例以及优缺点。
正则表达式基本字符表
| 字符 | 含义 |
|---|---|
. | 匹配除换行符以外的任何单个字符 |
^ | 匹配字符串的开始位置 |
$ | 匹配字符串的结束位置 |
* | 匹配前一个字符零次或多次 |
+ | 匹配前一个字符一次或多次 |
? | 匹配前一个字符零次或一次 |
{n} | 匹配前一个字符恰好n次 |
{n,} | 匹配前一个字符至少n次 |
{n,m} | 匹配前一个字符至少n次但不超过m次 |
[] | 匹配方括号内的任意一个字符 |
| ` | ` |
| `` | 转义字符,用于匹配特殊字符 |
\d | 匹配任何数字字符,相当于[0-9] |
\D | 匹配任何非数字字符 |
\w | 匹配任何单词字符,相当于[a-zA-Z0-9_] |
\W | 匹配任何非单词字符 |
\s | 匹配任何空白字符 |
\S | 匹配任何非空白字符 |
() | 捕获组,用于提取子匹配 |
常用示例
示例1:验证电子邮件地址
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/;
const email = "example@example.com";
console.log(emailRegex.test(email)); // true
这个正则表达式用于验证电子邮件地址的格式。
示例2:查找电话号码
const phoneRegex = /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/;
const text = "My phone number is 123-456-7890.";
const match = text.match(phoneRegex);
console.log(match[0]); // 123-456-7890
这个正则表达式用于查找包含在文本中的电话号码。
示例3:替换字符串中的空格
const str = "Hello World!";
const result = str.replace(/\s/g, '-');
console.log(result); // Hello-World!
这个正则表达式用于将字符串中的所有空格替换为连字符。
示例4:提取URL中的域名
const urlRegex = /https?://(www.)?([^/]+)/?/;
const url = "https://www.example.com/path";
const domain = url.match(urlRegex)[2];
console.log(domain); // example.com
这个正则表达式用于提取URL中的域名部分。
优缺点
优点
- 强大的文本匹配能力:正则表达式能够实现复杂的文本匹配和替换任务。
- 简洁的语法:使用正则表达式可以用较少的代码实现复杂的文本操作。
- 广泛的支持:几乎所有编程语言都支持正则表达式。
缺点
- 可读性差:正则表达式语法复杂,难以阅读和理解,尤其是对初学者而言。
- 调试困难:由于正则表达式的语法复杂,调试正则表达式可能比较困难。
- 性能问题:在某些情况下,复杂的正则表达式可能会导致性能问题,特别是处理大文本时。
JavaScript中的正则表达式高级用法
在正则表达式中,除了基本字符外,还有一些高级的用法可以帮助我们进行更复杂的文本匹配。这些高级用法包括分组、前瞻和后顾断言等。本文将详细介绍这些高级用法。
高级正则表达式字符表
| 字符 | 含义 |
|---|---|
(x) | 捕获组,用于提取匹配的子字符串 |
(?:x) | 非捕获组,用于匹配但不捕获子字符串 |
x(?=y) | 正向前瞻断言,匹配x仅当x后面跟着y |
x(?!y) | 负向前瞻断言,匹配x仅当x后面不跟着y |
(?<=y)x | 正向后顾断言,匹配x仅当x前面是y |
(?<!y)x | 负向后顾断言,匹配x仅当x前面不是y |
\cX | 控制字符,与X进行异或操作(X必须为A-Z,且大小写敏感) |
示例和用法
示例1:捕获组 (x)
捕获组用于提取匹配的子字符串,并可以在后续操作中使用。
const regex = /(hello) world/;
const text = "hello world";
const match = text.match(regex);
console.log(match[1]); // hello
示例2:非捕获组 (?:x)
非捕获组用于匹配但不捕获子字符串,适用于需要分组但不需要提取子字符串的情况。
const regex = /(?:hello) world/;
const text = "hello world";
const match = text.match(regex);
console.log(match); // ["hello world"]
示例3:正向前瞻断言 x(?=y)
正向前瞻断言用于匹配x,仅当x后面跟着y时才匹配。
const regex = /\d(?=px)/;
const text = "100px";
const match = text.match(regex);
console.log(match[0]); // 0
示例4:负向前瞻断言 x(?!y)
负向前瞻断言用于匹配x,仅当x后面不跟着y时才匹配。
const regex = /\d(?!px)/;
const text = "100px 50em";
const matches = text.match(regex);
console.log(matches); // ["5", "0"]
示例5:正向后顾断言 (?<=y)x
正向后顾断言用于匹配x,仅当x前面是y时才匹配。
const regex = /(?<=$)\d+/;
const text = "$100";
const match = text.match(regex);
console.log(match[0]); // 100
示例6:负向后顾断言 (?<!y)x
负向后顾断言用于匹配x,仅当x前面不是y时才匹配。
const regex = /(?<!$)\d+/;
const text = "100 $200";
const matches = text.match(regex);
console.log(matches); // ["100"]
示例7:控制字符 \cX
控制字符与X进行异或操作,X必须为A-Z且大小写敏感。
const regex = /\cJ/;
const text = String.fromCharCode(10); // 控制字符\n
const match = text.match(regex);
console.log(match); // ["\n"]
JavaScript中的正则表达式:安全性和性能
正则表达式的安全性
正则表达式可能会带来一些安全风险,尤其是在处理用户输入时。以下是一些常见的安全问题及其解决方案:
1. 正则表达式注入
正则表达式注入是一种通过构造恶意输入来操纵正则表达式的攻击。攻击者可以通过提供恶意输入,使正则表达式的行为发生意外变化。
示例:
let userInput = ".*";
let regex = new RegExp(userInput);
console.log("abc".match(regex)); // ['abc']
解决方案:
在构造正则表达式之前,对用户输入进行验证和转义。
let userInput = ".*";
let safeInput = userInput.replace(/[-/\^$*+?.()|[]{}]/g, '\$&');
let regex = new RegExp(safeInput);
console.log("abc".match(regex)); // null
2. 拒绝服务攻击(ReDoS)
正则表达式拒绝服务攻击(ReDoS)是利用正则表达式的高复杂度和回溯特性,构造特定输入使正则表达式在处理时耗费大量计算资源,从而导致系统性能下降或崩溃。
示例:
let regex = /(a+)+$/;
let input = "a".repeat(100000) + "!";
console.log(regex.test(input)); // 处理时间极长
解决方案:
避免使用容易导致回溯的复杂正则表达式,并使用正则表达式库(如safe-regex)来检查正则表达式的安全性。
let safeRegex = require('safe-regex');
let regex = /(a+)+$/;
if (safeRegex(regex)) {
console.log("Safe regex");
} else {
console.log("Unsafe regex");
}
正则表达式的性能
正则表达式的性能问题通常源于不合理的使用或过于复杂的正则表达式。在处理大文本或高频率调用正则表达式时,性能问题尤为明显。
1. 避免回溯
正则表达式中的回溯是导致性能问题的主要原因之一。回溯会在匹配失败时退回重新尝试,耗费大量时间。
解决方案:
使用非回溯的正则表达式结构,避免过多的可选匹配(如*、+、?)。
let regex = /a+$/;
let input = "a".repeat(100000) + "!";
console.log(regex.test(input)); // 快速处理
2. 使用懒惰量词
懒惰量词(*?、+?、??)会在匹配尽可能少的字符时终止,避免不必要的回溯。
示例:
let regex = /<.*?>/g; // 使用懒惰量词
let text = "<div>content</div>";
console.log(text.match(regex)); // ["<div>", "</div>"]
3. 分解复杂正则表达式
将复杂的正则表达式分解为多个简单的正则表达式,可以减少回溯和匹配的复杂度。
示例:
let text = "abc123xyz";
let part1 = text.match(/[a-z]+/)[0];
let part2 = text.match(/\d+/)[0];
console.log(part1, part2); // abc 123
4. 使用原生字符串函数
对于简单的字符串操作,使用原生字符串函数(如indexOf、startsWith、endsWith)通常比正则表达式更高效。
示例:
let text = "hello world";
console.log(text.indexOf("world") !== -1); // true
console.log(text.startsWith("hello")); // true
console.log(text.endsWith("world")); // true