正则表达式

本章将详细介绍正则表达式的基本语法和在JavaScript中的应用,包括模式匹配、字符串验证、文本替换等常用操作,帮助您掌握这一强大的文本处理工具。

14.1 正则表达式概述

正则表达式是一种用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式通过RegExp对象来表示和操作。

正则表达式的优势

  • 模式匹配:可以精确匹配复杂的字符串模式
  • 文本验证:验证输入是否符合特定格式
  • 文本搜索:在文本中查找特定模式
  • 文本替换:替换符合模式的文本
  • 文本分割:根据模式分割字符串

14.2 正则表达式的创建

在JavaScript中有两种创建正则表达式的方式。

字面量语法

// 字面量语法
let regex1 = /hello/;
let regex2 = /hello/i; // i标志表示忽略大小写
let regex3 = /hello/g; // g标志表示全局匹配
let regex4 = /hello/m; // m标志表示多行匹配
let regex5 = /hello/u; // u标志表示Unicode模式 (ES6)
let regex6 = /hello/y; // y标志表示粘性匹配 (ES6)

构造函数

// RegExp构造函数
let regex1 = new RegExp("hello");
let regex2 = new RegExp("hello", "i"); // 标志作为第二个参数
let regex3 = new RegExp(/hello/i, "g"); // 从现有正则表达式创建 (ES6)

// 动态创建正则表达式
let pattern = "hello";
let flags = "gi";
let dynamicRegex = new RegExp(pattern, flags);

14.3 正则表达式语法

正则表达式由普通字符和特殊字符(元字符)组成。

普通字符

// 普通字符匹配自身
let regex = /hello/;
console.log(regex.test("hello world")); // true
console.log(regex.test("Hello world")); // false (区分大小写)

字符类

// 字符类匹配方括号中的任意一个字符
let regex1 = /[abc]/; // 匹配a、b或c
console.log(regex1.test("apple")); // true
console.log(regex1.test("banana")); // true
console.log(regex1.test("cherry")); // true
console.log(regex1.test("dog")); // false

// 范围字符类
let regex2 = /[a-z]/; // 匹配任意小写字母
let regex3 = /[A-Z]/; // 匹配任意大写字母
let regex4 = /[0-9]/; // 匹配任意数字
let regex5 = /[a-zA-Z0-9]/; // 匹配任意字母或数字

// 反向字符类
let regex6 = /[^0-9]/; // 匹配任意非数字字符

预定义字符类

// 预定义字符类
let regex1 = /\d/; // 等价于[0-9],匹配数字
let regex2 = /\D/; // 等价于[^0-9],匹配非数字
let regex3 = /\w/; // 等价于[a-zA-Z0-9_],匹配单词字符
let regex4 = /\W/; // 等价于[^a-zA-Z0-9_],匹配非单词字符
let regex5 = /\s/; // 匹配空白字符(空格、制表符、换行符等)
let regex6 = /\S/; // 匹配非空白字符

量词

// 量词指定匹配次数
let regex1 = /a*/;  // 匹配0个或多个a
let regex2 = /a+/;  // 匹配1个或多个a
let regex3 = /a?/;  // 匹配0个或1个a
let regex4 = /a{3}/;  // 精确匹配3个a
let regex5 = /a{2,5}/;  // 匹配2到5个a
let regex6 = /a{2,}/;  // 匹配2个或更多a

// 贪婪匹配与非贪婪匹配
let text = "aaaa";
let greedy = /a+/;  // 贪婪匹配
let nonGreedy = /a+?/;  // 非贪婪匹配

console.log(text.match(greedy)); // ["aaaa"]
console.log(text.match(nonGreedy)); // ["a"]

定位符

// 定位符匹配位置而非字符
let regex1 = /^hello/;  // 匹配行首的hello
let regex2 = /world$/;  // 匹配行尾的world
let regex3 = /\bword\b/;  // 匹配完整单词word
let regex4 = /\Bword\B/;  // 匹配非单词边界的word

console.log(regex1.test("hello world")); // true
console.log(regex1.test("say hello")); // false
console.log(regex2.test("hello world")); // true
console.log(regex2.test("world peace")); // false

分组和捕获

// 分组使用圆括号
let regex = /(hello) (world)/;
let result = "hello world".match(regex);
console.log(result[0]); // "hello world" (完整匹配)
console.log(result[1]); // "hello" (第一个分组)
console.log(result[2]); // "world" (第二个分组)

