第 6 章:高级断言技术
2025/9/1大约 9 分钟
第 6 章:高级断言技术
学习目标
- 深入理解正向先行断言(?=)
- 掌握负向先行断言(?!)
- 学会使用正向后行断言(?<=)
- 掌握负向后行断言(?<!)
- 理解断言的组合使用技巧
6.1 断言概述
断言(Assertions)是零宽度的匹配,它们不消耗字符,只是检查某个位置是否满足特定条件。断言让我们能够进行更精确的模式匹配。
断言的类型
- 先行断言(Lookahead):检查当前位置后面的内容
- 后行断言(Lookbehind):检查当前位置前面的内容
- 正向断言:检查是否匹配特定模式
- 负向断言:检查是否不匹配特定模式
断言的特点
- 零宽度:不占用匹配结果中的字符
- 位置性:只检查位置,不匹配内容
- 条件性:基于条件进行匹配判断
6.2 正向先行断言 (?=)
正向先行断言 (?=pattern)
匹配后面紧跟特定模式的位置。
基础语法
// 语法:主模式(?=先行条件)
const pattern = /foo(?=bar)/;
console.log("foobar".match(pattern)); // ["foo"]
console.log("foobaz".match(pattern)); // null
// 匹配位置示例
const text = "foobar";
console.log(text.replace(/foo(?=bar)/, "XXX")); // "XXXbar"
实际应用
密码强度验证
// 验证密码包含大写字母、小写字母和数字,长度8-16位
const strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,16}$/;
function validatePassword(password) {
return strongPassword.test(password);
}
console.log(validatePassword("Password123")); // true
console.log(validatePassword("password")); // false (缺少大写和数字)
console.log(validatePassword("PASSWORD123")); // false (缺少小写)
查找特定上下文中的单词
// 查找后面跟着数字的单词
const wordBeforeNumber = /\w+(?=\s+\d+)/g;
const text = "item 123, product 456, name abc, count 789";
console.log(text.match(wordBeforeNumber)); // ["item", "product", "count"]
// 查找HTML标签内的属性
const attributePattern = /\w+(?==)/g; // 属性名后面跟着等号
const html = '<div class="container" id="main" data-value="test">';
console.log(html.match(attributePattern)); // ["class", "id", "data-value"]
复杂的先行断言
// 匹配后面不是特定单词的"the"
const theNotFollowedByEnd = /\bthe(?!\s+end\b)/gi;
const text = "the beginning, the middle, the end";
console.log(text.match(theNotFollowedByEnd)); // ["the", "the"]
// 匹配文件名但不包括扩展名
const filenameWithoutExt = /[\w-]+(?=\.\w+)/g;
const files = "document.pdf image.jpg script.js readme.txt";
console.log(files.match(filenameWithoutExt)); // ["document", "image", "script", "readme"]
6.3 负向先行断言 (?!)
负向先行断言 (?!pattern)
匹配后面不跟特定模式的位置。
基础用法
// 匹配不后跟"bar"的"foo"
const pattern = /foo(?!bar)/;
console.log("foobar".match(pattern)); // null
console.log("foobaz".match(pattern)); // ["foo"]
// 实际应用示例
const text = "Java JavaScript Python";
// 匹配"Java"但不是"JavaScript"
const javaNotScript = /Java(?!Script)/g;
console.log(text.match(javaNotScript)); // ["Java"]
实际应用
验证不包含特定字符的字符串
// 验证密码不包含常见的弱密码模式
const noWeakPattern = /^(?!.*(?:123|abc|password|admin)).+$/i;
function isNotWeakPassword(password) {
return noWeakPattern.test(password);
}
console.log(isNotWeakPassword("mypassword123")); // false (包含123)
console.log(isNotWeakPassword("StrongPass456")); // true
匹配非注释行
// 匹配不以//开头的代码行
const nonCommentLine = /^(?!\s*\/\/).*\S.*/gm;
const code = `
// This is a comment
console.log("Hello");
// Another comment
const x = 42;
`;
const codeLines = code.match(nonCommentLine);
console.log(codeLines); // ['console.log("Hello");', 'const x = 42;']
6.4 正向后行断言 (?<=)
正向后行断言 (?<=pattern)
匹配前面是特定模式的位置。
基础用法
// 匹配前面是"$"的数字
const pricePattern = /(?<=\$)\d+(\.\d{2})?/g;
const text = "The price is $19.99 and tax is 5%.";
console.log(text.match(pricePattern)); // ["19.99"]
// 匹配标签内的内容
const tagContent = /(?<=<b>).*?(?=<\/b>)/g;
const html = "<b>Bold text</b> and <b>more bold</b>";
console.log(html.match(tagContent)); // ["Bold text", "more bold"]
实际应用
提取特定格式的数据
// 提取@符号后的用户名
const username = /(?<=@)\w+/g;
const text = "Contact @john or @sarah for help";
console.log(text.match(username)); // ["john", "sarah"]
// 提取引号后的内容
const quotedContent = /(?<=["'])[^"']*(?=["'])/g;
const text2 = 'He said "Hello" and she replied \'Hi there\'';
console.log(text2.match(quotedContent)); // ["Hello", "Hi there"]
格式化替换
// 在数字前添加货币符号,但只对特定上下文
const text = "Price: 100, Cost: 50, ID: 123";
// 只对Price和Cost后的数字添加$符号
const formatted = text.replace(/(?<=Price: |Cost: )\d+/g, "$$$&");
console.log(formatted); // "Price: $100, Cost: $50, ID: 123"
6.5 负向后行断言 (?<!)
负向后行断言 (?<!pattern)
匹配前面不是特定模式的位置。
基础用法
// 匹配不在$符号后的数字
const nonPriceNumbers = /(?<!\$)\b\d+(?:\.\d{2})?\b/g;
const text = "Price $19.99, quantity 5, ID 12345";
console.log(text.match(nonPriceNumbers)); // ["5", "12345"]
实际应用
避免匹配特定上下文中的模式
// 匹配不在HTML标签内的文本
const nonTagText = /(?<!<[^>]*)\b\w+\b(?![^<]*>)/g;
const html = '<div class="container">Hello world</div>';
// 注意:这个例子在JavaScript中可能需要更复杂的实现
// 更实用的例子:匹配不在注释中的代码
const nonCommentCode = /(?<!\/\/.*)\bconsole\.log\b(?!.*\/\/)/g;
const code = `
console.log("active"); // This is active
// console.log("commented");
let x = console.log("also active");
`;
console.log(code.match(nonCommentCode)); // 匹配未被注释的console.log
数据清理
// 移除不在引号内的空格
function removeUnquotedSpaces(text) {
// 这是一个简化的示例,实际实现会更复杂
return text.replace(/(?<!["'])\s+(?!["'])/g, '');
}
// 匹配不是转义字符的特殊字符
const unescapedSpecial = /(?<!\\)[\\'"]/g;
const text = 'This is a "quote" and this is \\"escaped\\"';
console.log(text.match(unescapedSpecial)); // ['"', '"']
6.6 断言的组合使用
断言可以组合使用,创建复杂的匹配条件。
多个先行断言
// 验证密码:至少8位,包含大写、小写、数字和特殊字符
const complexPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/;
function validateComplexPassword(password) {
return complexPassword.test(password);
}
console.log(validateComplexPassword("Password123!")); // true
console.log(validateComplexPassword("password123")); // false (缺少大写和特殊字符)
先行和后行断言结合
// 提取被特定标记包围的内容
const betweenMarkers = /(?<=START:).*?(?=:END)/g;
const text = "START:important data:END and START:more data:END";
console.log(text.match(betweenMarkers)); // ["important data", "more data"]
// 匹配特定上下文中的单词
const contextualWord = /(?<=\b(?:Mr|Mrs|Ms)\.?\s+)\w+(?=\s)/g;
const names = "Mr. John Smith, Mrs. Jane Doe, Ms. Sarah Connor";
console.log(names.match(contextualWord)); // ["John", "Jane", "Sarah"]
嵌套断言
// 复杂的URL验证:确保包含协议但不是本地地址
const validUrl = /^(?=https?:\/\/)(?!.*localhost)(?!.*127\.0\.0\.1).+$/;
console.log(validUrl.test("https://example.com")); // true
console.log(validUrl.test("https://localhost:3000")); // false
console.log(validUrl.test("http://127.0.0.1:8080")); // false
6.7 实际应用案例
代码分析工具
// 查找未使用的变量(简化版本)
function findUnusedVariables(code) {
// 查找变量声明
const declarations = code.match(/(?:let|const|var)\s+(\w+)/g);
// 查找变量使用(不在声明语句中)
const usages = code.match(/(?<!(?:let|const|var)\s+)\b\w+(?=\s*[^\s=])/g);
// 实际应用中需要更复杂的逻辑来正确分析
return declarations?.filter(decl => {
const varName = decl.match(/\w+$/)[0];
return !usages?.includes(varName);
});
}
数据验证
// 验证信用卡号(基于Luhn算法的简化版本)
const creditCardPattern = /^(?=(?:\d{4}[-\s]?){3}\d{4}$)(?!\d*0{4})\d+$/;
// 验证IP地址(更严格的版本)
const ipAddress = /^(?=(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$)/;
// 验证邮箱(包含常见域名限制)
const emailWithDomain = /^(?=.{1,64}@.{4,253}$)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?=.{1,63}\.)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
文本处理
// 智能的单词边界(考虑Unicode字符)
function smartWordBoundary(text) {
// 使用断言来处理更复杂的单词边界情况
const wordPattern = /(?<!\w)[\w\u00C0-\u017F\u0100-\u017F]+(?!\w)/g;
return text.match(wordPattern);
}
// 提取代码注释,但排除字符串中的注释符号
const commentPattern = /(?<!["'])\/\*[\s\S]*?\*\/|(?<!["'])\/\/.*$/gm;
const codeWithComments = `
console.log("This // is not a comment");
// This is a comment
/* This is a
multi-line comment */
const url = "http://example.com"; // End of line comment
`;
console.log(codeWithComments.match(commentPattern));
6.8 浏览器兼容性和替代方案
后行断言兼容性
// 检查是否支持后行断言
function supportsLookbehind() {
try {
new RegExp('(?<=x)y');
return true;
} catch (e) {
return false;
}
}
// 不支持后行断言时的替代方案
function extractAfterPattern(text, pattern, target) {
const combined = new RegExp(`${pattern}(${target})`, 'g');
const matches = [];
let match;
while ((match = combined.exec(text)) !== null) {
matches.push(match[1]);
}
return matches;
}
// 使用示例
const text = "Price: $19.99, Cost: $25.50";
if (supportsLookbehind()) {
console.log(text.match(/(?<=\$)\d+\.\d{2}/g));
} else {
console.log(extractAfterPattern(text, '\\$', '\\d+\\.\\d{2}'));
}
6.9 性能考虑
断言的性能影响
// 高效的断言使用
const efficient = /^(?=.*\d)(?=.*[a-z]).{6,}$/; // 简单条件在前
// 可能低效的断言使用
const inefficient = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*])(?=.*[0-9]).*$/; // 重复条件
// 优化建议:合并相似条件,简单条件在前
const optimized = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[\d!@#$%^&*]).{8,}$/;
替代方案对比
// 使用断言
function validateWithAssertion(password) {
return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(password);
}
// 使用分步验证(有时更高效)
function validateStepByStep(password) {
return password.length >= 8 &&
/[a-z]/.test(password) &&
/[A-Z]/.test(password) &&
/\d/.test(password);
}
// 性能测试
console.time('assertion');
for (let i = 0; i < 100000; i++) {
validateWithAssertion('Password123');
}
console.timeEnd('assertion');
console.time('stepByStep');
for (let i = 0; i < 100000; i++) {
validateStepByStep('Password123');
}
console.timeEnd('stepByStep');
6.10 练习题
练习1:密码验证
编写一个正则表达式验证密码:
- 8-20位长度
- 至少包含一个小写字母
- 至少包含一个大写字母
- 至少包含一个数字
- 至少包含一个特殊字符 (!@#$%^&*)
- 不能包含用户名
// 答案
function createPasswordValidator(username) {
const pattern = new RegExp(
`^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*])(?!.*${username}).{8,20}$`,
'i'
);
return password => pattern.test(password);
}
const validator = createPasswordValidator('john');
console.log(validator('JohnPass123!')); // false (包含用户名)
console.log(validator('SecurePass123!')); // true
练习2:数据提取
从以下格式的文本中提取价格信息:
- 价格格式:$XX.XX 或 ¥XX.XX
- 忽略在括号或引号中的价格
// 答案
const pricePattern = /(?<![("'])[\$¥](\d+\.?\d{0,2})(?![)"'])/g;
function extractPrices(text) {
const matches = [...text.matchAll(pricePattern)];
return matches.map(match => ({
currency: match[0][0],
amount: parseFloat(match[1])
}));
}
const text = 'Price $19.99, ("$10.00" is fake), ¥25.50, another $30';
console.log(extractPrices(text));
// [
// { currency: '$', amount: 19.99 },
// { currency: '¥', amount: 25.5 },
// { currency: '$', amount: 30 }
// ]
练习3:代码分析
编写正则表达式查找JavaScript中的函数定义,但排除注释中的函数定义。
// 答案(简化版本)
const functionPattern = /(?<!\/\/.*?)(?<!\/\*[\s\S]*?)function\s+(\w+)\s*\(/g;
// 更实用的方法是分步处理
function findFunctionDefinitions(code) {
// 先移除注释
const withoutComments = code
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\/\/.*$/gm, '');
// 再查找函数定义
const functions = [...withoutComments.matchAll(/function\s+(\w+)\s*\(/g)];
return functions.map(match => match[1]);
}
const code = `
function activeFunction() {}
// function commentedFunction() {}
/* function blockCommentedFunction() {} */
const arrow = () => {};
function anotherActive() {}
`;
console.log(findFunctionDefinitions(code)); // ['activeFunction', 'anotherActive']
小结
高级断言技术是正则表达式的强大工具:
- 先行断言:
(?=)
正向,(?!)
负向,检查后面的内容 - 后行断言:
(?<=)
正向,(?<!)
负向,检查前面的内容 - 组合使用:多个断言可以组合创建复杂条件
- 实际应用:密码验证、数据提取、代码分析等
- 兼容性考虑:后行断言在一些环境中不支持,需要替代方案
- 性能优化:合理使用断言,避免过度复杂的条件
掌握断言技术能够让我们编写出更精确、更强大的正则表达式,实现复杂的匹配需求。