活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

JavaScript正则表达式匹配规则详解从基础到实战全面掌握文本处理技巧

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-3 13:10:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

正则表达式(Regular Expression)是一种强大的文本处理工具,它使用特定的字符序列来描述和匹配字符串模式。在JavaScript中,正则表达式被广泛应用于表单验证、数据提取、文本替换和搜索等场景。掌握正则表达式不仅能提高代码效率,还能简化复杂的文本处理任务。本文将从基础概念入手,逐步深入,通过丰富的示例帮助读者全面掌握JavaScript正则表达式的使用技巧。

一、正则表达式基础

1. 什么是正则表达式

正则表达式是由普通字符(如字母a-z)和特殊字符(称为”元字符”)组成的文字模式,用于描述在查找文本主体时待匹配的一个或多个字符串。JavaScript中的正则表达式用RegExp对象表示,可以通过字面量或构造函数两种方式创建。
  1. // 字面量方式
  2. const pattern1 = /pattern/flags;
  3. // 构造函数方式
  4. const pattern2 = new RegExp('pattern', 'flags');
复制代码

2. 正则表达式的基本语法

普通字符(如字母、数字、汉字等)在正则表达式中表示它们自身,直接匹配对应的字符。
  1. const regex = /hello/;
  2. console.log(regex.test('hello world')); // true
  3. console.log(regex.test('hi world'));    // false
复制代码

元字符是正则表达式中具有特殊含义的字符,包括:

• .:匹配除换行符以外的任意单个字符
• \w:匹配任意单词字符(字母、数字、下划线)
• \W:匹配任意非单词字符
• \d:匹配任意数字字符(0-9)
• \D:匹配任意非数字字符
• \s:匹配任意空白字符(空格、制表符、换行符等)
• \S:匹配任意非空白字符
• \b:匹配单词边界
• \B:匹配非单词边界
  1. console.log(/./.test('\n'));    // false,不匹配换行符
  2. console.log(/\w/.test('a'));    // true
  3. console.log(/\w/.test('_'));    // true
  4. console.log(/\w/.test('5'));    // true
  5. console.log(/\w/.test('@'));    // false
  6. console.log(/\d/.test('5'));    // true
  7. console.log(/\d/.test('a'));    // false
  8. console.log(/\s/.test(' '));    // true
  9. console.log(/\s/.test('\t'));   // true
  10. console.log(/\bhello\b/.test('hello world'));  // true
  11. console.log(/\bhello\b/.test('helloworld'));   // false
复制代码

字符类允许匹配指定集合中的任意一个字符,使用方括号[]表示。
  1. // 匹配a、b或c中的任意一个字符
  2. console.log(/[abc]/.test('a'));  // true
  3. console.log(/[abc]/.test('d'));  // false
  4. // 使用连字符-表示范围
  5. console.log(/[a-z]/.test('m'));  // true,匹配任意小写字母
  6. console.log(/[A-Z]/.test('M'));  // true,匹配任意大写字母
  7. console.log(/[0-9]/.test('5'));  // true,匹配任意数字
  8. // 使用^表示否定
  9. console.log(/[^abc]/.test('d'));  // true,匹配除a、b、c外的任意字符
复制代码

量词用于指定前面的字符或字符类出现的次数。

• *:匹配前面的元素零次或多次
• +:匹配前面的元素一次或多次
• ?:匹配前面的元素零次或一次
• {n}:匹配前面的元素恰好n次
• {n,}:匹配前面的元素至少n次
• {n,m}:匹配前面的元素至少n次,至多m次
  1. console.log(/ab*c/.test('ac'));    // true,b出现0次
  2. console.log(/ab*c/.test('abc'));   // true,b出现1次
  3. console.log(/ab*c/.test('abbc'));  // true,b出现2次
  4. console.log(/ab+c/.test('ac'));    // false,b至少出现1次
  5. console.log(/ab+c/.test('abc'));   // true
  6. console.log(/ab+c/.test('abbc'));  // true
  7. console.log(/ab?c/.test('ac'));    // true,b出现0次
  8. console.log(/ab?c/.test('abc'));   // true,b出现1次
  9. console.log(/ab?c/.test('abbc'));  // false,b出现超过1次
  10. console.log(/ab{2}c/.test('abbc'));    // true,b恰好出现2次
  11. console.log(/ab{2}c/.test('abc'));     // false
  12. console.log(/ab{2}c/.test('abbbc'));   // false
  13. console.log(/ab{2,}c/.test('abbc'));   // true,b至少出现2次
  14. console.log(/ab{2,}c/.test('abbbc'));  // true
  15. console.log(/ab{2,}c/.test('abc'));    // false
  16. console.log(/ab{2,3}c/.test('abbc'));   // true,b出现2次
  17. console.log(/ab{2,3}c/.test('abbbc'));  // true,b出现3次
  18. console.log(/ab{2,3}c/.test('abbbbc')); // false,b出现超过3次
复制代码

锚点用于匹配字符串的位置,而不是字符。

• ^:匹配字符串的开始
• $:匹配字符串的结束
  1. console.log(/^hello/.test('hello world'));  // true,hello在字符串开头
  2. console.log(/^hello/.test('world hello'));  // false,hello不在字符串开头
  3. console.log(/world$/.test('hello world'));  // true,world在字符串结尾
  4. console.log(/world$/.test('world hello'));  // false,world不在字符串结尾
  5. console.log(/^hello world$/.test('hello world'));  // true,完全匹配
  6. console.log(/^hello world$/.test('hello world!')); // false,不完全匹配
复制代码

