前置知识: JavaScript

正则表达式

2 minIntermediate2026/6/13

JavaScript正则表达式语法、常用模式、RegExp对象、字符串方法与实战技巧详解。

1. 正则表达式基础

1.1 创建正则表达式

// 方式1:字面量(推荐)
const pattern1 = /hello/i;

// 方式2:构造函数(动态生成时使用)
const keyword = 'hello';
const pattern2 = new RegExp(keyword, 'i');

// 修饰符
// g: 全局匹配(找到所有匹配)
// i: 忽略大小写
// m: 多行模式(^和$匹配行首行尾)
// s: dotAll模式(.匹配换行符)
// u: Unicode模式
// y: 粘连模式(从lastIndex位置开始匹配)

1.2 基本语法

// 字符类
/[abc]/       // 匹配a、b或c中任意一个
/[^abc]/      // 匹配除了a、b、c之外的字符
/[a-z]/       // 匹配小写字母
/[A-Z]/       // 匹配大写字母
/[0-9]/       // 匹配数字
/[a-zA-Z0-9]/ // 匹配字母和数字

// 预定义字符类
/./     // 匹配任意字符(除换行符,除非s修饰符)
/\d/    // 匹配数字 [0-9]
/\D/    // 匹配非数字 [^0-9]
/\w/    // 匹配单词字符 [a-zA-Z0-9_]
/\W/    // 匹配非单词字符
/\s/    // 匹配空白字符(空格、制表符、换行等)
/\S/    // 匹配非空白字符

// 量词
/x*/      // 匹配0次或多次
/x+/      // 匹配1次或多次
/x?/      // 匹配0次或1次
/x{n}/    // 匹配恰好n次
/x{n,}/   // 匹配至少n次
/x{n,m}/  // 匹配n到m次

// 贪婪 vs 非贪婪
/x*/      // 贪婪:尽可能多匹配
/x*?/     // 非贪婪:尽可能少匹配

// 边界
/^hello/  // 匹配以hello开头
/world$/  // 匹配以world结尾
/\bword\b/ // 匹配完整单词word

// 分组与引用
/(ab)+/     // 匹配ab一次或多次
/(a)(b)\1\2/ // \1引用第一个分组a,\2引用第二个分组b
/(?:ab)+/   // 非捕获分组,不记录匹配内容

// 或操作
/cat|dog/   // 匹配cat或dog

// 前瞻后顾
/x(?=y)/    // 正向前瞻:x后面紧跟y
/x(?!y)/    // 负向前瞻:x后面不跟y
/(?<=y)x/   // 正向后顾:x前面是y(ES2018)
/(?<!y)x/   // 负向后顾:x前面不是y(ES2018)

2. RegExp 对象方法

2.1 test 与 exec

// test: 检测是否匹配,返回布尔值
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailPattern.test('user@example.com')); // true
console.log(emailPattern.test('invalid-email')); // false

// exec: 返回匹配结果数组,包含分组信息
const datePattern = /(\d{4})-(\d{2})-(\d{2})/;
const result = datePattern.exec('Today is 2026-06-13');
console.log(result);
// ["2026-06-13", "2026", "06", "13"]
// result[0] = 完整匹配
// result[1] = 第一个分组
// result[2] = 第二个分组
// result[3] = 第三个分组
// result.index = 匹配开始位置
// result.input = 原始字符串

// 全局匹配时使用循环
const globalPattern = /\d+/g;
const text = 'a1b22c333';
let match;
while ((match = globalPattern.exec(text)) !== null) {
  console.log(`Found ${match[0]} at index ${match.index}`);
  // Found 1 at index 1
  // Found 22 at index 3
  // Found 333 at index 6
}

2.2 lastIndex 与粘连模式

// g修饰符的lastIndex
const pattern = /ab/g;
const str = 'ababab';

console.log(pattern.lastIndex); // 0
console.log(pattern.test(str)); // true, lastIndex变为2
console.log(pattern.test(str)); // true, lastIndex变为4
console.log(pattern.test(str)); // true, lastIndex变为6
console.log(pattern.test(str)); // false, lastIndex重置为0

// y修饰符(粘连模式):只从lastIndex位置匹配
const sticky = /ab/y;
sticky.lastIndex = 2;
console.log(sticky.test('abab')); // true,从位置2开始匹配"ab"
sticky.lastIndex = 1;
console.log(sticky.test('abab')); // false,位置1不是"ab"

3. 字符串方法

3.1 常用字符串正则方法

const text = 'Hello World, hello JavaScript';

// match: 返回匹配结果
console.log(text.match(/hello/i));
// ["Hello", index: 0, input: "Hello World, hello JavaScript", groups: undefined]

console.log(text.match(/hello/gi));
// ["Hello", "hello"]

console.log(text.match(/xyz/));
// null

// matchAll: 返回所有匹配的迭代器(ES2020)
const matches = [...text.matchAll(/he(l)(l)o/gi)];
for (const match of matches) {
  console.log(`Match: ${match[0]}, Groups: ${match[1]},${match[2]}`);
}

// search: 返回第一个匹配的索引
console.log(text.search(/World/)); // 6
console.log(text.search(/xyz/)); // -1

// replace: 替换匹配内容
console.log(text.replace(/hello/i, 'Hi'));
// "Hi World, hello JavaScript"

console.log(text.replace(/hello/gi, 'Hi'));
// "Hi World, Hi JavaScript"

