正则表达式 - 知蛛网正则表达式基础
1. 创建正则表达式
// 1. 字面量语法(推荐)
const regex1 = /pattern/;
const regex2 = /pattern/flags;
// 2. 构造函数
const regex3 = new RegExp('pattern');
const regex4 = new RegExp('pattern', 'flags');
const regex5 = new RegExp(/pattern/, 'flags'); // ES6+
// 3. 动态创建(使用构造函数)
const word = 'hello';
const regex6 = new RegExp(word, 'i');
// 4. 转义特殊字符
const regex7 = /\.js$/; // 匹配以 .js 结尾
const regex8 = /\[test\]/; // 匹配 [test]
// 5. 使用字符串创建时的双重转义
const literal = /\d+/; // 匹配一个或多个数字
const constructor = new RegExp('\\d+'); // 需要双重转义
2. 匹配模式标志(flags)
// i - 不区分大小写
const caseInsensitive = /hello/i;
console.log(caseInsensitive.test("Hello")); // true
console.log(caseInsensitive.test("HELLO")); // true
// g - 全局匹配(查找所有匹配)
const global = /a/g;
const str = "abc abc";
console.log(str.match(global)); // ['a', 'a']
// m - 多行模式
const multiline = /^test/m;
const multilineStr = "first line\ntest line";
console.log(multiline.test(multilineStr)); // true
// s - dotAll模式(.匹配包括换行符的所有字符)
const dotAll = /test.+end/s;
const multilineStr2 = "test line1\nline2 end";
console.log(dotAll.test(multilineStr2)); // true
// u - Unicode模式
const unicode = /\u{1F60A}/u; // 匹配😊
console.log(unicode.test("😊")); // true
// y - 粘性匹配(从上次匹配结束位置开始)
const sticky = /a/y;
sticky.lastIndex = 1;
console.log(sticky.test("aba")); // true(从索引1开始匹配)
console.log(sticky.lastIndex); // 2
// d - 包含匹配索引信息(ES2022+)
const hasIndices = /test/d;
const result = hasIndices.exec("test string");
console.log(result.indices); // [[0, 4]]
3. 基础匹配
// 测试是否存在匹配
const regex = /hello/;
console.log(regex.test("hello world")); // true
console.log(regex.test("goodbye")); // false
// 获取匹配结果
const str = "hello world hello";
const regex2 = /hello/;
console.log(regex2.exec(str)); // ["hello", index: 0, input: "hello world hello"]
// 字符串方法
const testStr = "JavaScript is fun!";
console.log(testStr.match(/Java/)); // ["Java"]
console.log(testStr.match(/java/i)); // ["Java"](不区分大小写)
console.log(testStr.match(/java/gi)); // ["Java"](全局+不区分大小写)
console.log(testStr.search(/fun/)); // 14(匹配位置)
console.log(testStr.replace(/fun/, "awesome")); // "JavaScript is awesome!"
console.log(testStr.split(/\s+/)); // ["JavaScript", "is", "fun!"]
字符与字符类
1. 特殊字符
// 字面量字符
const literal = /hello/; // 匹配 "hello"
// 元字符(需要转义)
// . \ + * ? ^ $ { } [ ] ( ) | /
// 点号 . - 匹配除换行符外的任意单个字符
const dot = /h.llo/;
console.log(dot.test("hello")); // true
console.log(dot.test("hallo")); // true
console.log(dot.test("h\nllo")); // false
// 在 dotAll 模式下匹配所有字符
const dotAll = /h.llo/s;
console.log(dotAll.test("h\nllo")); // true
2. 字符类
// 简单字符类 []
const vowel = /[aeiou]/; // 匹配任意元音字母
console.log(vowel.test("hello")); // true
console.log(vowel.test("xyz")); // false
// 范围字符类
const lowercase = /[a-z]/; // 匹配任意小写字母
const uppercase = /[A-Z]/; // 匹配任意大写字母
const digits = /[0-9]/; // 匹配任意数字
const alphanumeric = /[a-zA-Z0-9]/; // 匹配字母数字
// 排除字符类 [^]
const notVowel = /[^aeiou]/; // 匹配非元音字符
console.log(notVowel.test("a")); // false
console.log(notVowel.test("b")); // true
// 组合字符类
const hex = /[0-9a-fA-F]/; // 匹配十六进制字符
const identifier = /[a-zA-Z_$][a-zA-Z0-9_$]*/; // JavaScript标识符
// Unicode字符类
const chinese = /[\u4e00-\u9fff]/; // 匹配中文字符
const emoji = /\p{Emoji}/u; // 匹配表情符号(ES2018+)
const letter = /\p{Letter}/u; // 匹配任意字母
3. 预定义字符类
// \d - 数字(等价于 [0-9])
const digit = /\d/;
console.log(digit.test("123")); // true
console.log(digit.test("abc")); // false
// \D - 非数字(等价于 [^0-9])
const notDigit = /\D/;
console.log(notDigit.test("123")); // false
console.log(notDigit.test("abc")); // true
// \w - 单词字符(等价于 [a-zA-Z0-9_])
const wordChar = /\w/;
console.log(wordChar.test("a")); // true
console.log(wordChar.test("_")); // true
console.log(wordChar.test("@")); // false
// \W - 非单词字符
const notWordChar = /\W/;
console.log(notWordChar.test("a")); // false
console.log(notWordChar.test("@")); // true
// \s - 空白字符(空格、制表符、换行符等)
const whitespace = /\s/;
console.log(whitespace.test(" ")); // true
console.log(whitespace.test("\t")); // true
console.log(whitespace.test("a")); // false
// \S - 非空白字符
const notWhitespace = /\S/;
console.log(notWhitespace.test(" ")); // false
console.log(notWhitespace.test("a")); // true
// . - 除换行符外的任意字符
const anyChar = /./;
console.log(anyChar.test("a")); // true
console.log(anyChar.test("\n")); // false(除非使用s标志)
// \b - 单词边界
const wordBoundary = /\bword\b/;
console.log(wordBoundary.test("hello world")); // true
console.log(wordBoundary.test("hello worldview")); // false
// \B - 非单词边界
const notWordBoundary = /\Bword\B/;
console.log(notWordBoundary.test("hello worldview")); // true
// \0 - 空字符
// \n - 换行符
// \r - 回车符
// \t - 制表符
// \v - 垂直制表符
// \f - 换页符
量词与分组
1. 量词
// * - 0次或多次
const zeroOrMore = /ab*c/;
console.log(zeroOrMore.test("ac")); // true
console.log(zeroOrMore.test("abc")); // true
console.log(zeroOrMore.test("abbbc")); // true
// + - 1次或多次
const oneOrMore = /ab+c/;
console.log(oneOrMore.test("ac")); // false
console.log(oneOrMore.test("abc")); // true
console.log(oneOrMore.test("abbbc")); // true
// ? - 0次或1次
const zeroOrOne = /ab?c/;
console.log(zeroOrOne.test("ac")); // true
console.log(zeroOrOne.test("abc")); // true
console.log(zeroOrOne.test("abbbc")); // false
// {n} - 恰好n次
const exactlyThree = /a{3}/;
console.log(exactlyThree.test("aaa")); // true
console.log(exactlyThree.test("aa")); // false
console.log(exactlyThree.test("aaaa")); // true(匹配前3个a)
// {n,} - 至少n次
const atLeastTwo = /a{2,}/;
console.log(atLeastTwo.test("a")); // false
console.log(atLeastTwo.test("aa")); // true
console.log(atLeastTwo.test("aaaa")); // true
// {n,m} - n到m次
const betweenTwoAndFour = /a{2,4}/;
console.log(betweenTwoAndFour.test("a")); // false
console.log(betweenTwoAndFour.test("aa")); // true
console.log(betweenTwoAndFour.test("aaaa")); // true
console.log(betweenTwoAndFour.test("aaaaa")); // true(匹配前4个a)
// 贪婪匹配(默认)
const greedy = /a+/;
console.log("aaa".match(greedy)); // ["aaa"]
// 惰性匹配(非贪婪)
const lazy = /a+?/;
console.log("aaa".match(lazy)); // ["a"]
// 独占匹配(防止回溯)
const possessive = /a++/; // JavaScript不支持独占量词
2. 分组
// 捕获分组 ()
const capture = /(\d{4})-(\d{2})-(\d{2})/;
const date = "2024-01-15";
const match = date.match(capture);
console.log(match[0]); // "2024-01-15"(完整匹配)
console.log(match[1]); // "2024"(第一个分组)
console.log(match[2]); // "01"(第二个分组)
console.log(match[3]); // "15"(第三个分组)
// 命名捕获分组 ?<name>(ES2018+)
const namedCapture = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const namedMatch = date.match(namedCapture);
console.log(namedMatch.groups.year); // "2024"
console.log(namedMatch.groups.month); // "01"
console.log(namedMatch.groups.day); // "15"
// 非捕获分组 ?:
const nonCapture = /(?:Mr|Ms|Mrs)\. (\w+)/;
const nameMatch = "Mr. Smith".match(nonCapture);
console.log(nameMatch[0]); // "Mr. Smith"
console.log(nameMatch[1]); // "Smith"
console.log(nameMatch[2]); // undefined(?:不捕获)
// 分组引用
const duplicateWords = /\b(\w+)\s+\1\b/;
console.log(duplicateWords.test("hello hello")); // true
console.log(duplicateWords.test("hello world")); // false
// 命名分组引用 \k<name>
const namedDuplicate = /\b(?<word>\w+)\s+\k<word>\b/;
console.log(namedDuplicate.test("test test")); // true
// 在替换中使用分组
const reformatDate = "2024-01-15".replace(/(\d{4})-(\d{2})-(\d{2})/, "$2/$3/$1");
console.log(reformatDate); // "01/15/2024"
// 在替换中使用命名分组
const reformatNamed = "2024-01-15".replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
"$<month>/$<day>/$<year>"
);
console.log(reformatNamed); // "01/15/2024"
3. 选择与断言
// 选择符 |
const or = /cat|dog|bird/;
console.log(or.test("I have a cat")); // true
console.log(or.test("I have a dog")); // true
console.log(or.test("I have a fish")); // false
// 分组中的选择
const animal = /(cat|dog|bird)s?/;
console.log(animal.test("cats")); // true
console.log(animal.test("dog")); // true
// 正向先行断言 ?=(后面是...)
const followedBy = /\d+(?= dollars)/;
console.log(followedBy.exec("100 dollars")); // ["100"]
console.log(followedBy.exec("100 yen")); // null
// 负向先行断言 ?!(后面不是...)
const notFollowedBy = /\d+(?! dollars)/;
console.log(notFollowedBy.exec("100 dollars")); // null
console.log(notFollowedBy.exec("100 yen")); // ["100"]
// 正向后行断言 ?<=(前面是...)
const precededBy = /(?<=\$)\d+/;
console.log(precededBy.exec("Price: $100")); // ["100"]
console.log(precededBy.exec("Price: 100")); // null
// 负向后行断言 ?<!(前面不是...)
const notPrecededBy = /(?<!\$)\d+/;
console.log(notPrecededBy.exec("Price: $100")); // null
console.log(notPrecededBy.exec("Price: 100")); // ["100"]
// 实用断言示例
// 匹配不在引号内的单词
const notInQuotes = /(?<!["'])\bword\b(?!["'])/;
// 匹配独立的数字(不是其他数字的一部分)
const standaloneNumber = /(?<!\d)\d+(?!\d)/;
正则表达式方法
1. RegExp 对象方法
const regex = /test/gi;
// test() - 测试是否匹配
console.log(regex.test("test Test TEST")); // true
// exec() - 执行匹配,返回详细信息
regex.lastIndex = 0; // 重置位置
console.log(regex.exec("test Test TEST"));
// ["test", index: 0, input: "test Test TEST", groups: undefined]
regex.lastIndex = 0;
const result = regex.exec("test Test TEST");
console.log(result[0]); // 匹配的文本
console.log(result.index); // 匹配开始的位置
console.log(result.input); // 原始字符串
// toString() - 返回正则表达式字符串
console.log(regex.toString()); // "/test/gi"
// source - 获取正则表达式源码(不含标志)
console.log(regex.source); // "test"
// flags - 获取标志字符串
console.log(regex.flags); // "gi"
// lastIndex - 下次匹配开始的位置(仅对g或y标志有效)
const globalRegex = /a/g;
globalRegex.test("abc");
console.log(globalRegex.lastIndex); // 1
globalRegex.test("abc");
console.log(globalRegex.lastIndex); // 2
2. String 对象方法
const str = "Hello World! Hello JavaScript!";
// match() - 返回匹配结果
console.log(str.match(/hello/gi)); // ["Hello", "Hello"]
console.log(str.match(/hello/i)); // ["Hello"](第一个匹配)
// matchAll() - 返回所有匹配的迭代器(ES2020+)
const matches = str.matchAll(/hello/gi);
for (const match of matches) {
console.log(match[0], match.index);
}
// "Hello" 0
// "Hello" 13
// search() - 返回第一个匹配的位置
console.log(str.search(/world/i)); // 6
console.log(str.search(/notfound/)); // -1
// replace() - 替换匹配的文本
console.log(str.replace(/hello/gi, "Hi")); // "Hi World! Hi JavaScript!"
// replace() 使用函数
console.log(
str.replace(/hello/gi, (match, offset) => {
return match.toUpperCase() + `(${offset})`;
})
); // "HELLO(0) World! HELLO(13) JavaScript!"
// split() - 使用正则表达式分割字符串
console.log("a,b, c, d".split(/\s*,\s*/)); // ["a", "b", "c", "d"]
console.log("hello world".split(/\s+/)); // ["hello", "world"]
// split() 使用分组(包含分隔符)
console.log("hello world".split(/(\s+)/)); // ["hello", " ", "world"]
高级模式与应用
1. 常用正则表达式模式
// 邮箱验证
const email = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(email.test("test@example.com")); // true
console.log(email.test("test@example.co.uk")); // true
console.log(email.test("test@example")); // false
// 手机号码(中国)
const mobileCN = /^(?:(?:\+|00)86)?1[3-9]\d{9}$/;
console.log(mobileCN.test("13800138000")); // true
console.log(mobileCN.test("+8613800138000")); // true
console.log(mobileCN.test("12800138000")); // false
// URL验证
const url = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/;
console.log(url.test("https://www.example.com")); // true
console.log(url.test("http://example.com/path")); // true
// 身份证号码(中国)
const idCard = /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dX]$/;
console.log(idCard.test("110101199003075678")); // true
// 密码强度验证
const strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
console.log(strongPassword.test("Password123!")); // true
// IPv4地址
const ipv4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
console.log(ipv4.test("192.168.1.1")); // true
// 中文姓名
const chineseName = /^[\u4e00-\u9fa5]{2,4}$/;
console.log(chineseName.test("张三")); // true
console.log(chineseName.test("李小龙")); // true
// 日期格式(YYYY-MM-DD)
const dateFormat = /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$/;
console.log(dateFormat.test("2024-01-15")); // true
console.log(dateFormat.test("2024-1-15")); // false
// 提取HTML标签内容
const htmlTag = /<([a-z]+)(?:\s+[^>]*)?>([^<]*)<\/\1>/i;
const html = '<div class="test">Hello World</div>';
const tagMatch = html.match(htmlTag);
console.log(tagMatch[2]); // "Hello World"
2. 验证与提取工具
// 验证工具函数
const Validator = {
// 邮箱验证
isEmail(email) {
return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email);
},
// 手机号验证
isMobile(mobile, country = "CN") {
const patterns = {
CN: /^(?:(?:\+|00)86)?1[3-9]\d{9}$/,
US: /^\+1\d{10}$/,
UK: /^\+44\d{10}$/,
};
return patterns[country]?.test(mobile) || false;
},
// URL验证
isURL(url) {
try {
new URL(url);
return true;
} catch {
return /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/.test(url);
}
},
// 身份证验证
isIDCard(id, country = "CN") {
if (country === "CN") {
if (
!/^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dX]$/.test(
id
)
) {
return false;
}
// 校验码验证(简化版)
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
const checkCodes = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"];
let sum = 0;
for (let i = 0; i < 17; i++) {
sum += parseInt(id[i]) * weights[i];
}
const checkCode = checkCodes[sum % 11];
return id[17].toUpperCase() === checkCode;
}
return false;
},
// 密码强度检查
checkPasswordStrength(password) {
const checks = {
length: password.length >= 8,
lowercase: /[a-z]/.test(password),
uppercase: /[A-Z]/.test(password),
number: /\d/.test(password),
special: /[@$!%*?&]/.test(password),
};
const score = Object.values(checks).filter(Boolean).length;
return {
score,
level: score >= 4 ? "strong" : score >= 3 ? "medium" : "weak",
checks,
};
},
};
// 提取工具函数
const Extractor = {
// 提取所有邮箱
extractEmails(text) {
const pattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
return text.match(pattern) || [];
},
// 提取所有URL
extractURLs(text) {
const pattern = /https?:\/\/[^\s/$.?#].[^\s]*/g;
return text.match(pattern) || [];
},
// 提取所有电话号码
extractPhoneNumbers(text, country = "CN") {
const patterns = {
CN: /(?:(?:\+|00)86)?1[3-9]\d{9}/g,
US: /\+1\d{10}/g,
};
return text.match(patterns[country]) || [];
},
// 提取JSON对象
extractJSON(text) {
const pattern = /\{(?:[^{}]|(?:\{[^{}]*\}))*\}/g;
const matches = text.match(pattern) || [];
return matches
.map(match => {
try {
return JSON.parse(match);
} catch {
return null;
}
})
.filter(Boolean);
},
// 提取中文字符
extractChinese(text) {
const pattern = /[\u4e00-\u9fff]+/g;
return text.match(pattern) || [];
},
// 提取哈希标签
extractHashtags(text) {
const pattern = /#[\w\u4e00-\u9fff]+/g;
return text.match(pattern) || [];
},
// 提取@提及
extractMentions(text) {
const pattern = /@[\w\u4e00-\u9fff]+/g;
return text.match(pattern) || [];
},
};
// 使用示例
console.log(Validator.isEmail("test@example.com")); // true
console.log(Validator.checkPasswordStrength("Password123!"));
console.log(Extractor.extractEmails("Contact: test@example.com, support@test.com"));
3. 文本处理与清洗
// 文本处理工具
const TextProcessor = {
// 移除HTML标签
stripHTML(html) {
return html.replace(/<[^>]*>/g, "");
},
// 移除多余空白
normalizeWhitespace(text) {
return text.replace(/\s+/g, " ").trim();
},
// 移除标点符号
removePunctuation(text) {
return text.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "");
},
// 提取纯文本(移除所有非字母数字和中文)
extractPlainText(text) {
return text.replace(/[^\w\u4e00-\u9fff\s]/g, "");
},
// 单词计数
wordCount(text) {
const words = text.match(/\b[\w\u4e00-\u9fff]+\b/g) || [];
return words.length;
},
// 句子分割
splitSentences(text) {
return text.split(/[.!?]+/).filter(s => s.trim());
},
// 首字母大写
toTitleCase(text) {
return text.toLowerCase().replace(/\b\w/g, char => char.toUpperCase());
},
// 驼峰转下划线
camelToSnake(camelCase) {
return camelCase.replace(/[A-Z]/g, match => "_" + match.toLowerCase());
},
// 下划线转驼峰
snakeToCamel(snakeCase) {
return snakeCase.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
},
// 截断文本并添加省略号
truncate(text, maxLength, suffix = "...") {
if (text.length <= maxLength) return text;
return text.substr(0, maxLength - suffix.length).replace(/\s+\S*$/, "") + suffix;
},
// 生成摘要(提取前N个句子)
generateSummary(text, sentenceCount = 2) {
const sentences = this.splitSentences(text);
return sentences.slice(0, sentenceCount).join(". ") + ".";
},
// 查找和替换(支持正则表达式)
findAndReplace(text, find, replace) {
const regex = new RegExp(find, "gi");
return text.replace(regex, replace);
},
// 高亮关键词
highlightKeywords(text, keywords) {
const escapedKeywords = keywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
const pattern = new RegExp(`(${escapedKeywords.join("|")})`, "gi");
return text.replace(pattern, "<mark>$1</mark>");
},
};
// 使用示例
const html = "<div><h1>标题</h1><p>这是一段文本。</p></div>";
console.log(TextProcessor.stripHTML(html)); // "标题这是一段文本。"
console.log(TextProcessor.wordCount("Hello World 你好世界")); // 4
性能优化
1. 编译与缓存
// 1. 缓存正则表达式
const regexCache = new Map();
function getCachedRegex(pattern, flags) {
const key = pattern + "|" + flags;
if (!regexCache.has(key)) {
regexCache.set(key, new RegExp(pattern, flags));
}
return regexCache.get(key);
}
// 使用缓存的regex
const emailRegex = getCachedRegex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
console.log(emailRegex.test("test@example.com"));
// 2. 预编译常用正则表达式
const CommonRegex = {
EMAIL: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
URL: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/,
PHONE: /^(?:(?:\+|00)86)?1[3-9]\d{9}$/,
ID_CARD: /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dX]$/,
CHINESE: /[\u4e00-\u9fff]/,
DIGITS: /\d+/g,
WHITESPACE: /\s+/g,
};
// 3. 使用字面量而非构造函数(性能更好)
// 好
const goodRegex = /\d+/g;
// 不好(每次都会编译)
const badRegex = new RegExp("\\d+", "g");
2. 优化技巧
// 1. 避免回溯爆炸
// 不好:可能导致灾难性回溯
const badRegex = /(a+)+b/;
// 好:使用原子组(JavaScript不支持)或修改模式
const goodRegex = /a+b/;
// 2. 使用具体字符类而非通用字符
// 不好
const slow = /.*@.*\..*/;
// 好
const fast = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
// 3. 使用锚点加速
// 不好
const noAnchor = /\d{4}-\d{2}-\d{2}/;
// 好
const withAnchor = /^\d{4}-\d{2}-\d{2}$/;
// 4. 减少捕获分组
// 不好:不必要的捕获
const withCapture = /(\d{4})-(\d{2})-(\d{2})/;
// 好:使用非捕获分组或命名分组
const withoutCapture = /(?:\d{4})-(?:\d{2})-(?:\d{2})/;
const namedGroups = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
// 5. 避免重复编译
function processText(text, pattern) {
// 不好:每次调用都编译
const regex = new RegExp(pattern, "g");
return text.match(regex);
}
function createProcessor(pattern) {
// 好:只编译一次
const regex = new RegExp(pattern, "g");
return text => text.match(regex);
}
const emailProcessor = createProcessor("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");
// 6. 使用test()而非exec()进行存在性检查
// 好:只需要检查是否存在
if (/\d+/.test(text)) {
// 处理
}
// 不好:获取不需要的详细信息
const match = /\d+/.exec(text);
if (match) {
// 处理
}
3. 性能测试
// 正则表达式性能测试工具
function benchmarkRegex(name, regex, testString, iterations = 10000) {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
regex.test(testString);
}
const end = performance.now();
const duration = end - start;
console.log(`${name}: ${duration.toFixed(2)}ms (${(duration / iterations).toFixed(4)}ms/op)`);
return duration;
}
// 测试不同模式
const testString = "test@example.com";
// 贪婪 vs 惰性
benchmarkRegex("Greedy", /.*@.*\..*/, testString);
benchmarkRegex("Lazy", /.*?@.*?\..*?/, testString);
// 有无锚点
benchmarkRegex("No anchor", /\d{4}-\d{2}-\d{2}/, "2024-01-15");
benchmarkRegex("With anchor", /^\d{4}-\d{2}-\d{2}$/, "2024-01-15");
// 缓存效果
const uncachedTime = benchmarkRegex("Uncached", new RegExp("\\d+"), "12345");
const cachedRegex = /\d+/;
const cachedTime = benchmarkRegex("Cached", cachedRegex, "12345");
console.log(
`Caching improvement: ${(((uncachedTime - cachedTime) / uncachedTime) * 100).toFixed(2)}%`
);
调试与测试
1. 调试工具
// 正则表达式调试器
class RegexDebugger {
constructor(pattern, flags = '') {
this.regex = new RegExp(pattern, flags);
this.pattern = pattern;
this.flags = flags;
this.matches = [];
}
// 详细匹配
debug(text) {
console.log('=== Regex Debug ===');
console.log('Pattern:', this.pattern);
console.log('Flags:', this.flags);
console.log('Test string:', text);
console.log('---');
this.regex.lastIndex = 0;
let match;
this.matches = [];
while ((match = this.regex.exec(text)) !== null) {
console.log(`Match #${this.matches.length + 1}:`);
console.log(' Full match:', match[0]);
console.log(' Index:', match.index);
if (match.groups) {
console.log(' Named groups:', match.groups);
}
for (let i = 1; i < match.length; i++) {
if (match[i] !== undefined) {
console.log(` Group ${i}:`, match[i]);
}
}
console.log('---');
this.matches.push(match);
}
if (this.matches.length === 0) {
console.log('No matches found.');
}
return this.matches;
}
// 可视化分组
visualizeGroups(text) {
const match = this.regex.exec(text);
if (!match) {
console.log('No match');
return;
}
console.log('Group visualization:');
console.log('Full match:', match[0]);
console.log(' ');
for (let i = 0; i < match[0].length; i++) {
let groupNum = 0;
for (let g = 1; g < match.length; g++) {
if (match[g] && i >= match.index && i < match.index + match[0].length) {
// 简单可视化分组
console.log(`Position ${i}: "${match[0][i]}"`);
}
}
}
}
// 测试多个字符串
testCases(cases) {
console.log('=== Test Cases ===');
cases.forEach((testCase, index) => {
console.log(`\nTest ${index + 1}: "${testCase}"`);
const result = this.regex.test(testCase);
console.log('Result:', result ? '✓ PASS' : '✗ FAIL');
if (result) {
const match = this.regex.exec(testCase);
console.log('Match:', match[0]);
this.regex.lastIndex = 0; // 重置
}
});
}
}
// 使用示例
const debugger = new RegexDebugger('(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})');
debugger.debug('Date: 2024-01-15');
debugger.testCases(['2024-01-15', '2024/01/15', 'invalid-date']);
2. 单元测试
// 正则表达式测试套件
class RegexTestSuite {
constructor() {
this.tests = [];
}
// 添加测试用例
addTest(name, pattern, testCases) {
this.tests.push({ name, pattern, testCases });
}
// 运行所有测试
runAll() {
console.log("Running Regex Tests...\n");
let passed = 0;
let failed = 0;
this.tests.forEach((test, index) => {
console.log(`Test ${index + 1}: ${test.name}`);
console.log(`Pattern: ${test.pattern}`);
const regex = new RegExp(test.pattern);
let testPassed = true;
test.testCases.forEach(testCase => {
const result = regex.test(testCase.input);
const passed = result === testCase.expected;
if (!passed) {
testPassed = false;
console.log(` ✗ FAIL: "${testCase.input}"`);
console.log(` Expected: ${testCase.expected}, Got: ${result}`);
}
regex.lastIndex = 0; // 重置
});
if (testPassed) {
console.log(` ✓ PASS (${test.testCases.length} cases)`);
passed++;
} else {
failed++;
}
console.log();
});
console.log(`\nSummary: ${passed} passed, ${failed} failed`);
return { passed, failed, total: passed + failed };
}
}
// 使用示例
const suite = new RegexTestSuite();
// 测试邮箱正则
suite.addTest("Email Validation", "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", [
{ input: "test@example.com", expected: true },
{ input: "test@example.co.uk", expected: true },
{ input: "test@example", expected: false },
{ input: "@example.com", expected: false },
]);
// 测试日期正则
suite.addTest("Date Format (YYYY-MM-DD)", "^\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])$", [
{ input: "2024-01-15", expected: true },
{ input: "2024-1-15", expected: false },
{ input: "2024-13-01", expected: false },
{ input: "2024-01-32", expected: false },
]);
// 运行测试
suite.runAll();
高级技巧与模式
1. 递归匹配
// 匹配嵌套结构(如括号)
function matchNestedPairs(text, open = "(", close = ")") {
const regex = new RegExp(
`\\${open}(?:(?!\\${open}|\\${close}).|\\${open}(?<depth>)|\\)(?<-depth>))*(?(depth)(?!))\\${close}`,
"g"
);
const matches = [];
let match;
while ((match = regex.exec(text)) !== null) {
matches.push(match[0]);
}
return matches;
}
// 使用平衡组(需要递归模式支持,JavaScript有限支持)
const nestedParentheses = /\((?:[^()]|(?R))*\)/;
// 替代方案:使用函数递归处理
function extractNested(text, open = "(", close = ")") {
const result = [];
let depth = 0;
let current = "";
let startIndex = -1;
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (char === open) {
if (depth === 0) {
startIndex = i;
}
depth++;
}
if (depth > 0) {
current += char;
}
if (char === close) {
depth--;
if (depth === 0) {
result.push({
text: current,
start: startIndex,
end: i,
});
current = "";
}
}
}
return result;
}
// 示例
console.log(extractNested("a(b(c)d)e(f)g"));
2. 复杂验证
// 信用卡验证
function validateCreditCard(cardNumber) {
// 移除空格和破折号
const cleaned = cardNumber.replace(/[\s-]/g, "");
// 检查基本格式
if (!/^\d{13,19}$/.test(cleaned)) {
return { valid: false, reason: "Invalid length" };
}
// Luhn算法校验
let sum = 0;
let isSecond = false;
for (let i = cleaned.length - 1; i >= 0; i--) {
let digit = parseInt(cleaned[i]);
if (isSecond) {
digit *= 2;
if (digit > 9) {
digit -= 9;
}
}
sum += digit;
isSecond = !isSecond;
}
const luhnValid = sum % 10 === 0;
// 卡类型识别
const cardTypes = {
Visa: /^4[0-9]{12}(?:[0-9]{3})?$/,
Mastercard: /^5[1-5][0-9]{14}$/,
"American Express": /^3[47][0-9]{13}$/,
Discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/,
UnionPay: /^62[0-9]{14,17}$/,
};
let cardType = "Unknown";
for (const [type, pattern] of Object.entries(cardTypes)) {
if (pattern.test(cleaned)) {
cardType = type;
break;
}
}
return {
valid: luhnValid,
cardType,
formatted: cleaned.replace(/(\d{4})(?=\d)/g, "$1 "),
luhnValid,
};
}
// 使用示例
console.log(validateCreditCard("4111 1111 1111 1111"));
3. 模板引擎实现
// 简单的模板引擎
class TemplateEngine {
constructor(template) {
this.template = template;
this.variables = {};
}
// 设置变量
set(name, value) {
this.variables[name] = value;
return this;
}
// 渲染模板
render() {
let result = this.template;
// 替换变量 {{variable}}
result = result.replace(/\{\{(\w+)\}\}/g, (match, name) => {
return this.variables[name] !== undefined ? this.variables[name] : match;
});
// 处理条件语句 {% if condition %}...{% endif %}
result = result.replace(
/\{%\s*if\s+(\w+)\s*%\}(.*?)\{%\s*endif\s*%\}/gs,
(match, condition, content) => {
return this.variables[condition] ? content : "";
}
);
// 处理循环 {% for item in items %}...{% endfor %}
result = result.replace(
/\{%\s*for\s+(\w+)\s+in\s+(\w+)\s*%\}(.*?)\{%\s*endfor\s*%\}/gs,
(match, itemName, arrayName, content) => {
const items = this.variables[arrayName] || [];
return items
.map(item => {
const itemContext = { ...this.variables, [itemName]: item };
return content.replace(/\{\{(\w+)\}\}/g, (match, name) => {
return itemContext[name] !== undefined ? itemContext[name] : match;
});
})
.join("");
}
);
return result;
}
}
// 使用示例
const template = `
Hello {{name}}!
{% if showDetails %}
Details:
{% for item in items %}
- {{item}}
{% endfor %}
{% endif %}
`;
const engine = new TemplateEngine(template);
engine.set("name", "John").set("showDetails", true).set("items", ["Item 1", "Item 2", "Item 3"]);
console.log(engine.render());