如果要匹配元字符本身,需要使用反斜杠\进行转义。
  1. console.log(/\./.test('.'));     // true,匹配点号
  2. console.log(/\*/.test('*'));     // true,匹配星号
  3. console.log(/\?/.test('?'));     // true,匹配问号
  4. console.log(/\+/.test('+'));     // true,匹配加号
  5. console.log(/\^/.test('^'));     // true,匹配脱字符
  6. console.log(/\$/.test('$'));     // true,匹配美元符号
  7. console.log(/\|/.test('|'));     // true,匹配竖线
  8. console.log(/\//.test('/'));     // true,匹配斜杠
  9. console.log(/\\/.test('\\'));    // true,匹配反斜杠
复制代码

二、正则表达式的高级特性

1. 分组和引用

使用圆括号()创建捕获组,可以将多个字符作为一个整体,并且可以在后续引用。
  1. // 将多个字符作为一个整体
  2. console.log(/(ab)+/.test('abab'));  // true,ab作为一个整体重复
  3. // 使用|表示或
  4. console.log(/(red|green|blue)/.test('green'));  // true,匹配red、green或blue中的任意一个
复制代码

有时我们只需要分组功能,而不需要捕获,可以使用非捕获组(?:)。
  1. // 非捕获组不会保存匹配的内容
  2. const regex = /(?:ab)+/;
  3. console.log(regex.test('abab'));  // true
  4. // 捕获组会保存匹配的内容
  5. const regex2 = /(ab)+/;
  6. console.log(regex2.exec('abab'));  // ["abab", "ab"]
复制代码

使用\n(n为数字)引用前面捕获组匹配的内容。
  1. // 匹配重复的单词
  2. console.log(/(\w+)\s+\1/.test('hello hello'));  // true,\1引用第一个捕获组匹配的内容
  3. console.log(/(\w+)\s+\1/.test('hello world'));  // false
  4. // 匹配HTML标签(简化版)
  5. console.log(/<(\w+)>(.*)<\/\1>/.test('<div>content</div>'));  // true,\1引用第一个捕获组匹配的div
  6. console.log(/<(\w+)>(.*)<\/\1>/.test('<div>content</span>')); // false
复制代码

2. 断言

断言用于匹配某些条件,但不消耗字符,即不会将匹配到的内容包含在最终结果中。

x(?=y):匹配x,但只有在x后面跟着y的情况下。
  1. // 匹配后面跟着world的hello
  2. console.log(/hello(?= world)/.test('hello world'));  // true
  3. console.log(/hello(?= world)/.test('hello everyone')); // false
  4. // 提取后面跟着world的hello
  5. const regex = /hello(?= world)/g;
  6. const str = 'hello world, hello everyone, hello world again';
  7. console.log(str.match(regex));  // ["hello", "hello"]
复制代码

x(?!y):匹配x,但只有在x后面不跟着y的情况下。
  1. // 匹配后面不跟着world的hello
  2. console.log(/hello(?! world)/.test('hello world'));    // false
  3. console.log(/hello(?! world)/.test('hello everyone')); // true
  4. // 提取后面不跟着world的hello
  5. const regex = /hello(?! world)/g;
  6. const str = 'hello world, hello everyone, hello world again';
  7. console.log(str.match(regex));  // ["hello"]
复制代码

(?<=y)x:匹配x,但只有在x前面是y的情况下。
  1. // 匹配前面是hello的world
  2. console.log(/(?<=hello )world/.test('hello world'));    // true
  3. console.log(/(?<=hello )world/.test('hi world'));       // false
  4. // 提取前面是hello的world
  5. const regex = /(?<=hello )world/g;
  6. const str = 'hello world, hi world, hello world again';
  7. console.log(str.match(regex));  // ["world", "world"]
复制代码

(?<!y)x:匹配x,但只有在x前面不是y的情况下。
  1. // 匹配前面不是hello的world
  2. console.log(/(?<!hello )world/.test('hello world'));    // false
  3. console.log(/(?<!hello )world/.test('hi world'));       // true
  4. // 提取前面不是hello的world
  5. const regex = /(?<!hello )world/g;
  6. const str = 'hello world, hi world, hello world again';
  7. console.log(str.match(regex));  // ["world"]
复制代码

3. 标志(修饰符)

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

• g:全局匹配,查找所有匹配项,而不是在找到第一个匹配项后停止
• i:忽略大小写
• m:多行模式,使^和$匹配每行的开始和结束,而不是整个字符串的开始和结束
• s:dotAll模式,使.匹配包括换行符在内的所有字符(ES2018)
• u:Unicode模式,正确处理UTF-16编码的字符(ES2015)
• y:粘滞模式,只在目标字符串的当前位置匹配(ES2015)
  1. // g标志 - 全局匹配
  2. const regex1 = /hello/g;
  3. const str1 = 'hello world, hello everyone';
  4. console.log(str1.match(regex1));  // ["hello", "hello"]
  5. // i标志 - 忽略大小写
  6. const regex2 = /hello/i;
  7. console.log(regex2.test('Hello'));  // true
  8. console.log(regex2.test('HELLO'));  // true
  9. // m标志 - 多行模式
  10. const regex3 = /^hello/m;
  11. const str3 = 'hello world\nhello everyone';
  12. console.log(str3.match(regex3));  // ["hello", "hello"]
  13. // s标志 - dotAll模式
  14. const regex4 = /hello.world/s;
  15. console.log(regex4.test('hello\nworld'));  // true
  16. // u标志 - Unicode模式
  17. const regex5 = /\u{1F600}/u;  // 匹配表情符号😀
  18. console.log(regex5.test('😀'));  // true
  19. // y标志 - 粘滞模式
  20. const regex6 = /hello/y;
  21. const str6 = 'hello world';
  22. regex6.lastIndex = 6;  // 设置开始匹配的位置
  23. console.log(regex6.test(str6));  // false,因为位置6不是hello
复制代码

三、JavaScript中的正则表达式

1. RegExp对象

在JavaScript中,正则表达式是通过RegExp对象来表示的。可以通过字面量或构造函数创建正则表达式对象。
  1. // 字面量方式
  2. const regex1 = /pattern/flags;
  3. // 构造函数方式
  4. const regex2 = new RegExp('pattern', 'flags');
  5. // 使用变量作为模式
  6. const pattern = 'hello';
  7. const flags = 'gi';
  8. const regex3 = new RegExp(pattern, flags);
复制代码
  1. const regex = /hello.world/gi;
  2. console.log(regex.source);    // "hello.world",正则表达式的文本
  3. console.log(regex.flags);     // "gi",正则表达式的标志
  4. console.log(regex.global);    // true,是否设置了g标志
  5. console.log(regex.ignoreCase); // true,是否设置了i标志
  6. console.log(regex.multiline); // false,是否设置了m标志
  7. console.log(regex.dotAll);    // false,是否设置了s标志
  8. console.log(regex.unicode);   // false,是否设置了u标志
  9. console.log(regex.sticky);    // false,是否设置了y标志
  10. console.log(regex.lastIndex); // 0,下次匹配开始的索引
复制代码

2. RegExp对象的方法

test()方法用于测试字符串是否匹配正则表达式,返回true或false。
  1. const regex = /hello/;
  2. console.log(regex.test('hello world'));  // true
  3. console.log(regex.test('hi world'));     // false
  4. // 使用g标志时,lastIndex会影响匹配结果
  5. const regex2 = /hello/g;
  6. const str = 'hello world, hello everyone';
  7. console.log(regex2.test(str));  // true,匹配第一个hello
  8. console.log(regex2.lastIndex);  // 5,匹配结束的位置
  9. console.log(regex2.test(str));  // true,从位置5开始匹配第二个hello
  10. console.log(regex2.lastIndex);  // 18,匹配结束的位置
  11. console.log(regex2.test(str));  // false,没有更多匹配
  12. console.log(regex2.lastIndex);  // 0,重置为0
复制代码

exec()方法用于在字符串中执行匹配搜索,返回匹配结果的数组,如果没有匹配则返回null。
  1. const regex = /hello/;
  2. console.log(regex.exec('hello world'));  
  3. // ["hello", index: 0, input: "hello world", groups: undefined]
  4. console.log(regex.exec('hi world'));     
  5. // null
  6. // 使用g标志时,可以多次调用exec()来查找所有匹配
  7. const regex2 = /hello/g;
  8. const str = 'hello world, hello everyone';
  9. let match;
  10. while ((match = regex2.exec(str)) !== null) {
  11.   console.log(`Found ${match[0]} at index ${match.index}`);
  12.   // Found hello at index 0
  13.   // Found hello at index 13
  14. }
复制代码

3. String对象的方法

JavaScript中的String对象提供了多个接受正则表达式作为参数的方法。

match()方法用于检索字符串中与正则表达式匹配的结果,返回匹配结果的数组,如果没有匹配则返回null。
  1. const str = 'hello world, hello everyone';
  2. // 不使用g标志,只返回第一个匹配
  3. const result1 = str.match(/hello/);
  4. console.log(result1);  
  5. // ["hello", index: 0, input: "hello world, hello everyone", groups: undefined]
  6. // 使用g标志,返回所有匹配
  7. const result2 = str.match(/hello/g);
  8. console.log(result2);  // ["hello", "hello"]
  9. // 使用捕获组
  10. const result3 = str.match(/(hello) (world)/);
  11. console.log(result3);  
  12. // ["hello world", "hello", "world", index: 0, input: "hello world, hello everyone", groups: undefined]
复制代码

search()方法用于检索字符串中与正则表达式匹配的子串的起始位置,如果没有匹配则返回-1。
  1. const str = 'hello world, hello everyone';
  2. console.log(str.search(/hello/));    // 0
  3. console.log(str.search(/world/));    // 6
  4. console.log(str.search(/everyone/)); // 14
  5. console.log(str.search(/goodbye/));  // -1
复制代码

replace()方法用于替换字符串中与正则表达式匹配的子串,返回替换后的新字符串。
  1. const str = 'hello world, hello everyone';
  2. // 替换第一个匹配
  3. const result1 = str.replace(/hello/, 'hi');
  4. console.log(result1);  // "hi world, hello everyone"
  5. // 使用g标志替换所有匹配
  6. const result2 = str.replace(/hello/g, 'hi');
  7. console.log(result2);  // "hi world, hi everyone"
  8. // 使用捕获组
  9. const result3 = str.replace(/(hello) (world)/, '$2 $1');
  10. console.log(result3);  // "world hello, hello everyone"
  11. // 使用回调函数
  12. const result4 = str.replace(/hello/g, (match, index, original) => {
  13.   console.log(`Found ${match} at index ${index}`);
  14.   return 'hi';
  15. });
  16. console.log(result4);  // "hi world, hi everyone"
复制代码

split()方法用于将字符串分割为字符串数组,可以使用正则表达式作为分隔符。
  1. const str = 'hello world, hello everyone';
  2. // 使用逗号和空格分割
  3. const result1 = str.split(/, /);
  4. console.log(result1);  // ["hello world", "hello everyone"]
  5. // 使用空格分割
  6. const result2 = str.split(/\s/);
  7. console.log(result2);  // ["hello", "world,", "hello", "everyone"]
  8. // 限制分割次数
  9. const result3 = str.split(/\s/, 2);
  10. console.log(result3);  // ["hello", "world,"]
复制代码

matchAll()方法用于返回一个迭代器,该迭代器包含所有匹配的匹配结果及其捕获组。
  1. const str = 'hello world, hello everyone';
  2. // 使用g标志
  3. const regex = /hello/g;
  4. const matches = str.matchAll(regex);
  5. for (const match of matches) {
  6.   console.log(match);
  7. }
  8. // ["hello", index: 0, input: "hello world, hello everyone", groups: undefined]
  9. // ["hello", index: 13, input: "hello world, hello everyone", groups: undefined]
  10. // 使用捕获组
  11. const regex2 = /(hello) (world)/g;
  12. const matches2 = str.matchAll(regex2);
  13. for (const match of matches2) {
  14.   console.log(match);
  15. }
  16. // ["hello world", "hello", "world", index: 0, input: "hello world, hello everyone", groups: undefined]
复制代码

四、常见正则表达式实例

1. 表单验证
  1. function validateEmail(email) {
  2.   const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  3.   return regex.test(email);
  4. }
  5. console.log(validateEmail('user@example.com'));       // true
  6. console.log(validateEmail('user.name@example.com'));  // true
  7. console.log(validateEmail('user@sub.example.com'));   // true
  8. console.log(validateEmail('user@example'));           // false
  9. console.log(validateEmail('user.example.com'));       // false
  10. console.log(validateEmail('@example.com'));           // false
复制代码
  1. function validatePhoneNumber(phone) {
  2.   // 简化版,只验证中国大陆手机号
  3.   const regex = /^1[3-9]\d{9}$/;
  4.   return regex.test(phone);
  5. }
  6. console.log(validatePhoneNumber('13812345678'));  // true
  7. console.log(validatePhoneNumber('15912345678'));  // true
  8. console.log(validatePhoneNumber('12345678901'));  // false
  9. console.log(validatePhoneNumber('1381234567'));   // false
  10. console.log(validatePhoneNumber('138123456789')); // false
复制代码
  1. function validateIDCard(id) {
  2.   // 简化版,只验证18位身份证号码
  3.   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]$/;
  4.   return regex.test(id);
  5. }
  6. console.log(validateIDCard('11010519491231002X'));  // true
  7. console.log(validateIDCard('110105194912310021'));  // true
  8. console.log(validateIDCard('11010519491331002X'));  // false,月份错误
  9. console.log(validateIDCard('11010519491232002X'));  // false,日期错误
  10. console.log(validateIDCard('11010519491231002'));   // false,位数不足