// 非捕获分组
let regex2 = /(?:hello) (world)/;
let result2 = "hello world".match(regex2);
console.log(result2.length); // 2 (不包含非捕获分组)

// 命名捕获组 (ES2018)
let regex3 = /(?hello) (?world)/;
let result3 = "hello world".match(regex3);
console.log(result3.groups.greet); // "hello"
console.log(result3.groups.name); // "world"

14.4 正则表达式标志

正则表达式标志用于修改匹配行为。

// i标志 - 忽略大小写
let regex1 = /hello/i;
console.log(regex1.test("HELLO")); // true

// g标志 - 全局匹配
let regex2 = /hello/g;
let text = "hello hello hello";
console.log(text.match(regex2)); // ["hello", "hello", "hello"]

// m标志 - 多行匹配
let regex3 = /^hello/m;
let text2 = "say\nhello\nworld";
console.log(regex3.test(text2)); // true

// u标志 - Unicode模式 (ES6)
let regex4 = /\u{61}/u; // Unicode转义
console.log(regex4.test("a")); // true

// y标志 - 粘性匹配 (ES6)
let regex5 = /hello/y;
let text3 = "hello world";
console.log(regex5.test(text3)); // true
regex5.lastIndex = 1;
console.log(regex5.test(text3)); // false (必须从lastIndex位置开始匹配)

14.5 RegExp对象方法

RegExp对象提供了多种方法用于执行匹配操作。

test()方法

// test()方法测试是否匹配
let regex = /hello/;
console.log(regex.test("hello world")); // true
console.log(regex.test("goodbye")); // false

// 全局匹配时的状态
let globalRegex = /hello/g;
console.log(globalRegex.test("hello hello")); // true
console.log(globalRegex.lastIndex); // 5
console.log(globalRegex.test("hello hello")); // true
console.log(globalRegex.lastIndex); // 11
console.log(globalRegex.test("hello hello")); // false
console.log(globalRegex.lastIndex); // 0 (重置)

exec()方法

// exec()方法执行匹配并返回详细信息
let regex = /hello (world)/;
let result = regex.exec("hello world");

console.log(result[0]); // "hello world" (完整匹配)
console.log(result[1]); // "world" (捕获组)
console.log(result.index); // 0 (匹配位置)
console.log(result.input); // "hello world" (输入字符串)

// 全局匹配
let globalRegex = /hello/g;
let text = "hello hello";
let result1 = globalRegex.exec(text);
let result2 = globalRegex.exec(text);

console.log(result1); // 第一个匹配
console.log(result2); // 第二个匹配

14.6 字符串的正则方法

字符串对象也提供了使用正则表达式的方法。

match()方法

// match()方法返回匹配结果
let text = "hello world";
let regex = /hello/;

console.log(text.match(regex)); // ["hello", index: 0, input: "hello world", groups: undefined]

// 全局匹配返回所有匹配
let globalRegex = /l/g;
console.log(text.match(globalRegex)); // ["l", "l", "l"]

// 没有匹配返回null
console.log(text.match(/xyz/)); // null

search()方法

// search()方法返回匹配位置
let text = "hello world";
console.log(text.search(/world/)); // 6
console.log(text.search(/xyz/)); // -1 (未找到)

replace()方法

// replace()方法替换匹配内容
let text = "hello world, hello javascript";

// 替换第一个匹配
console.log(text.replace(/hello/, "hi")); // "hi world, hello javascript"

// 全局替换
console.log(text.replace(/hello/g, "hi")); // "hi world, hi javascript"

// 使用捕获组
let phone = "123-456-7890";
console.log(phone.replace(/(\d{3})-(\d{3})-(\d{4})/, "($1) $2-$3")); // "(123) 456-7890"

// 使用函数替换
let text2 = "The price is $100 and $200";
console.log(text2.replace(/\$(\d+)/g, (match, price) => `$${price * 1.1}`)); 
// "The price is $110 and $220"

split()方法

// split()方法根据正则表达式分割字符串
let text = "apple,banana;orange:grape";
let separators = /[,;:]/;
console.log(text.split(separators)); // ["apple", "banana", "orange", "grape"]

