|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
正则表达式(Regular Expression)是一种强大的文本处理工具,它使用特定的字符序列来描述和匹配字符串模式。在JavaScript中,正则表达式被广泛应用于表单验证、数据提取、文本替换和搜索等场景。掌握正则表达式不仅能提高代码效率,还能简化复杂的文本处理任务。本文将从基础概念入手,逐步深入,通过丰富的示例帮助读者全面掌握JavaScript正则表达式的使用技巧。
一、正则表达式基础
1. 什么是正则表达式
正则表达式是由普通字符(如字母a-z)和特殊字符(称为”元字符”)组成的文字模式,用于描述在查找文本主体时待匹配的一个或多个字符串。JavaScript中的正则表达式用RegExp对象表示,可以通过字面量或构造函数两种方式创建。
- // 字面量方式
- const pattern1 = /pattern/flags;
- // 构造函数方式
- const pattern2 = new RegExp('pattern', 'flags');
复制代码
2. 正则表达式的基本语法
普通字符(如字母、数字、汉字等)在正则表达式中表示它们自身,直接匹配对应的字符。
- const regex = /hello/;
- console.log(regex.test('hello world')); // true
- console.log(regex.test('hi world')); // false
复制代码
元字符是正则表达式中具有特殊含义的字符,包括:
• .:匹配除换行符以外的任意单个字符
• \w:匹配任意单词字符(字母、数字、下划线)
• \W:匹配任意非单词字符
• \d:匹配任意数字字符(0-9)
• \D:匹配任意非数字字符
• \s:匹配任意空白字符(空格、制表符、换行符等)
• \S:匹配任意非空白字符
• \b:匹配单词边界
• \B:匹配非单词边界
- console.log(/./.test('\n')); // false,不匹配换行符
- console.log(/\w/.test('a')); // true
- console.log(/\w/.test('_')); // true
- console.log(/\w/.test('5')); // true
- console.log(/\w/.test('@')); // false
- console.log(/\d/.test('5')); // true
- console.log(/\d/.test('a')); // false
- console.log(/\s/.test(' ')); // true
- console.log(/\s/.test('\t')); // true
- console.log(/\bhello\b/.test('hello world')); // true
- console.log(/\bhello\b/.test('helloworld')); // false
复制代码
字符类允许匹配指定集合中的任意一个字符,使用方括号[]表示。
- // 匹配a、b或c中的任意一个字符
- console.log(/[abc]/.test('a')); // true
- console.log(/[abc]/.test('d')); // false
- // 使用连字符-表示范围
- console.log(/[a-z]/.test('m')); // true,匹配任意小写字母
- console.log(/[A-Z]/.test('M')); // true,匹配任意大写字母
- console.log(/[0-9]/.test('5')); // true,匹配任意数字
- // 使用^表示否定
- console.log(/[^abc]/.test('d')); // true,匹配除a、b、c外的任意字符
复制代码
量词用于指定前面的字符或字符类出现的次数。
• *:匹配前面的元素零次或多次
• +:匹配前面的元素一次或多次
• ?:匹配前面的元素零次或一次
• {n}:匹配前面的元素恰好n次
• {n,}:匹配前面的元素至少n次
• {n,m}:匹配前面的元素至少n次,至多m次
- console.log(/ab*c/.test('ac')); // true,b出现0次
- console.log(/ab*c/.test('abc')); // true,b出现1次
- console.log(/ab*c/.test('abbc')); // true,b出现2次
- console.log(/ab+c/.test('ac')); // false,b至少出现1次
- console.log(/ab+c/.test('abc')); // true
- console.log(/ab+c/.test('abbc')); // true
- console.log(/ab?c/.test('ac')); // true,b出现0次
- console.log(/ab?c/.test('abc')); // true,b出现1次
- console.log(/ab?c/.test('abbc')); // false,b出现超过1次
- console.log(/ab{2}c/.test('abbc')); // true,b恰好出现2次
- console.log(/ab{2}c/.test('abc')); // false
- console.log(/ab{2}c/.test('abbbc')); // false
- console.log(/ab{2,}c/.test('abbc')); // true,b至少出现2次
- console.log(/ab{2,}c/.test('abbbc')); // true
- console.log(/ab{2,}c/.test('abc')); // false
- console.log(/ab{2,3}c/.test('abbc')); // true,b出现2次
- console.log(/ab{2,3}c/.test('abbbc')); // true,b出现3次
- console.log(/ab{2,3}c/.test('abbbbc')); // false,b出现超过3次
复制代码
锚点用于匹配字符串的位置,而不是字符。
• ^:匹配字符串的开始
• $:匹配字符串的结束
- console.log(/^hello/.test('hello world')); // true,hello在字符串开头
- console.log(/^hello/.test('world hello')); // false,hello不在字符串开头
- console.log(/world$/.test('hello world')); // true,world在字符串结尾
- console.log(/world$/.test('world hello')); // false,world不在字符串结尾
- console.log(/^hello world$/.test('hello world')); // true,完全匹配
- console.log(/^hello world$/.test('hello world!')); // false,不完全匹配
复制代码
如果要匹配元字符本身,需要使用反斜杠\进行转义。
- console.log(/\./.test('.')); // true,匹配点号
- console.log(/\*/.test('*')); // true,匹配星号
- console.log(/\?/.test('?')); // true,匹配问号
- console.log(/\+/.test('+')); // true,匹配加号
- console.log(/\^/.test('^')); // true,匹配脱字符
- console.log(/\$/.test('$')); // true,匹配美元符号
- console.log(/\|/.test('|')); // true,匹配竖线
- console.log(/\//.test('/')); // true,匹配斜杠
- console.log(/\\/.test('\\')); // true,匹配反斜杠
复制代码
二、正则表达式的高级特性
1. 分组和引用
使用圆括号()创建捕获组,可以将多个字符作为一个整体,并且可以在后续引用。
- // 将多个字符作为一个整体
- console.log(/(ab)+/.test('abab')); // true,ab作为一个整体重复
- // 使用|表示或
- console.log(/(red|green|blue)/.test('green')); // true,匹配red、green或blue中的任意一个
复制代码
有时我们只需要分组功能,而不需要捕获,可以使用非捕获组(?:)。
- // 非捕获组不会保存匹配的内容
- const regex = /(?:ab)+/;
- console.log(regex.test('abab')); // true
- // 捕获组会保存匹配的内容
- const regex2 = /(ab)+/;
- console.log(regex2.exec('abab')); // ["abab", "ab"]
复制代码
使用\n(n为数字)引用前面捕获组匹配的内容。
- // 匹配重复的单词
- console.log(/(\w+)\s+\1/.test('hello hello')); // true,\1引用第一个捕获组匹配的内容
- console.log(/(\w+)\s+\1/.test('hello world')); // false
- // 匹配HTML标签(简化版)
- console.log(/<(\w+)>(.*)<\/\1>/.test('<div>content</div>')); // true,\1引用第一个捕获组匹配的div
- console.log(/<(\w+)>(.*)<\/\1>/.test('<div>content</span>')); // false
复制代码
2. 断言
断言用于匹配某些条件,但不消耗字符,即不会将匹配到的内容包含在最终结果中。
x(?=y):匹配x,但只有在x后面跟着y的情况下。
- // 匹配后面跟着world的hello
- console.log(/hello(?= world)/.test('hello world')); // true
- console.log(/hello(?= world)/.test('hello everyone')); // false
- // 提取后面跟着world的hello
- const regex = /hello(?= world)/g;
- const str = 'hello world, hello everyone, hello world again';
- console.log(str.match(regex)); // ["hello", "hello"]
复制代码
x(?!y):匹配x,但只有在x后面不跟着y的情况下。
- // 匹配后面不跟着world的hello
- console.log(/hello(?! world)/.test('hello world')); // false
- console.log(/hello(?! world)/.test('hello everyone')); // true
- // 提取后面不跟着world的hello
- const regex = /hello(?! world)/g;
- const str = 'hello world, hello everyone, hello world again';
- console.log(str.match(regex)); // ["hello"]
复制代码
(?<=y)x:匹配x,但只有在x前面是y的情况下。
- // 匹配前面是hello的world
- console.log(/(?<=hello )world/.test('hello world')); // true
- console.log(/(?<=hello )world/.test('hi world')); // false
- // 提取前面是hello的world
- const regex = /(?<=hello )world/g;
- const str = 'hello world, hi world, hello world again';
- console.log(str.match(regex)); // ["world", "world"]
复制代码
(?<!y)x:匹配x,但只有在x前面不是y的情况下。
- // 匹配前面不是hello的world
- console.log(/(?<!hello )world/.test('hello world')); // false
- console.log(/(?<!hello )world/.test('hi world')); // true
- // 提取前面不是hello的world
- const regex = /(?<!hello )world/g;
- const str = 'hello world, hi world, hello world again';
- console.log(str.match(regex)); // ["world"]
复制代码
3. 标志(修饰符)
标志用于修改正则表达式的匹配行为。
• g:全局匹配,查找所有匹配项,而不是在找到第一个匹配项后停止
• i:忽略大小写
• m:多行模式,使^和$匹配每行的开始和结束,而不是整个字符串的开始和结束
• s:dotAll模式,使.匹配包括换行符在内的所有字符(ES2018)
• u:Unicode模式,正确处理UTF-16编码的字符(ES2015)
• y:粘滞模式,只在目标字符串的当前位置匹配(ES2015)
- // g标志 - 全局匹配
- const regex1 = /hello/g;
- const str1 = 'hello world, hello everyone';
- console.log(str1.match(regex1)); // ["hello", "hello"]
- // i标志 - 忽略大小写
- const regex2 = /hello/i;
- console.log(regex2.test('Hello')); // true
- console.log(regex2.test('HELLO')); // true
- // m标志 - 多行模式
- const regex3 = /^hello/m;
- const str3 = 'hello world\nhello everyone';
- console.log(str3.match(regex3)); // ["hello", "hello"]
- // s标志 - dotAll模式
- const regex4 = /hello.world/s;
- console.log(regex4.test('hello\nworld')); // true
- // u标志 - Unicode模式
- const regex5 = /\u{1F600}/u; // 匹配表情符号😀
- console.log(regex5.test('😀')); // true
- // y标志 - 粘滞模式
- const regex6 = /hello/y;
- const str6 = 'hello world';
- regex6.lastIndex = 6; // 设置开始匹配的位置
- console.log(regex6.test(str6)); // false,因为位置6不是hello
复制代码
三、JavaScript中的正则表达式
1. RegExp对象
在JavaScript中,正则表达式是通过RegExp对象来表示的。可以通过字面量或构造函数创建正则表达式对象。
- // 字面量方式
- const regex1 = /pattern/flags;
- // 构造函数方式
- const regex2 = new RegExp('pattern', 'flags');
- // 使用变量作为模式
- const pattern = 'hello';
- const flags = 'gi';
- const regex3 = new RegExp(pattern, flags);
复制代码- const regex = /hello.world/gi;
- console.log(regex.source); // "hello.world",正则表达式的文本
- console.log(regex.flags); // "gi",正则表达式的标志
- console.log(regex.global); // true,是否设置了g标志
- console.log(regex.ignoreCase); // true,是否设置了i标志
- console.log(regex.multiline); // false,是否设置了m标志
- console.log(regex.dotAll); // false,是否设置了s标志
- console.log(regex.unicode); // false,是否设置了u标志
- console.log(regex.sticky); // false,是否设置了y标志
- console.log(regex.lastIndex); // 0,下次匹配开始的索引
复制代码
2. RegExp对象的方法
test()方法用于测试字符串是否匹配正则表达式,返回true或false。
- const regex = /hello/;
- console.log(regex.test('hello world')); // true
- console.log(regex.test('hi world')); // false
- // 使用g标志时,lastIndex会影响匹配结果
- const regex2 = /hello/g;
- const str = 'hello world, hello everyone';
- console.log(regex2.test(str)); // true,匹配第一个hello
- console.log(regex2.lastIndex); // 5,匹配结束的位置
- console.log(regex2.test(str)); // true,从位置5开始匹配第二个hello
- console.log(regex2.lastIndex); // 18,匹配结束的位置
- console.log(regex2.test(str)); // false,没有更多匹配
- console.log(regex2.lastIndex); // 0,重置为0
复制代码
exec()方法用于在字符串中执行匹配搜索,返回匹配结果的数组,如果没有匹配则返回null。
- const regex = /hello/;
- console.log(regex.exec('hello world'));
- // ["hello", index: 0, input: "hello world", groups: undefined]
- console.log(regex.exec('hi world'));
- // null
- // 使用g标志时,可以多次调用exec()来查找所有匹配
- const regex2 = /hello/g;
- const str = 'hello world, hello everyone';
- let match;
- while ((match = regex2.exec(str)) !== null) {
- console.log(`Found ${match[0]} at index ${match.index}`);
- // Found hello at index 0
- // Found hello at index 13
- }
复制代码
3. String对象的方法
JavaScript中的String对象提供了多个接受正则表达式作为参数的方法。
match()方法用于检索字符串中与正则表达式匹配的结果,返回匹配结果的数组,如果没有匹配则返回null。
- const str = 'hello world, hello everyone';
- // 不使用g标志,只返回第一个匹配
- const result1 = str.match(/hello/);
- console.log(result1);
- // ["hello", index: 0, input: "hello world, hello everyone", groups: undefined]
- // 使用g标志,返回所有匹配
- const result2 = str.match(/hello/g);
- console.log(result2); // ["hello", "hello"]
- // 使用捕获组
- const result3 = str.match(/(hello) (world)/);
- console.log(result3);
- // ["hello world", "hello", "world", index: 0, input: "hello world, hello everyone", groups: undefined]
复制代码
search()方法用于检索字符串中与正则表达式匹配的子串的起始位置,如果没有匹配则返回-1。
- const str = 'hello world, hello everyone';
- console.log(str.search(/hello/)); // 0
- console.log(str.search(/world/)); // 6
- console.log(str.search(/everyone/)); // 14
- console.log(str.search(/goodbye/)); // -1
复制代码
replace()方法用于替换字符串中与正则表达式匹配的子串,返回替换后的新字符串。
- const str = 'hello world, hello everyone';
- // 替换第一个匹配
- const result1 = str.replace(/hello/, 'hi');
- console.log(result1); // "hi world, hello everyone"
- // 使用g标志替换所有匹配
- const result2 = str.replace(/hello/g, 'hi');
- console.log(result2); // "hi world, hi everyone"
- // 使用捕获组
- const result3 = str.replace(/(hello) (world)/, '$2 $1');
- console.log(result3); // "world hello, hello everyone"
- // 使用回调函数
- const result4 = str.replace(/hello/g, (match, index, original) => {
- console.log(`Found ${match} at index ${index}`);
- return 'hi';
- });
- console.log(result4); // "hi world, hi everyone"
复制代码
split()方法用于将字符串分割为字符串数组,可以使用正则表达式作为分隔符。
- const str = 'hello world, hello everyone';
- // 使用逗号和空格分割
- const result1 = str.split(/, /);
- console.log(result1); // ["hello world", "hello everyone"]
- // 使用空格分割
- const result2 = str.split(/\s/);
- console.log(result2); // ["hello", "world,", "hello", "everyone"]
- // 限制分割次数
- const result3 = str.split(/\s/, 2);
- console.log(result3); // ["hello", "world,"]
复制代码
matchAll()方法用于返回一个迭代器,该迭代器包含所有匹配的匹配结果及其捕获组。
- const str = 'hello world, hello everyone';
- // 使用g标志
- const regex = /hello/g;
- const matches = str.matchAll(regex);
- for (const match of matches) {
- console.log(match);
- }
- // ["hello", index: 0, input: "hello world, hello everyone", groups: undefined]
- // ["hello", index: 13, input: "hello world, hello everyone", groups: undefined]
- // 使用捕获组
- const regex2 = /(hello) (world)/g;
- const matches2 = str.matchAll(regex2);
- for (const match of matches2) {
- console.log(match);
- }
- // ["hello world", "hello", "world", index: 0, input: "hello world, hello everyone", groups: undefined]
复制代码
四、常见正则表达式实例
1. 表单验证
- function validateEmail(email) {
- const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
- return regex.test(email);
- }
- console.log(validateEmail('user@example.com')); // true
- console.log(validateEmail('user.name@example.com')); // true
- console.log(validateEmail('user@sub.example.com')); // true
- console.log(validateEmail('user@example')); // false
- console.log(validateEmail('user.example.com')); // false
- console.log(validateEmail('@example.com')); // false
复制代码- function validatePhoneNumber(phone) {
- // 简化版,只验证中国大陆手机号
- const regex = /^1[3-9]\d{9}$/;
- return regex.test(phone);
- }
- console.log(validatePhoneNumber('13812345678')); // true
- console.log(validatePhoneNumber('15912345678')); // true
- console.log(validatePhoneNumber('12345678901')); // false
- console.log(validatePhoneNumber('1381234567')); // false
- console.log(validatePhoneNumber('138123456789')); // false
复制代码- function validateIDCard(id) {
- // 简化版,只验证18位身份证号码
- const regex = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
- return regex.test(id);
- }
- console.log(validateIDCard('11010519491231002X')); // true
- console.log(validateIDCard('110105194912310021')); // true
- console.log(validateIDCard('11010519491331002X')); // false,月份错误
- console.log(validateIDCard('11010519491232002X')); // false,日期错误
- console.log(validateIDCard('11010519491231002')); // false,位数不足
复制代码- function validatePassword(password) {
- // 至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符
- const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
- return regex.test(password);
- }
- console.log(validatePassword('Password123!')); // true
- console.log(validatePassword('password123!')); // false,缺少大写字母
- console.log(validatePassword('PASSWORD123!')); // false,缺少小写字母
- console.log(validatePassword('Password!')); // false,缺少数字
- console.log(validatePassword('Password123')); // false,缺少特殊字符
- console.log(validatePassword('Pass12!')); // false,长度不足
复制代码
2. 数据提取
- function getUrlParams(url) {
- const params = {};
- const regex = /[?&]([^=#]+)=([^&#]*)/g;
- let match;
-
- while ((match = regex.exec(url)) !== null) {
- params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
- }
-
- return params;
- }
- const url = 'https://example.com?name=John&age=30&city=New%20York';
- console.log(getUrlParams(url));
- // { name: "John", age: "30", city: "New York" }
复制代码- function extractHTMLTags(html) {
- const regex = /<(\w+)(?:\s+[^>]*)?>(.*?)<\/\1>/gs;
- const matches = [];
- let match;
-
- while ((match = regex.exec(html)) !== null) {
- matches.push({
- tag: match[1],
- content: match[2]
- });
- }
-
- return matches;
- }
- const html = '<div class="container"><p>Hello, <span>world</span>!</p></div>';
- console.log(extractHTMLTags(html));
- // [
- // { tag: "div", content: "<p>Hello, <span>world</span>!</p>" },
- // { tag: "p", content: "Hello, <span>world</span>!" },
- // { tag: "span", content: "world" }
- // ]
复制代码- function extractMarkdownLinks(markdown) {
- const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
- const links = [];
- let match;
-
- while ((match = regex.exec(markdown)) !== null) {
- links.push({
- text: match[1],
- url: match[2]
- });
- }
-
- return links;
- }
- const markdown = 'Visit [GitHub](https://github.com) and [Stack Overflow](https://stackoverflow.com).';
- console.log(extractMarkdownLinks(markdown));
- // [
- // { text: "GitHub", url: "https://github.com" },
- // { text: "Stack Overflow", url: "https://stackoverflow.com" }
- // ]
复制代码
3. 文本替换
- function removeHTMLTags(html) {
- return html.replace(/<[^>]*>/g, '');
- }
- const html = '<div class="container"><p>Hello, <span>world</span>!</p></div>';
- console.log(removeHTMLTags(html)); // "Hello, world!"
复制代码- function formatPhoneNumber(phone) {
- // 假设输入是10位数字(美国电话号码格式)
- return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
- }
- console.log(formatPhoneNumber('1234567890')); // "(123) 456-7890"
复制代码- function hideSensitiveInfo(text) {
- // 隐藏邮箱地址
- text = text.replace(/(\w)[\w.-]*@([\w.]+\w)/g, '$1***@$2');
-
- // 隐藏手机号码
- text = text.replace(/(\d{3})\d{4}(\d{4})/g, '$1****$2');
-
- // 隐藏身份证号码
- text = text.replace(/(\d{6})\d{8}(\d{4})/g, '$1********$2');
-
- return text;
- }
- const text = 'Contact me at user@example.com or call 13812345678. My ID is 11010519491231002X.';
- console.log(hideSensitiveInfo(text));
- // "Contact me at u***@example.com or call 138****5678. My ID is 110105********02X."
复制代码
4. 文本搜索
- function highlightKeywords(text, keywords) {
- // 创建正则表达式,匹配所有关键词
- const regex = new RegExp(`(${keywords.join('|')})`, 'gi');
-
- // 使用回调函数替换匹配的关键词
- return text.replace(regex, '<mark>$1</mark>');
- }
- const text = 'JavaScript is a popular programming language. Many developers use JavaScript for web development.';
- const keywords = ['JavaScript', 'developers', 'web'];
- console.log(highlightKeywords(text, keywords));
- // "<mark>JavaScript</mark> is a popular programming language. Many <mark>developers</mark> use <mark>JavaScript</mark> for <mark>web</mark> development."
复制代码- function findDuplicateWords(text) {
- const regex = /\b(\w+)\s+\1\b/gi;
- const duplicates = [];
- let match;
-
- while ((match = regex.exec(text)) !== null) {
- duplicates.push({
- word: match[1],
- index: match.index
- });
- }
-
- return duplicates;
- }
- const text = 'This is is a test test to find duplicate words.';
- console.log(findDuplicateWords(text));
- // [
- // { word: "is", index: 5 },
- // { word: "test", index: 15 }
- // ]
复制代码
五、性能优化和最佳实践
1. 正则表达式性能优化
回溯是正则表达式性能问题的常见原因。当正则表达式有多种可能的匹配路径时,引擎会尝试所有路径直到找到匹配或所有可能性都耗尽。
- // 低效的正则表达式,可能导致回溯灾难
- const inefficientRegex = /^(a+)+$/;
- // 更高效的正则表达式
- const efficientRegex = /^a+$/;
复制代码
如果不需要捕获组的内容,使用非捕获组(?:)可以提高性能。
- // 使用捕获组
- const withCapturing = /(hello|world) (hello|world)/;
- // 使用非捕获组
- const withoutCapturing = /(?:hello|world) (?:hello|world)/;
复制代码
使用具体的字符类而不是.或\w等通用字符类可以提高性能。
- // 使用通用字符类
- const generic = /\d+-\d+-\d+/;
- // 使用具体字符类
- const specific = /[0-9]+-[0-9]+-[0-9]+/;
复制代码
贪婪量词(如.*)会尝试匹配尽可能多的字符,可能导致不必要的回溯。使用惰性量词(如.*?)或更具体的模式可以提高性能。
- // 贪婪量词
- const greedy = /<div>.*<\/div>/;
- // 惰性量词
- const lazy = /<div>.*?<\/div>/;
- // 更具体的模式
- const specific = /<div>[^<]*<\/div>/;
复制代码
2. 正则表达式最佳实践
如果正则表达式是静态的,使用字面量语法而不是构造函数可以提高性能。
- // 字面量语法(推荐)
- const regex1 = /pattern/g;
- // 构造函数语法
- const regex2 = new RegExp('pattern', 'g');
复制代码
如果多次使用同一个正则表达式,将其缓存起来可以提高性能。
- // 不缓存
- function validateEmail(email) {
- return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email);
- }
- // 缓存
- const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
- function validateEmail(email) {
- return emailRegex.test(email);
- }
复制代码
根据需要使用适当的标志,避免不必要的全局匹配。
- // 只需要测试是否匹配,不需要g标志
- const regex1 = /hello/;
- // 需要找到所有匹配,使用g标志
- const regex2 = /hello/g;
复制代码
复杂的正则表达式应该添加注释和文档,以便他人理解和维护。
- // 验证邮箱地址的正则表达式
- // 格式:local-part@domain
- // local-part: 字母、数字、点、下划线、百分号、加号或减号
- // domain: 字母、数字、点和减号,后跟点和至少2个字母
- const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
复制代码
六、实战案例
1. 构建一个简单的模板引擎
- class SimpleTemplateEngine {
- constructor(template) {
- this.template = template;
- // 匹配 {{ variable }} 格式的变量
- this.variableRegex = /\{\{\s*([^}\s]+)\s*\}\}/g;
- // 匹配 {% if condition %}...{% endif %} 格式的条件语句
- this.ifRegex = /\{%\s*if\s+([^}]+)\s*%\}([\s\S]*?)\{%\s*endif\s*%\}/g;
- // 匹配 {% for item in items %}...{% endfor %} 格式的循环语句
- this.forRegex = /\{%\s*for\s+(\w+)\s+in\s+(\w+)\s*%\}([\s\S]*?)\{%\s*endfor\s*%\}/g;
- }
-
- render(context) {
- let result = this.template;
-
- // 处理条件语句
- result = this._processIfStatements(result, context);
-
- // 处理循环语句
- result = this._processForStatements(result, context);
-
- // 处理变量
- result = this._processVariables(result, context);
-
- return result;
- }
-
- _processVariables(template, context) {
- return template.replace(this.variableRegex, (match, variable) => {
- // 支持嵌套属性,如 user.name
- const properties = variable.split('.');
- let value = context;
-
- for (const prop of properties) {
- if (value && typeof value === 'object' && prop in value) {
- value = value[prop];
- } else {
- return '';
- }
- }
-
- return value !== null && value !== undefined ? value : '';
- });
- }
-
- _processIfStatements(template, context) {
- let result = template;
- let match;
-
- while ((match = this.ifRegex.exec(template)) !== null) {
- const condition = match[1];
- const content = match[2];
-
- try {
- // 简单的条件评估,实际应用中可能需要更复杂的解析器
- const conditionResult = this._evaluateCondition(condition, context);
- const replacement = conditionResult ? content : '';
- result = result.replace(match[0], replacement);
- } catch (e) {
- console.error('Error evaluating condition:', condition, e);
- result = result.replace(match[0], '');
- }
- }
-
- return result;
- }
-
- _processForStatements(template, context) {
- let result = template;
- let match;
-
- while ((match = this.forRegex.exec(template)) !== null) {
- const itemVar = match[1];
- const listVar = match[2];
- const content = match[3];
-
- try {
- const list = this._getValueFromContext(listVar, context);
- let replacement = '';
-
- if (Array.isArray(list)) {
- for (const item of list) {
- // 创建新的上下文,包含当前项
- const itemContext = { ...context, [itemVar]: item };
- replacement += this._processVariables(content, itemContext);
- }
- }
-
- result = result.replace(match[0], replacement);
- } catch (e) {
- console.error('Error processing for loop:', e);
- result = result.replace(match[0], '');
- }
- }
-
- return result;
- }
-
- _evaluateCondition(condition, context) {
- // 简单的条件评估,支持 ==, !=, >, <, >=, <=
- const operators = ['==', '!=', '>', '<', '>=', '<='];
-
- for (const op of operators) {
- if (condition.includes(op)) {
- const [left, right] = condition.split(op).map(s => s.trim());
- const leftValue = this._getValueFromContext(left, context);
- const rightValue = this._getValueFromContext(right, context);
-
- switch (op) {
- case '==': return leftValue == rightValue;
- case '!=': return leftValue != rightValue;
- case '>': return leftValue > rightValue;
- case '<': return leftValue < rightValue;
- case '>=': return leftValue >= rightValue;
- case '<=': return leftValue <= rightValue;
- }
- }
- }
-
- // 如果没有找到操作符,检查变量是否为真
- return !!this._getValueFromContext(condition, context);
- }
-
- _getValueFromContext(path, context) {
- // 支持嵌套属性,如 user.name
- const properties = path.split('.');
- let value = context;
-
- for (const prop of properties) {
- if (value && typeof value === 'object' && prop in value) {
- value = value[prop];
- } else {
- return undefined;
- }
- }
-
- return value;
- }
- }
- // 使用示例
- const template = `
- <div>
- <h1>{{ title }}</h1>
- <p>{{ description }}</p>
-
- {% if showList %}
- <ul>
- {% for item in items %}
- <li>{{ item.name }} - {{ item.price }}</li>
- {% endfor %}
- </ul>
- {% endif %}
-
- {% if user.loggedIn %}
- <p>Welcome, {{ user.name }}!</p>
- {% else %}
- <p>Please <a href="/login">log in</a>.</p>
- {% endif %}
- </div>
- `;
- const context = {
- title: 'Product List',
- description: 'Here are our products:',
- showList: true,
- items: [
- { name: 'Product 1', price: '$10' },
- { name: 'Product 2', price: '$20' },
- { name: 'Product 3', price: '$30' }
- ],
- user: {
- loggedIn: true,
- name: 'John Doe'
- }
- };
- const engine = new SimpleTemplateEngine(template);
- console.log(engine.render(context));
复制代码
2. 构建一个简单的查询语言解析器
- class SimpleQueryParser {
- constructor() {
- // 匹配字段:操作符:值 格式的查询条件
- this.conditionRegex = /(\w+)(:|=|!=|>|<|>=|<=)([^&|]+)/g;
- // 匹配 AND 和 OR 操作符
- this.operatorRegex = /\s*(\&\&|\|\|)\s*/g;
- }
-
- parse(query) {
- // 预处理查询,替换 AND 和 OR 为 && 和 ||
- query = query.replace(/\bAND\b/g, '&&').replace(/\bOR\b/g, '||');
-
- // 提取所有条件
- const conditions = [];
- let match;
-
- while ((match = this.conditionRegex.exec(query)) !== null) {
- conditions.push({
- field: match[1],
- operator: match[2],
- value: match[3].trim(),
- fullMatch: match[0]
- });
- }
-
- // 提取所有操作符
- const operators = [];
- const operatorMatches = query.match(this.operatorRegex);
- if (operatorMatches) {
- operators.push(...operatorMatches);
- }
-
- // 构建解析树
- const tree = this._buildParseTree(conditions, operators);
-
- return {
- query,
- conditions,
- operators,
- tree,
- evaluate: (data) => this._evaluateTree(tree, data)
- };
- }
-
- _buildParseTree(conditions, operators) {
- if (conditions.length === 0) {
- return null;
- }
-
- if (conditions.length === 1) {
- return {
- type: 'condition',
- condition: conditions[0]
- };
- }
-
- // 简单的优先级处理:AND 优先于 OR
- // 实际应用中可能需要更复杂的优先级处理
- const andIndex = operators.indexOf('&&');
-
- if (andIndex !== -1) {
- return {
- type: 'operator',
- operator: '&&',
- left: this._buildParseTree(conditions.slice(0, andIndex + 1), operators.slice(0, andIndex)),
- right: this._buildParseTree(conditions.slice(andIndex + 1), operators.slice(andIndex + 1))
- };
- } else {
- const orIndex = operators.indexOf('||');
- return {
- type: 'operator',
- operator: '||',
- left: this._buildParseTree(conditions.slice(0, orIndex + 1), operators.slice(0, orIndex)),
- right: this._buildParseTree(conditions.slice(orIndex + 1), operators.slice(orIndex + 1))
- };
- }
- }
-
- _evaluateTree(tree, data) {
- if (!tree) {
- return true;
- }
-
- if (tree.type === 'condition') {
- return this._evaluateCondition(tree.condition, data);
- }
-
- if (tree.type === 'operator') {
- const left = this._evaluateTree(tree.left, data);
- const right = this._evaluateTree(tree.right, data);
-
- if (tree.operator === '&&') {
- return left && right;
- } else if (tree.operator === '||') {
- return left || right;
- }
- }
-
- return false;
- }
-
- _evaluateCondition(condition, data) {
- const { field, operator, value } = condition;
-
- // 获取字段的值
- let fieldValue = data;
- const fieldParts = field.split('.');
-
- for (const part of fieldParts) {
- if (fieldValue && typeof fieldValue === 'object' && part in fieldValue) {
- fieldValue = fieldValue[part];
- } else {
- fieldValue = undefined;
- break;
- }
- }
-
- // 尝试解析值为数字或布尔值
- let parsedValue;
- if (value.toLowerCase() === 'true') {
- parsedValue = true;
- } else if (value.toLowerCase() === 'false') {
- parsedValue = false;
- } else if (!isNaN(value) && value.trim() !== '') {
- parsedValue = Number(value);
- } else {
- parsedValue = value;
- }
-
- // 根据操作符比较值
- switch (operator) {
- case ':':
- case '=':
- return fieldValue == parsedValue;
- case '!=':
- return fieldValue != parsedValue;
- case '>':
- return fieldValue > parsedValue;
- case '<':
- return fieldValue < parsedValue;
- case '>=':
- return fieldValue >= parsedValue;
- case '<=':
- return fieldValue <= parsedValue;
- default:
- return false;
- }
- }
- }
- // 使用示例
- const parser = new SimpleQueryParser();
- // 简单查询
- const query1 = 'name:John AND age:30';
- const parsed1 = parser.parse(query1);
- console.log(parsed1);
- const data1 = { name: 'John', age: 30 };
- console.log(parsed1.evaluate(data1)); // true
- const data2 = { name: 'John', age: 25 };
- console.log(parsed1.evaluate(data2)); // false
- // 复杂查询
- const query2 = 'name:John AND (age:>25 OR city:"New York")';
- // 注意:这个简化版本不支持括号,实际应用中需要更复杂的解析器
- const parsed2 = parser.parse('name:John AND age:>25 OR city:"New York"');
- console.log(parsed2);
- const data3 = { name: 'John', age: 30, city: 'Boston' };
- console.log(parsed2.evaluate(data3)); // true
- const data4 = { name: 'John', age: 20, city: 'New York' };
- console.log(parsed2.evaluate(data4)); // true
- const data5 = { name: 'Jane', age: 30, city: 'New York' };
- console.log(parsed2.evaluate(data5)); // false
复制代码
3. 构建一个简单的代码高亮器
- class SimpleCodeHighlighter {
- constructor(language = 'javascript') {
- this.language = language;
- this.rules = this._getRulesForLanguage(language);
- }
-
- highlight(code) {
- let highlightedCode = code;
-
- // 应用所有规则
- for (const rule of this.rules) {
- highlightedCode = highlightedCode.replace(rule.regex, (match, ...groups) => {
- // 处理捕获组
- let replacement = rule.replacement;
-
- // 替换捕获组引用
- for (let i = 0; i < groups.length - 2; i++) {
- replacement = replacement.replace(new RegExp(`\\$${i + 1}`, 'g'), groups[i]);
- }
-
- return replacement;
- });
- }
-
- return highlightedCode;
- }
-
- _getRulesForLanguage(language) {
- const rules = {
- javascript: [
- // 关键字
- {
- regex: /\b(break|case|catch|class|const|continue|debugger|default|delete|do|else|export|extends|finally|for|function|if|import|in|instanceof|new|return|super|switch|this|throw|try|typeof|var|void|while|with|yield|let|async|await|of|get|set)\b/g,
- replacement: '<span class="keyword">$1</span>'
- },
- // 字符串
- {
- regex: /(['"])((?:\\\1|(?!\1).)*)\1/g,
- replacement: '<span class="string">$1$2$1</span>'
- },
- // 数字
- {
- regex: /\b(\d+\.?\d*|\.\d+)\b/g,
- replacement: '<span class="number">$1</span>'
- },
- // 注释
- {
- regex: /(\/\/.*$|\/\*[\s\S]*?\*\/)/gm,
- replacement: '<span class="comment">$1</span>'
- },
- // 正则表达式
- {
- regex: /\/(?![*])([^\/\n\\]*(?:\\.[^\/\n\\]*)*)\/[gimuy]*/g,
- replacement: '<span class="regex">$&</span>'
- },
- // 函数调用
- {
- regex: /\b([a-zA-Z_]\w*)\s*\(/g,
- replacement: '<span class="function">$1</span>('
- },
- // 操作符
- {
- regex: /(\+|\-|\*|\/|%|&|\||\^|!|~|=|<|>|?|:|\.|,|;|\{|\}|\(|\)|\[|\])/g,
- replacement: '<span class="operator">$1</span>'
- }
- ],
- html: [
- // 标签
- {
- regex: /<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>/g,
- replacement: '<span class="tag"><$1</span><span class="tag-attributes">$2</span><span class="tag">></span>'
- },
- // 属性
- {
- regex: /\b([a-zA-Z-]+)\s*=\s*("[^"]*"|'[^']*'|[^>\s]*)/g,
- replacement: '<span class="attribute">$1</span>=<span class="attribute-value">$2</span>'
- },
- // 注释
- {
- regex: /(<!--[\s\S]*?-->)/g,
- replacement: '<span class="comment">$1</span>'
- },
- // DOCTYPE
- {
- regex: /<!DOCTYPE[^>]*>/gi,
- replacement: '<span class="doctype">$&</span>'
- }
- ],
- css: [
- // 选择器
- {
- regex: /([^{]+)\{/g,
- replacement: '<span class="selector">$1</span>{'
- },
- // 属性
- {
- regex: /\b([a-zA-Z-]+)\s*:/g,
- replacement: '<span class="property">$1</span>:'
- },
- // 值
- {
- regex: /:\s*([^;};]+);/g,
- replacement: ': <span class="value">$1</span>;'
- },
- // 注释
- {
- regex: /(\/\*[\s\S]*?\*\/)/g,
- replacement: '<span class="comment">$1</span>'
- },
- // 颜色
- {
- regex: /(#([0-9a-fA-F]{3}){1,2}|rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)|rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\))/g,
- replacement: '<span class="color">$1</span>'
- },
- // 单位
- {
- regex: /\b(\d+)(px|em|rem|%|vh|vw|vmin|vmax|pt|pc|in|cm|mm)\b/g,
- replacement: '<span class="number">$1</span><span class="unit">$2</span>'
- }
- ]
- };
-
- return rules[language] || [];
- }
- }
- // 使用示例
- const highlighter = new SimpleCodeHighlighter('javascript');
- const jsCode = `
- function fibonacci(n) {
- if (n <= 1) {
- return n;
- }
-
- return fibonacci(n - 1) + fibonacci(n - 2);
- }
- // 计算前10个斐波那契数
- for (let i = 0; i < 10; i++) {
- console.log(\`fibonacci(\${i}) = \${fibonacci(i)}\`);
- }
- `;
- console.log(highlighter.highlight(jsCode));
- const htmlHighlighter = new SimpleCodeHighlighter('html');
- const htmlCode = `
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Sample Page</title>
- </head>
- <body>
- <div class="container">
- <h1 id="title">Hello, World!</h1>
- <p>This is a sample paragraph.</p>
- </div>
- </body>
- </html>
- `;
- console.log(htmlHighlighter.highlight(htmlCode));
- const cssHighlighter = new SimpleCodeHighlighter('css');
- const cssCode = `
- .container {
- width: 100%;
- max-width: 1200px;
- margin: 0 auto;
- padding: 20px;
- background-color: #f5f5f5;
- border-radius: 5px;
- }
- #title {
- font-size: 2rem;
- color: #333;
- text-align: center;
- }
- /* 响应式设计 */
- @media (max-width: 768px) {
- .container {
- padding: 10px;
- }
-
- #title {
- font-size: 1.5rem;
- }
- }
- `;
- console.log(cssHighlighter.highlight(cssCode));
复制代码
七、总结与展望
正则表达式是JavaScript中处理文本的强大工具,通过本文的学习,我们了解了正则表达式的基础语法、高级特性、JavaScript中的正则表达式API以及实际应用案例。掌握正则表达式不仅能提高代码效率,还能简化复杂的文本处理任务。
1. 关键要点回顾
• 基础语法:包括字面量字符、元字符、字符类、量词、锚点和转义字符等。
• 高级特性:包括分组和引用、断言(先行断言、先行否定断言、后行断言、后行否定断言)等。
• JavaScript中的正则表达式:包括RegExp对象、RegExp对象的方法(test()、exec())和String对象的方法(match()、search()、replace()、split()、matchAll())。
• 常见应用:包括表单验证、数据提取、文本替换和文本搜索等。
• 性能优化:包括避免回溯、使用非捕获组、使用具体字符类、避免贪婪量词等。
• 最佳实践:包括使用字面量而不是构造函数、缓存正则表达式、使用适当的标志、添加注释和文档等。
2. 进阶学习建议
• 深入学习正则表达式的内部工作原理,了解DFA(确定性有限自动机)和NFA(非确定性有限自动机)的区别。
• 学习更复杂的正则表达式技巧,如递归模式、原子组、占有量词等。
• 探索正则表达式在不同编程语言中的实现差异。
• 学习使用正则表达式调试工具,如Regex101、Debuggex等。
• 研究正则表达式在特定领域的应用,如日志分析、数据清洗、自然语言处理等。
3. 未来展望
随着JavaScript语言的发展,正则表达式也在不断演进。ES2018引入了后行断言、Unicode属性转义等新特性,未来可能会有更多强大的特性被添加到语言中。同时,随着Web应用的复杂化,对文本处理的需求也会不断增加,正则表达式作为一种高效、灵活的文本处理工具,将继续发挥重要作用。
通过不断学习和实践,我们可以更好地掌握正则表达式这一强大的工具,为我们的开发工作带来更多便利和效率。 |
|