复制代码
  1. function validatePassword(password) {
  2.   // 至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符
  3.   const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
  4.   return regex.test(password);
  5. }
  6. console.log(validatePassword('Password123!'));     // true
  7. console.log(validatePassword('password123!'));     // false,缺少大写字母
  8. console.log(validatePassword('PASSWORD123!'));     // false,缺少小写字母
  9. console.log(validatePassword('Password!'));        // false,缺少数字
  10. console.log(validatePassword('Password123'));      // false,缺少特殊字符
  11. console.log(validatePassword('Pass12!'));          // false,长度不足
复制代码

2. 数据提取
  1. function getUrlParams(url) {
  2.   const params = {};
  3.   const regex = /[?&]([^=#]+)=([^&#]*)/g;
  4.   let match;
  5.   
  6.   while ((match = regex.exec(url)) !== null) {
  7.     params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
  8.   }
  9.   
  10.   return params;
  11. }
  12. const url = 'https://example.com?name=John&age=30&city=New%20York';
  13. console.log(getUrlParams(url));
  14. // { name: "John", age: "30", city: "New York" }
复制代码
  1. function extractHTMLTags(html) {
  2.   const regex = /<(\w+)(?:\s+[^>]*)?>(.*?)<\/\1>/gs;
  3.   const matches = [];
  4.   let match;
  5.   
  6.   while ((match = regex.exec(html)) !== null) {
  7.     matches.push({
  8.       tag: match[1],
  9.       content: match[2]
  10.     });
  11.   }
  12.   
  13.   return matches;
  14. }
  15. const html = '<div class="container"><p>Hello, <span>world</span>!</p></div>';
  16. console.log(extractHTMLTags(html));
  17. // [
  18. //   { tag: "div", content: "<p>Hello, <span>world</span>!</p>" },
  19. //   { tag: "p", content: "Hello, <span>world</span>!" },
  20. //   { tag: "span", content: "world" }
  21. // ]
复制代码
  1. function extractMarkdownLinks(markdown) {
  2.   const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
  3.   const links = [];
  4.   let match;
  5.   
  6.   while ((match = regex.exec(markdown)) !== null) {
  7.     links.push({
  8.       text: match[1],
  9.       url: match[2]
  10.     });
  11.   }
  12.   
  13.   return links;
  14. }
  15. const markdown = 'Visit [GitHub](https://github.com) and [Stack Overflow](https://stackoverflow.com).';
  16. console.log(extractMarkdownLinks(markdown));
  17. // [
  18. //   { text: "GitHub", url: "https://github.com" },
  19. //   { text: "Stack Overflow", url: "https://stackoverflow.com" }
  20. // ]
复制代码

3. 文本替换
  1. function removeHTMLTags(html) {
  2.   return html.replace(/<[^>]*>/g, '');
  3. }
  4. const html = '<div class="container"><p>Hello, <span>world</span>!</p></div>';
  5. console.log(removeHTMLTags(html));  // "Hello, world!"
复制代码
  1. function formatPhoneNumber(phone) {
  2.   // 假设输入是10位数字(美国电话号码格式)
  3.   return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
  4. }
  5. console.log(formatPhoneNumber('1234567890'));  // "(123) 456-7890"
复制代码
  1. function hideSensitiveInfo(text) {
  2.   // 隐藏邮箱地址
  3.   text = text.replace(/(\w)[\w.-]*@([\w.]+\w)/g, '$1***@$2');
  4.   
  5.   // 隐藏手机号码
  6.   text = text.replace(/(\d{3})\d{4}(\d{4})/g, '$1****$2');
  7.   
  8.   // 隐藏身份证号码
  9.   text = text.replace(/(\d{6})\d{8}(\d{4})/g, '$1********$2');
  10.   
  11.   return text;
  12. }
  13. const text = 'Contact me at user@example.com or call 13812345678. My ID is 11010519491231002X.';
  14. console.log(hideSensitiveInfo(text));
  15. // "Contact me at u***@example.com or call 138****5678. My ID is 110105********02X."
复制代码

4. 文本搜索
  1. function highlightKeywords(text, keywords) {
  2.   // 创建正则表达式,匹配所有关键词
  3.   const regex = new RegExp(`(${keywords.join('|')})`, 'gi');
  4.   
  5.   // 使用回调函数替换匹配的关键词
  6.   return text.replace(regex, '<mark>$1</mark>');
  7. }
  8. const text = 'JavaScript is a popular programming language. Many developers use JavaScript for web development.';
  9. const keywords = ['JavaScript', 'developers', 'web'];
  10. console.log(highlightKeywords(text, keywords));
  11. // "<mark>JavaScript</mark> is a popular programming language. Many <mark>developers</mark> use <mark>JavaScript</mark> for <mark>web</mark> development."
复制代码
  1. function findDuplicateWords(text) {
  2.   const regex = /\b(\w+)\s+\1\b/gi;
  3.   const duplicates = [];
  4.   let match;
  5.   
  6.   while ((match = regex.exec(text)) !== null) {
  7.     duplicates.push({
  8.       word: match[1],
  9.       index: match.index
  10.     });
  11.   }
  12.   
  13.   return duplicates;
  14. }
  15. const text = 'This is is a test test to find duplicate words.';
  16. console.log(findDuplicateWords(text));
  17. // [
  18. //   { word: "is", index: 5 },
  19. //   { word: "test", index: 15 }
  20. // ]
复制代码

五、性能优化和最佳实践

1. 正则表达式性能优化

回溯是正则表达式性能问题的常见原因。当正则表达式有多种可能的匹配路径时,引擎会尝试所有路径直到找到匹配或所有可能性都耗尽。
  1. // 低效的正则表达式,可能导致回溯灾难
  2. const inefficientRegex = /^(a+)+$/;
  3. // 更高效的正则表达式
  4. const efficientRegex = /^a+$/;
复制代码

如果不需要捕获组的内容,使用非捕获组(?:)可以提高性能。
  1. // 使用捕获组
  2. const withCapturing = /(hello|world) (hello|world)/;
  3. // 使用非捕获组
  4. const withoutCapturing = /(?:hello|world) (?:hello|world)/;
复制代码

使用具体的字符类而不是.或\w等通用字符类可以提高性能。
  1. // 使用通用字符类
  2. const generic = /\d+-\d+-\d+/;
  3. // 使用具体字符类
  4. const specific = /[0-9]+-[0-9]+-[0-9]+/;
复制代码

贪婪量词(如.*)会尝试匹配尽可能多的字符,可能导致不必要的回溯。使用惰性量词(如.*?)或更具体的模式可以提高性能。
  1. // 贪婪量词
  2. const greedy = /<div>.*<\/div>/;
  3. // 惰性量词
  4. const lazy = /<div>.*?<\/div>/;
  5. // 更具体的模式
  6. const specific = /<div>[^<]*<\/div>/;
复制代码

2. 正则表达式最佳实践

如果正则表达式是静态的,使用字面量语法而不是构造函数可以提高性能。
  1. // 字面量语法(推荐)
  2. const regex1 = /pattern/g;
  3. // 构造函数语法
  4. const regex2 = new RegExp('pattern', 'g');
复制代码

如果多次使用同一个正则表达式,将其缓存起来可以提高性能。
  1. // 不缓存
  2. function validateEmail(email) {
  3.   return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email);
  4. }
  5. // 缓存
  6. const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  7. function validateEmail(email) {
  8.   return emailRegex.test(email);
  9. }
复制代码

根据需要使用适当的标志,避免不必要的全局匹配。
  1. // 只需要测试是否匹配,不需要g标志
  2. const regex1 = /hello/;
  3. // 需要找到所有匹配,使用g标志
  4. const regex2 = /hello/g;
复制代码

复杂的正则表达式应该添加注释和文档,以便他人理解和维护。
  1. // 验证邮箱地址的正则表达式
  2. // 格式:local-part@domain
  3. // local-part: 字母、数字、点、下划线、百分号、加号或减号
  4. // domain: 字母、数字、点和减号,后跟点和至少2个字母
  5. const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
复制代码

六、实战案例

1. 构建一个简单的模板引擎
  1. class SimpleTemplateEngine {
  2.   constructor(template) {
  3.     this.template = template;
  4.     // 匹配 {{ variable }} 格式的变量
  5.     this.variableRegex = /\{\{\s*([^}\s]+)\s*\}\}/g;
  6.     // 匹配 {% if condition %}...{% endif %} 格式的条件语句
  7.     this.ifRegex = /\{%\s*if\s+([^}]+)\s*%\}([\s\S]*?)\{%\s*endif\s*%\}/g;
  8.     // 匹配 {% for item in items %}...{% endfor %} 格式的循环语句
  9.     this.forRegex = /\{%\s*for\s+(\w+)\s+in\s+(\w+)\s*%\}([\s\S]*?)\{%\s*endfor\s*%\}/g;
  10.   }
  11.   
  12.   render(context) {
  13.     let result = this.template;
  14.    
  15.     // 处理条件语句
  16.     result = this._processIfStatements(result, context);
  17.    
  18.     // 处理循环语句
  19.     result = this._processForStatements(result, context);
  20.    
  21.     // 处理变量
  22.     result = this._processVariables(result, context);
  23.    
  24.     return result;
  25.   }
  26.   
  27.   _processVariables(template, context) {
  28.     return template.replace(this.variableRegex, (match, variable) => {
  29.       // 支持嵌套属性,如 user.name
  30.       const properties = variable.split('.');
  31.       let value = context;
  32.       
  33.       for (const prop of properties) {
  34.         if (value && typeof value === 'object' && prop in value) {
  35.           value = value[prop];
  36.         } else {
  37.           return '';
  38.         }
  39.       }
  40.       
  41.       return value !== null && value !== undefined ? value : '';
  42.     });
  43.   }
  44.   
  45.   _processIfStatements(template, context) {
  46.     let result = template;
  47.     let match;
  48.    
  49.     while ((match = this.ifRegex.exec(template)) !== null) {
  50.       const condition = match[1];
  51.       const content = match[2];
  52.       
  53.       try {
  54.         // 简单的条件评估,实际应用中可能需要更复杂的解析器
  55.         const conditionResult = this._evaluateCondition(condition, context);
  56.         const replacement = conditionResult ? content : '';
  57.         result = result.replace(match[0], replacement);
  58.       } catch (e) {
  59.         console.error('Error evaluating condition:', condition, e);
  60.         result = result.replace(match[0], '');
  61.       }
  62.     }
  63.    
  64.     return result;
  65.   }
  66.   
  67.   _processForStatements(template, context) {
  68.     let result = template;
  69.     let match;
  70.    
  71.     while ((match = this.forRegex.exec(template)) !== null) {
  72.       const itemVar = match[1];
  73.       const listVar = match[2];
  74.       const content = match[3];
  75.       
  76.       try {
  77.         const list = this._getValueFromContext(listVar, context);
  78.         let replacement = '';
  79.         
  80.         if (Array.isArray(list)) {
  81.           for (const item of list) {
  82.             // 创建新的上下文,包含当前项
  83.             const itemContext = { ...context, [itemVar]: item };
  84.             replacement += this._processVariables(content, itemContext);
  85.           }
  86.         }
  87.         
  88.         result = result.replace(match[0], replacement);
  89.       } catch (e) {
  90.         console.error('Error processing for loop:', e);
  91.         result = result.replace(match[0], '');
  92.       }
  93.     }
  94.    
  95.     return result;
  96.   }
  97.   
  98.   _evaluateCondition(condition, context) {
  99.     // 简单的条件评估,支持 ==, !=, >, <, >=, <=
  100.     const operators = ['==', '!=', '>', '<', '>=', '<='];
  101.    
  102.     for (const op of operators) {
  103.       if (condition.includes(op)) {
  104.         const [left, right] = condition.split(op).map(s => s.trim());
  105.         const leftValue = this._getValueFromContext(left, context);
  106.         const rightValue = this._getValueFromContext(right, context);
  107.         
  108.         switch (op) {
  109.           case '==': return leftValue == rightValue;
  110.           case '!=': return leftValue != rightValue;
  111.           case '>': return leftValue > rightValue;
  112.           case '<': return leftValue < rightValue;
  113.           case '>=': return leftValue >= rightValue;
  114.           case '<=': return leftValue <= rightValue;
  115.         }
  116.       }
  117.     }
  118.    
  119.     // 如果没有找到操作符,检查变量是否为真
  120.     return !!this._getValueFromContext(condition, context);
  121.   }
  122.   
  123.   _getValueFromContext(path, context) {
  124.     // 支持嵌套属性,如 user.name
  125.     const properties = path.split('.');
  126.     let value = context;
  127.    
  128.     for (const prop of properties) {
  129.       if (value && typeof value === 'object' && prop in value) {
  130.         value = value[prop];
  131.       } else {
  132.         return undefined;
  133.       }
  134.     }
  135.    
  136.     return value;
  137.   }
  138. }
  139. // 使用示例
  140. const template = `
  141. <div>
  142.   <h1>{{ title }}</h1>
  143.   <p>{{ description }}</p>
  144.   
  145.   {% if showList %}
  146.   <ul>
  147.     {% for item in items %}
  148.     <li>{{ item.name }} - {{ item.price }}</li>
  149.     {% endfor %}
  150.   </ul>
  151.   {% endif %}
  152.   
  153.   {% if user.loggedIn %}
  154.   <p>Welcome, {{ user.name }}!</p>
  155.   {% else %}
  156.   <p>Please <a href="/login">log in</a>.</p>
  157.   {% endif %}
  158. </div>
  159. `;
  160. const context = {
  161.   title: 'Product List',
  162.   description: 'Here are our products:',
  163.   showList: true,
  164.   items: [
  165.     { name: 'Product 1', price: '$10' },
  166.     { name: 'Product 2', price: '$20' },
  167.     { name: 'Product 3', price: '$30' }
  168.   ],
  169.   user: {
  170.     loggedIn: true,
  171.     name: 'John Doe'
  172.   }
  173. };
  174. const engine = new SimpleTemplateEngine(template);
  175. console.log(engine.render(context));