// 限制分割数量
console.log(text.split(separators, 2)); // ["apple", "banana"]

// 使用捕获组保留分隔符
let text2 = "hello123world456test";
console.log(text2.split(/(\d+)/)); // ["hello", "123", "world", "456", "test"]

14.7 正则表达式高级特性

ES6及后续版本为正则表达式添加了新的特性。

Unicode属性转义 (ES2018)

// Unicode属性转义
let regex1 = /\p{Script=Han}+/u; // 匹配汉字
console.log(regex1.test("你好世界")); // true

let regex2 = /\p{Emoji}+/u; // 匹配表情符号
console.log(regex2.test("Hello 👋 World 🌍")); // true

let regex3 = /\p{Lu}+/u; // 匹配大写字母
console.log(regex3.test("HELLO")); // true

后行断言 (ES2018)

// 后行断言
let regex1 = /(?<=\$)\d+/; // 匹配美元符号后的数字
console.log("价格是$100".match(regex1)); // ["100"]

let regex2 = /(?

dotAll模式 (ES2018)

// dotAll模式
let regex1 = /hello.world/; // .不匹配换行符
console.log(regex1.test("hello\nworld")); // false

let regex2 = /hello.world/s; // s标志使.匹配换行符
console.log(regex2.test("hello\nworld")); // true

14.8 实际应用示例

表单验证

// 邮箱验证
function validateEmail(email) {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(email);
}

console.log(validateEmail("user@example.com")); // true
console.log(validateEmail("invalid.email")); // false

// 手机号验证
function validatePhone(phone) {
    const regex = /^1[3-9]\d{9}$/;
    return regex.test(phone);
}

console.log(validatePhone("13812345678")); // true
console.log(validatePhone("12345678901")); // false

// 密码强度验证
function validatePassword(password) {
    // 至少8位,包含大小写字母、数字和特殊字符
    const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
    return regex.test(password);
}

console.log(validatePassword("Password123!")); // true
console.log(validatePassword("password")); // false

文本处理

// 提取URL
function extractUrls(text) {
    const urlRegex = /https?:\/\/[^\s]+/g;
    return text.match(urlRegex) || [];
}

let text = "访问 https://example.com 或 http://test.org 获取更多信息";
console.log(extractUrls(text)); // ["https://example.com", "http://test.org"]

// 格式化电话号码
function formatPhone(phone) {
    const cleanPhone = phone.replace(/\D/g, ''); // 移除所有非数字字符
    return cleanPhone.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
}

console.log(formatPhone("13812345678")); // "138-1234-5678"
console.log(formatPhone("138-1234-5678")); // "138-1234-5678"

// 敏感词过滤
function filterSensitiveWords(text, sensitiveWords) {
    let filteredText = text;
    sensitiveWords.forEach(word => {
        const regex = new RegExp(word, 'gi');
        filteredText = filteredText.replace(regex, '*'.repeat(word.length));
    });
    return filteredText;
}

let content = "这篇文章包含敏感词";
let sensitiveWords = ["敏感", "词"];
console.log(filterSensitiveWords(content, sensitiveWords)); // "这篇文章包含**词"

数据解析

// 解析CSV数据
function parseCSV(csvText) {
    const lines = csvText.trim().split('\n');
    const headers = lines[0].split(',').map(h => h.trim());
    
    return lines.slice(1).map(line => {
        const values = line.split(',').map(v => v.trim());
        const obj = {};
        headers.forEach((header, index) => {
            obj[header] = values[index];
        });
        return obj;
    });
}

let csv = `
name,age,city
张三,25,北京
李四,30,上海
`;
console.log(parseCSV(csv));

// 解析日志
function parseLog(logLine) {
    const regex = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(\w+)\] (.+)$/;
    const match = logLine.match(regex);
    
    if (match) {
        return {
            timestamp: match[1],
            level: match[2],
            message: match[3]
        };
    }
    return null;
}

let log = "2025-10-11 14:30:45 [INFO] Application started";
console.log(parseLog(log));
// {timestamp: "2025-10-11 14:30:45", level: "INFO", message: "Application started"}

提示:正则表达式是处理文本的强大工具,但也要注意其复杂性。在实际开发中,对于复杂的文本处理需求,可以考虑使用专门的解析库。同时要注意正则表达式的性能,避免使用回溯过多的复杂模式。