// 使用回调函数进行替换
const result = text.replace(/hello/gi, (match, offset, string) => {
  return match.toUpperCase();
});
console.log(result); // "HELLO World, HELLO JavaScript"

// 使用分组引用
const dateStr = '2026-06-13';
const formatted = dateStr.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1');
console.log(formatted); // "13/06/2026"

// split: 使用正则分割字符串
const csv = 'one,two;three|four';
const parts = csv.split(/[,;|]/);
console.log(parts); // ["one", "two", "three", "four"]

4. 实用正则模式

4.1 表单验证

// 邮箱验证
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

// 手机号验证(中国大陆)
const phoneRegex = /^1[3-9]\d{9}$/;

// 身份证号(18位)
const idCardRegex = /^\d{17}[\dXx]$/;

// 密码强度(至少8位,包含大小写字母和数字)
const strongPasswordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;

// URL验证
const urlRegex =
  /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;

// IPv4地址
const ipv4Regex = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;

// 中文字符
const chineseRegex = /^[\u4e00-\u9fa5]+$/;

// 验证函数
function validate(value, pattern, message) {
  if (!pattern.test(value)) {
    return { valid: false, message };
  }
  return { valid: true };
}

console.log(validate('user@example.com', emailRegex, '邮箱格式不正确'));
console.log(validate('13800138000', phoneRegex, '手机号格式不正确'));

4.2 文本处理

// 提取所有URL
const text = 'Visit https://example.com and http://test.org for more info.';
const urls = text.match(/https?:\/\/[^\s]+/g);
console.log(urls); // ["https://example.com", "http://test.org"]

// 提取HTML标签内容
const html = '<div class="main"><h1>Title</h1><p>Content</p></div>';
const tags = [...html.matchAll(/<(\w+)[^>]*>(.*?)<\/\1>/gs)];
for (const tag of tags) {
  console.log(`Tag: ${tag[1]}, Content: ${tag[2]}`);
}

// 去除HTML标签
const cleanText = html.replace(/<[^>]+>/g, '');
console.log(cleanText); // "TitleContent"

// 千分位格式化
function formatNumber(num) {
  return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
console.log(formatNumber(1234567890)); // "1,234,567,890"

// 驼峰转下划线
function camelToSnake(str) {
  return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
}
console.log(camelToSnake('myVariableName')); // "my_variable_name"

// 下划线转驼峰
function snakeToCamel(str) {
  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}
console.log(snakeToCamel('my_variable_name')); // "myVariableName"

// 模板引擎简单实现
function render(template, data) {
  return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
    return data[key] !== undefined ? data[key] : match;
  });
}
const tpl = 'Hello, {{name}}! You are {{age}} years old.';
console.log(render(tpl, { name: 'Alice', age: 25 }));
// "Hello, Alice! You are 25 years old."

4.3 命名捕获组(ES2018)

// 使用命名捕获组
const datePattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const result = datePattern.exec('2026-06-13');
console.log(result.groups.year); // "2026"
console.log(result.groups.month); // "06"
console.log(result.groups.day); // "13"

// 在replace中使用命名捕获组
const formatted = '2026-06-13'.replace(datePattern, '$<day>/$<month>/$<year>');
console.log(formatted); // "13/06/2026"

5. 性能优化

5.1 预编译正则

// 避免在循环中重复创建正则
function badPractice(items) {
  return items.filter((item) => /^[a-z]+$/.test(item)); // 每次都创建新正则
}

function goodPractice(items) {
  const pattern = /^[a-z]+$/; // 预编译
  return items.filter((item) => pattern.test(item));
}

5.2 避免灾难性回溯

// 危险:嵌套量词可能导致指数级回溯
const dangerous = /(a+)+b/;

// 安全:使用占有量词或原子组(部分引擎支持)
// 或重构正则避免嵌套量词
const safe = /a+b/;

6. 常见问题与解决方案

6.1 正则中的特殊字符转义

// 转义正则特殊字符
function escapeRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

const userInput = 'price: $50';
const escaped = escapeRegExp(userInput);
const regex = new RegExp(escaped, 'g');
console.log(regex.test('The price: $50 is correct')); // true

6.2 Unicode 匹配

// u修饰符处理Unicode
const emoji = '';
console.log(/^.$/.test(emoji)); // false(emoji是2个UTF-16码元)
console.log(/^.$/u.test(emoji)); // true(u修饰符正确处理)

// Unicode属性转义(ES2018)
const greek = /\p{Script=Greek}/u;
console.log(greek.test('α')); // true

const emojiPattern = /\p{Emoji}/u;
console.log(emojiPattern.test('')); // true

7. 总结与最佳实践

7.1 核心要点

  1. 字面量 vs 构造函数:静态正则用字面量,动态正则用构造函数
  2. 贪婪 vs 非贪婪:默认贪婪,加 ? 变非贪婪
  3. 分组:捕获分组用 (),非捕获用 (?:)
  4. 修饰符g 全局、i 忽略大小写、m 多行、s dotAll

7.2 最佳实践

  1. 预编译正则:避免在循环中重复创建
  2. 使用命名捕获组:提高可读性
  3. 转义用户输入:防止注入
  4. 避免过度复杂的正则:难以维护,考虑分步处理
  5. 使用在线工具测试:如 regex101.com、regexr.com
  6. 添加注释:复杂正则使用 x 修饰符或拆分注释