复制代码

2. 构建一个简单的查询语言解析器
  1. class SimpleQueryParser {
  2.   constructor() {
  3.     // 匹配字段:操作符:值 格式的查询条件
  4.     this.conditionRegex = /(\w+)(:|=|!=|>|<|>=|<=)([^&|]+)/g;
  5.     // 匹配 AND 和 OR 操作符
  6.     this.operatorRegex = /\s*(\&\&|\|\|)\s*/g;
  7.   }
  8.   
  9.   parse(query) {
  10.     // 预处理查询,替换 AND 和 OR 为 && 和 ||
  11.     query = query.replace(/\bAND\b/g, '&&').replace(/\bOR\b/g, '||');
  12.    
  13.     // 提取所有条件
  14.     const conditions = [];
  15.     let match;
  16.    
  17.     while ((match = this.conditionRegex.exec(query)) !== null) {
  18.       conditions.push({
  19.         field: match[1],
  20.         operator: match[2],
  21.         value: match[3].trim(),
  22.         fullMatch: match[0]
  23.       });
  24.     }
  25.    
  26.     // 提取所有操作符
  27.     const operators = [];
  28.     const operatorMatches = query.match(this.operatorRegex);
  29.     if (operatorMatches) {
  30.       operators.push(...operatorMatches);
  31.     }
  32.    
  33.     // 构建解析树
  34.     const tree = this._buildParseTree(conditions, operators);
  35.    
  36.     return {
  37.       query,
  38.       conditions,
  39.       operators,
  40.       tree,
  41.       evaluate: (data) => this._evaluateTree(tree, data)
  42.     };
  43.   }
  44.   
  45.   _buildParseTree(conditions, operators) {
  46.     if (conditions.length === 0) {
  47.       return null;
  48.     }
  49.    
  50.     if (conditions.length === 1) {
  51.       return {
  52.         type: 'condition',
  53.         condition: conditions[0]
  54.       };
  55.     }
  56.    
  57.     // 简单的优先级处理:AND 优先于 OR
  58.     // 实际应用中可能需要更复杂的优先级处理
  59.     const andIndex = operators.indexOf('&&');
  60.    
  61.     if (andIndex !== -1) {
  62.       return {
  63.         type: 'operator',
  64.         operator: '&&',
  65.         left: this._buildParseTree(conditions.slice(0, andIndex + 1), operators.slice(0, andIndex)),
  66.         right: this._buildParseTree(conditions.slice(andIndex + 1), operators.slice(andIndex + 1))
  67.       };
  68.     } else {
  69.       const orIndex = operators.indexOf('||');
  70.       return {
  71.         type: 'operator',
  72.         operator: '||',
  73.         left: this._buildParseTree(conditions.slice(0, orIndex + 1), operators.slice(0, orIndex)),
  74.         right: this._buildParseTree(conditions.slice(orIndex + 1), operators.slice(orIndex + 1))
  75.       };
  76.     }
  77.   }
  78.   
  79.   _evaluateTree(tree, data) {
  80.     if (!tree) {
  81.       return true;
  82.     }
  83.    
  84.     if (tree.type === 'condition') {
  85.       return this._evaluateCondition(tree.condition, data);
  86.     }
  87.    
  88.     if (tree.type === 'operator') {
  89.       const left = this._evaluateTree(tree.left, data);
  90.       const right = this._evaluateTree(tree.right, data);
  91.       
  92.       if (tree.operator === '&&') {
  93.         return left && right;
  94.       } else if (tree.operator === '||') {
  95.         return left || right;
  96.       }
  97.     }
  98.    
  99.     return false;
  100.   }
  101.   
  102.   _evaluateCondition(condition, data) {
  103.     const { field, operator, value } = condition;
  104.    
  105.     // 获取字段的值
  106.     let fieldValue = data;
  107.     const fieldParts = field.split('.');
  108.    
  109.     for (const part of fieldParts) {
  110.       if (fieldValue && typeof fieldValue === 'object' && part in fieldValue) {
  111.         fieldValue = fieldValue[part];
  112.       } else {
  113.         fieldValue = undefined;
  114.         break;
  115.       }
  116.     }
  117.    
  118.     // 尝试解析值为数字或布尔值
  119.     let parsedValue;
  120.     if (value.toLowerCase() === 'true') {
  121.       parsedValue = true;
  122.     } else if (value.toLowerCase() === 'false') {
  123.       parsedValue = false;
  124.     } else if (!isNaN(value) && value.trim() !== '') {
  125.       parsedValue = Number(value);
  126.     } else {
  127.       parsedValue = value;
  128.     }
  129.    
  130.     // 根据操作符比较值
  131.     switch (operator) {
  132.       case ':':
  133.       case '=':
  134.         return fieldValue == parsedValue;
  135.       case '!=':
  136.         return fieldValue != parsedValue;
  137.       case '>':
  138.         return fieldValue > parsedValue;
  139.       case '<':
  140.         return fieldValue < parsedValue;
  141.       case '>=':
  142.         return fieldValue >= parsedValue;
  143.       case '<=':
  144.         return fieldValue <= parsedValue;
  145.       default:
  146.         return false;
  147.     }
  148.   }
  149. }
  150. // 使用示例
  151. const parser = new SimpleQueryParser();
  152. // 简单查询
  153. const query1 = 'name:John AND age:30';
  154. const parsed1 = parser.parse(query1);
  155. console.log(parsed1);
  156. const data1 = { name: 'John', age: 30 };
  157. console.log(parsed1.evaluate(data1));  // true
  158. const data2 = { name: 'John', age: 25 };
  159. console.log(parsed1.evaluate(data2));  // false
  160. // 复杂查询
  161. const query2 = 'name:John AND (age:>25 OR city:"New York")';
  162. // 注意:这个简化版本不支持括号,实际应用中需要更复杂的解析器
  163. const parsed2 = parser.parse('name:John AND age:>25 OR city:"New York"');
  164. console.log(parsed2);
  165. const data3 = { name: 'John', age: 30, city: 'Boston' };
  166. console.log(parsed2.evaluate(data3));  // true
  167. const data4 = { name: 'John', age: 20, city: 'New York' };
  168. console.log(parsed2.evaluate(data4));  // true
  169. const data5 = { name: 'Jane', age: 30, city: 'New York' };
  170. console.log(parsed2.evaluate(data5));  // false
复制代码

3. 构建一个简单的代码高亮器
  1. class SimpleCodeHighlighter {
  2.   constructor(language = 'javascript') {
  3.     this.language = language;
  4.     this.rules = this._getRulesForLanguage(language);
  5.   }
  6.   
  7.   highlight(code) {
  8.     let highlightedCode = code;
  9.    
  10.     // 应用所有规则
  11.     for (const rule of this.rules) {
  12.       highlightedCode = highlightedCode.replace(rule.regex, (match, ...groups) => {
  13.         // 处理捕获组
  14.         let replacement = rule.replacement;
  15.         
  16.         // 替换捕获组引用
  17.         for (let i = 0; i < groups.length - 2; i++) {
  18.           replacement = replacement.replace(new RegExp(`\\$${i + 1}`, 'g'), groups[i]);
  19.         }
  20.         
  21.         return replacement;
  22.       });
  23.     }
  24.    
  25.     return highlightedCode;
  26.   }
  27.   
  28.   _getRulesForLanguage(language) {
  29.     const rules = {
  30.       javascript: [
  31.         // 关键字
  32.         {
  33.           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,
  34.           replacement: '<span class="keyword">$1</span>'
  35.         },
  36.         // 字符串
  37.         {
  38.           regex: /(['"])((?:\\\1|(?!\1).)*)\1/g,
  39.           replacement: '<span class="string">$1$2$1</span>'
  40.         },
  41.         // 数字
  42.         {
  43.           regex: /\b(\d+\.?\d*|\.\d+)\b/g,
  44.           replacement: '<span class="number">$1</span>'
  45.         },
  46.         // 注释
  47.         {
  48.           regex: /(\/\/.*$|\/\*[\s\S]*?\*\/)/gm,
  49.           replacement: '<span class="comment">$1</span>'
  50.         },
  51.         // 正则表达式
  52.         {
  53.           regex: /\/(?![*])([^\/\n\\]*(?:\\.[^\/\n\\]*)*)\/[gimuy]*/g,
  54.           replacement: '<span class="regex">$&</span>'
  55.         },
  56.         // 函数调用
  57.         {
  58.           regex: /\b([a-zA-Z_]\w*)\s*\(/g,
  59.           replacement: '<span class="function">$1</span>('
  60.         },
  61.         // 操作符
  62.         {
  63.           regex: /(\+|\-|\*|\/|%|&|\||\^|!|~|=|<|>|?|:|\.|,|;|\{|\}|\(|\)|\[|\])/g,
  64.           replacement: '<span class="operator">$1</span>'
  65.         }
  66.       ],
  67.       html: [
  68.         // 标签
  69.         {
  70.           regex: /<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>/g,
  71.           replacement: '<span class="tag">&lt;$1</span><span class="tag-attributes">$2</span><span class="tag">&gt;</span>'
  72.         },
  73.         // 属性
  74.         {
  75.           regex: /\b([a-zA-Z-]+)\s*=\s*("[^"]*"|'[^']*'|[^>\s]*)/g,
  76.           replacement: '<span class="attribute">$1</span>=<span class="attribute-value">$2</span>'
  77.         },
  78.         // 注释
  79.         {
  80.           regex: /(<!--[\s\S]*?-->)/g,
  81.           replacement: '<span class="comment">$1</span>'
  82.         },
  83.         // DOCTYPE
  84.         {
  85.           regex: /<!DOCTYPE[^>]*>/gi,
  86.           replacement: '<span class="doctype">$&</span>'
  87.         }
  88.       ],
  89.       css: [
  90.         // 选择器
  91.         {
  92.           regex: /([^{]+)\{/g,
  93.           replacement: '<span class="selector">$1</span>{'
  94.         },
  95.         // 属性
  96.         {
  97.           regex: /\b([a-zA-Z-]+)\s*:/g,
  98.           replacement: '<span class="property">$1</span>:'
  99.         },
  100.         // 值
  101.         {
  102.           regex: /:\s*([^;};]+);/g,
  103.           replacement: ': <span class="value">$1</span>;'
  104.         },
  105.         // 注释
  106.         {
  107.           regex: /(\/\*[\s\S]*?\*\/)/g,
  108.           replacement: '<span class="comment">$1</span>'
  109.         },
  110.         // 颜色
  111.         {
  112.           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,
  113.           replacement: '<span class="color">$1</span>'
  114.         },
  115.         // 单位
  116.         {
  117.           regex: /\b(\d+)(px|em|rem|%|vh|vw|vmin|vmax|pt|pc|in|cm|mm)\b/g,
  118.           replacement: '<span class="number">$1</span><span class="unit">$2</span>'
  119.         }
  120.       ]
  121.     };
  122.    
  123.     return rules[language] || [];
  124.   }
  125. }
  126. // 使用示例
  127. const highlighter = new SimpleCodeHighlighter('javascript');
  128. const jsCode = `
  129. function fibonacci(n) {
  130.   if (n <= 1) {
  131.     return n;
  132.   }
  133.   
  134.   return fibonacci(n - 1) + fibonacci(n - 2);
  135. }
  136. // 计算前10个斐波那契数
  137. for (let i = 0; i < 10; i++) {
  138.   console.log(\`fibonacci(\${i}) = \${fibonacci(i)}\`);
  139. }
  140. `;
  141. console.log(highlighter.highlight(jsCode));
  142. const htmlHighlighter = new SimpleCodeHighlighter('html');
  143. const htmlCode = `
  144. <!DOCTYPE html>
  145. <html lang="en">
  146. <head>
  147.   <meta charset="UTF-8">
  148.   <title>Sample Page</title>
  149. </head>
  150. <body>
  151.   <div class="container">
  152.     <h1 id="title">Hello, World!</h1>
  153.     <p>This is a sample paragraph.</p>
  154.   </div>
  155. </body>
  156. </html>
  157. `;
  158. console.log(htmlHighlighter.highlight(htmlCode));
  159. const cssHighlighter = new SimpleCodeHighlighter('css');
  160. const cssCode = `
  161. .container {
  162.   width: 100%;
  163.   max-width: 1200px;
  164.   margin: 0 auto;
  165.   padding: 20px;
  166.   background-color: #f5f5f5;
  167.   border-radius: 5px;
  168. }
  169. #title {
  170.   font-size: 2rem;
  171.   color: #333;
  172.   text-align: center;
  173. }
  174. /* 响应式设计 */
  175. @media (max-width: 768px) {
  176.   .container {
  177.     padding: 10px;
  178.   }
  179.   
  180.   #title {
  181.     font-size: 1.5rem;
  182.   }
  183. }
  184. `;
  185. 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应用的复杂化,对文本处理的需求也会不断增加,正则表达式作为一种高效、灵活的文本处理工具,将继续发挥重要作用。

通过不断学习和实践,我们可以更好地掌握正则表达式这一强大的工具,为我们的开发工作带来更多便利和效率。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则