写在开头,扫一眼
讲个故事,常态化的加班,今天上面再和客户沟通的时候,客户说,你们咋这么能抗,随时打电话随时在(为什么这么能加班!),上面说因为你的项目着急,技术同事都在努力。本来到这里还正常,没想到客户说,那工资一定很高吧,上面说,“还行,市面上不算太低(diss:明明马上就到地。。),还有项目奖金(diss:这里应该是笑的,上面真能吹牛皮,可是一想到连加班。。咳咳,为什么鼻子一酸呢,好想哭)”。后来有同事咳嗽了两声,估计是听不下去了吧,上面背上电脑就走了。
你们也都一直在加班吗,别让我知道有人不是996,鸡哔你。
加班谁研究的,真是,劳人伤神。
文章7K字,系统梳理加案例解读辅佐你更好的一文学会正则。建议初中级同学能坚持把文章看下去。如果有发现错误的地方,请及时评论告诉小编,小编会及时做调整。
正文开始。
正则表达式简介
-
正则表达式的定义:什么是正则边表达式,为什么要使用它。
- 正则表达式(Regular Expression)是一种用于描述字符串模式的工具,允许进行复杂的字符串匹配和搜索。
- 使用正则表达式可以简化数据验证、文本搜索和替换等任务。
- 为什么要使用它,是个很好的问题,因为开发需要,面试更需要!
-
正则表达式的应用场景:文本匹配、数据验证、字符串替换等。
-
ex1:文本匹配
const phoneRegex = /\((\d{3})\) (\d{3})-(\d{4})/g; const text = "请拨打电话(123) 456-7890 联系我们。"; const replacedText = text.replace(phoneRegex, "$1-$2-$3"); console.log(replacrText); // 请拨打电话 123-456-7890 联系我们。开始拆解分析
-
正则表达式
/:正则表达式的开始和结束标记。\(和\):匹配字面意义上的括号。由于括号在正则表达式中有特使意义,因此需要用\转义。(\d{3}):匹配三位数字,并将其捕获为一个组。这里的\d代表数字。:注意,中间有空格,匹配一个空格。(\d{3}): 再次匹配并捕获3位数字,作为第二个组。-:匹配字面意义上的连字符。(\d{4}):匹配4位数字,并捕获为第三个组。/g:表示全局搜索,即替换文本中所有匹配的部分。
-
text.replace()方法:text:是待处理的字符串replace(phoneRegex, "$1-$2-$3"):phoneRegex:是前面定义的正则表达式,用于匹配目标格式的电话号码。"$1-$2-$3":这里的$1、$2和$3分别代表正则表达式中捕获的三个组(即区号、前缀和后缀)。通过这种方式,原来的格式(123) 456-7890被替换为123-456-7890。
-
-
ex2:数据验证
const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/; const vaildEmail = eamil => emailRegex.test(email); vaildEmail('pang_tt@163.com') // true vaildEmail('pang_tt@pang-tt.163.cn.com') // true开始拆解分析
-
正则表达式
^:表示字符串的开始[\w-\.]+匹配一个或者多个字母、数字、下划线、连字符或点(即用户名称部分)@: 匹配一个@符号([\w-]+\.)+:匹配一个或多个字母、数字或连字符,后跟一个点(即域名部分),整个部分可以出现多次,比如example.com或wxample.co.cn。[\w-]{2,4}:匹配2到4个字符或数字(即顶级域名,如com、org、net等)。$:表示字符串的结束。
-
vaildEmail:函数:- 接受一个
email字符串作为参数。 - 使用
emailRegex.text(email)方法检查该字符串是否符合正则表达式的模式。 - 返回布尔值,如果匹配成功,返回true,否则返回false。
- 接受一个
-
-
重点:设计思路剖析
-
输入格式的严格性:起始符
^和结束符$- 这些符号强制规定整个字符串必须完全匹配。这一设计确保整个字符串都符合邮箱格式,而不是检查部分字符串。它避免了潜在的安全漏洞或者错误输入,例如abv@domain.com text会被视为无效。- 用户部分
[\w-\.]+:这一部分的设计考虑到了电子邮件用户名的灵活性,允许包含字母、数字、下划线、连字符和点号,也限制了其他字符避免了非法输入(如空格或特殊字符)。 - 域名部分
([\w-]+\.)+- 捕获组
([\w-]+\.)+:该部分匹配邮箱域名,例如example.或mail.,可以匹配一个或多个这样的域名段。 - 可重复部分
+:允许多层次的子域名,比如mail.example.com。这是通过+来实现的,即使邮箱域名有多个点分割的第二部分,也可以处理。 - 顶级域名部分
[\w-]{2,4}[\w-]{2,4}:这个部分匹配顶级域名(TLD),例如com、org等。匹配长度为2-4的部分,确保只捕获常见的TLD形式。- 此设计较为灵活,可以覆盖大多数常见的顶级域名。这种设计确保避免过渡宽松的匹配,但也能适应大多数有效邮箱。
- 捕获组
- 用户部分
-
为什么要这样设计
- 精确控制输入范围:通过每个部分的细致分割,正则表达式能够有效地控制每个部分的输入。用户名、域名、顶级域名部分都有各自的限定符,确保不符合标准的输入被拒绝。
- 安全性
- 使用正则表达式来限定输入的格式是一种防御性编程的设计,确保不合法的输入被排除。这对于防止恶意输入或潜在的安全漏洞至关重要。
- 尤其是在处理表单输入时,确保邮箱格式正确可以避免恶意用户注入不合法的字符串,进而可能触发数据库或者系统漏洞。
- 灵活性和可扩展性
- 该设计虽然比较严格,但也保留一定灵活性。正则表达式允许用户部分有较多样的字符选择,域名部分也能匹配复杂的域名结构。同时,顶级域名的长度设计可以通过简单的修改来适应新的需求,比如允许更长的顶级域名。
- 简介性与可读性
- 虽然正则表达式看起来复杂,但实际上它通过几个关键部分将邮箱格式进行了清晰的分解,最终生成一个简洁的验证工具。相比较于手动检查每一部分,这种方式更加直观和高效。
-
设计模式总结
- 分层匹配与捕获:通过分离用户名、域名、顶级域名的不同部分,使得正则表达式清晰且具有层次感。每一部分的设计都针对特定的需求进行了匹配和控制,确保只接受有效输入。
- 灵活而有约束:在设计中既考虑了通用的输入请求(允许字母、数字、连字符、点号等),又通过边界控制、字符长度限制等约束提高了安全性和准确性。
- 可扩展的匹配模式:域名部分设计的比较灵活,可以适应多级域名,顶级域名部分则可以根据需要修改匹配长度,从而支持更多新兴的TLD。
基本字符与符号
- 字面字符:
-
匹配普通字符(例如 abc 匹配字符串 "abc")
// 示例 const regex = /abc/; const text1 = "这是一个包含 abc 的字符串。"; const text2 = "这个字符串不包含目标字符串。"; // 检查 text1 是否包含 "abc" regex.test(text1) // true // 检查 text2 是否包含 "abc" regex.test(text2) // false相信有些小伙伴呢,从上文读下来会发现,为什么
/和/^都可以称为正则的开始呢?答案来了/abc/和/^abc$/是两种不同的正则表达式,他们的区别在于匹配的范围和条件。/abc/:这是一个简单的匹配模式,表示匹配任何包含“abc”的字符串。不限制“abc”出现在字符串的具体位置,只要字符串中包含这个字符序列,就可以匹配成功。
const regex = /abc/; console.log(regex.test("abc")); // true console.log(regex.test("123abc456")); // true console.log(regex.test("def")) // false/^abc$/:这是一个精确匹配模式,其中^表示匹配字符串的开头,$表示匹配字符串的结尾。这种模式要求字符串完全等于“abc”,不能有其他字符。
const regex = /^abc$/; console.log(regex.test("abc")) // true console.log(regex.test("123abc456")); // false console.log(regex.test("def")) // false -
匹配数字、字母、特殊字符
上案例,单独匹配数字、字母、特殊字符
// 匹配数字 const numberRegex = /\d/; // 匹配字母(包括大小写) const letterRegex = /[a-zA-Z]/; // 匹配特殊字符 const specialRegex = /[!@#\$%\^\&*\)\(+=._-]/; /* 这里需要打一个备注 `[]`:用来定义字符类,即表示在这个位置可以匹配括号内任意一个字符。这个字符类中的每个字符都是单独的选项,不需要按照顺序匹配 **为什么要这么设计** 1、**覆盖常见特殊字符**:这个正则表达式捕捉了很多常见的特殊字符。这些字符经常在密码、标识符或者验证输入中使用,因此在表单验证、密码强度检测等应用场景中,这些字符的匹配是必要的。 2、**避免歧义**:有些符号在正则表达式中具有特殊意义,例如`.`、`(`、`)`等,因此需要特别处理,确保他们按照 **字面** 意义匹配。所以需要适当的转义符号`\`来确保匹配的是符号本身。 3、**灵活性与简洁性**:使用字符类`[]`的设计能够在一个字符位置同时匹配多个字符,简化了代码逻辑。如果需要匹配多个特殊字符,不需要多次调用正则或`|`(或运算符),而是将所有可能的字符组合在一起,提高了正则的简洁性和可读性。 */ const text1 = "Hello123!"; const text2 = "OnlyText"; const text3 = "12345"; const text4 = "!!@@"; console.log(numberRegex.test(text1)); // true(包含数字) console.log(letterRegex.test(text1)); // true(包含字母) console.log(specialRegex.test(text1)); // true(包含特殊字符) console.log(numberRegex.test(text2)); // false(不包含数字) console.log(letterRegex.test(text2)); // true(包含字母) console.log(specialRegex.test(text2)); // false(不包含特殊字符) console.log(numberRegex.test(text3)); // true(包含数字) console.log(letterRegex.test(text3)); // false(不包含字母) console.log(specialRegex.test(text3)); // false(不包含特殊字符) console.log(numberRegex.test(text4)); // false(不包含字母) console.log(letterRegex.test(text4)); // false(不包含字母) console.log(specialRegex.test(text4)); // true(包含特殊字符)继续上强度:匹配必须包含数字、字母和特殊字符的字符串
const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[!@#\$%\^\&*\)\(+=._-]).{8,}$/; const password1 = "Password123!"; const password2 = "NoSpecial123"; const password3 = "NoNumber!"; const password4 = "short1!"; console.log(passwordRegex.test(password1)); // true(包含字母、数字、特殊字符,且长度>=8) console.log(passwordRegex.test(password2)); // false(缺少特殊字符) console.log(passwordRegex.test(password3)); // false(缺少数字) console.log(passwordRegex.test(password4)); // false(长度不足8个字符) /* 做个拆解 `(?=.*[a-zA-Z])`: 确保字符串中包含至少一个字母 `(?=.*\d)`: 确保字符串中包含至少一个数字 `(?=.*[!@#\$%\^\&*\)\(+=._-])`: 确保字符串中包含至少一个特殊字符 `.{8,}`: 确保字符串的最小长度为8个字符。 */
-
- 特殊字符
-
.(点号):匹配任意单个字符(除了换行符)注意:点号的行为必须引起特别的关注,需要小心对待。
案例1:点号匹配任意字符。
const regex = /.b./; // 匹配任意单个字符,后跟"b",再跟任意单个字符。 regex.test("abc") // true,匹配"abc" regex.test("1b2") // true,匹配"1b2" regex.test("a_b") // true,匹配"a_b" regex.test("a\nb") // false,点号不匹配换行符案例2:多行文本中的点号行为
const multilineText = "first line\nsecond line"; // 正常的点号,不匹配换行符 const regex = /first.*second/ regex.test(multilineText) // false,点号不匹配换行符 // 使用's'标志,点号可以匹配换行符 const regexDotAll = /first.*second/s; regexDotAll.test(multilineText) // true,成功匹配 /* - 使用`s`(dotall模式)标志后,点号就能匹配换行符,从而使整个表达式匹配成功 - `.*`表示匹配任意字符,而在启用了 s 模式后,换行符也包含在其中 */案例3:匹配带有点号的字符
const regex = /a\.b/ // 严格匹配字母"a"后跟一个点号(字面意思) regex.test("a.b") // true regex.test("axb") // false regex.test("a2b") // false案例4:限制匹配的字符集
const regex = /a.c/; const regex2 = /a.c/s; regex.test("a2c") // true,匹配"a2c" regex.test("abc") // true,匹配"abc" regex.test("a\nc") // false,点号不匹配换行符 regex2.test("a2c") // true,匹配"a2c" regex2.test("abc") // true,匹配"abc" regex2.test("a\nc") // true,dotall模式下,点号匹配换行符 /* - `a`开头 - `[^b]`,表示除了 b 的任意字符 - 然后是任意一个字符(点号) - 最后是 c */案例5:非贪婪匹配中的点号
const regex = /<.*?>/; const text = "<div>Some content</div>" text.match(regex) // 输出:<div> // `.*?` 是非贪婪匹配模式,表示尽可能少地匹配字符。这里点号仍然表示任意字符,但通过`?`使其停止在第一个`>`处match 方法请移步下文
正则表达式在不同语言中的应用
-
- 原字符
^:匹配字符串的开始$: 匹配字符串的结束
字符集与预定义字符类
-
字符集
[](字符集):匹配方括号内的任意一个字符(例如[abc]匹配"a", "b" 或 "c")- 字符集的取反
[^]:匹配除了方括号内字符之外的任意字符 (如[^0-9]匹配非数字)
// 案例一:匹配单个字母 [abc] const regexOne = /[abc]/g; const text = "apple ball cat dog"; const matches = text.match(regex); console.log(matches) // ['a', 'b', 'a', 'c', 'a'] const test = regexOne.test(text) console.log(test) // true // 案例二:匹配字母范围[a-z] const regexTwo = /[a-z]/g; const text = "Dog 123!"; const matches = text.match(regexTwo); console.log(matches); // ['o', 'g'] const test = regexTwo.test(text); console.log(test); // true // 案例三:匹配数字[0-9] const regexThr = /[0-9]/g; const text = "Room 101 is ready"; const matches = text.match(regexThr); console.log(matches); // ['1', '0', '1'] const test = regexThr.test(text); console.log(test); // true // 案例四:匹配大小写字母 const regexFour = /[A-Za-z]/g; const text = "Welcome 2024!"; const matches = text.match(regexFour); console.log(matches); // ['W', 'e', 'l', 'c', 'o', 'm', 'e'] const test = regexFour.test(text) console.log(test); // true // 案例五:匹配特殊字符[!@#] const regexFive = /[!@#]/g; const text = "@method is post in $Api"; const matches = text.match(regexFive); console.log(matches); // ['@'] const test = regexFive.test(text); console.log(test) // true // 案例六:匹配非数字[^0-9] const regexSix = /[^0-9]/g; const text = "abc123"; const matches = text.match(regexSix); console.log(matches); // ['a', 'b', 'c'] const test = regexSix.test(text); console.log(true) // true -
预定义字符类:
\d:匹配数字字符(相当于[0-9])\D:匹配非数字字符\w: 匹配字母、数字或下划线(相当于[a-zA-Z0-9_])\W: 匹配非字母、数字或下划线的字符\s: 匹配空白字符(空格、制表符等)\S: 匹配非空白字符
## 上案例。匹配复杂文本中的不同字符类型 const text = "User_123 login in 10:45 AM, IP: 198.162.1.1"; // 1、匹配所有数字字符 const digits = text.match(/\d/g); console.log("匹配到的所有数字字符:", digits) // 匹配到的所有数字字符:['1', '2', '3', '1', '0', '4', '5', '1', '9', '8', '1', '6', '2', '1', '1'] // 2、匹配所有非数字字符 const notDigits = text.match(/\D/g); console.log("匹配到的所有非数字字符:", notDigits) // 匹配到的所有非数字字符:['U', 's', 'e', 'r', '_',' ', 'l', 'o', 'g', 'i', 'n', ' ', 'i', 'n', ' ', ':', ' ', 'A', 'M', ',', ' ', 'I', 'P', ':', ' ', '.', '.', '.'] // 3、匹配所有数字、字母或下划线 const wordChars = text.match(/\w/g); console.log("匹配到的所有数字、字母或下划线字符:", wordChars) // 匹配到的所有数字、字母或下划线字符:['U', 's', 'e', 'r', '_', '1', '2', '3', 'l', 'o', 'g', 'i', 'n', 'i', 'n', '1', '0', '4', '5', 'A', 'M', 'I', 'P', '1', '9', '8', '1', '6', '2', '1', '1'] // 4、匹配所有非数字、字母或下划线 const notWordChars = text.match(/\W/g); console.log("匹配到的所有非数字、字母或下划线字符:", notWordChars) // 匹配到的所有非数字、字母或下划线字符:[' ', ' ', ' ', ':', ' ', ',', ' ', ':', ' ', '.', '.', '.'] // 5、匹配所有空白字符 const spaces = text.match(/\s/g); console.log("匹配到的所有空白字符:", spaces) // 匹配到的所有空白字符:[' ', ' ', ' ', ' ', ' ', ' '] // 6、匹配所有非空白字符 const notSpaces = text.match(/\S/g); console.log("匹配到的所有非空白字符:", notSpaces) // 匹配到的所有非空白字符:['U', 's', 'e', 'r', '_', '1', '2', '3', 'l', 'o', 'g', 'i', 'n', 'i', 'n', '1', '0', ':', '4', '5', 'A', 'M', ',', 'I', 'P', ':', '1', '9', '8', '.', '1', '6', '2', '.', '1', '.', '1']
重复与量词
-
基本量词
*: 匹配0次或多次(例如a*匹配"a","aa"或无"a")+: 匹配1次或多次(例如a+匹配"a", "aa")?: 匹配0次或一次(例如a?匹配"a"或无"a")
-
限定重复次数的量词
{n}: 匹配恰好n次(如a{3}匹配"aaa"){n,}: 匹配至少n次(如a{2,}匹配"aa", "aaa"){n,m}: 匹配至少n次,至多m次(如a{2,5}匹配2至5个"a")
-
懒惰量词
*?、+?、??:使量词尽可能少匹配(例如a+?匹配最少数量的"a")
-
代码示例
// 基本量词 let text1 = 'aaa'; console.log(text1.metch(/a*/g)); // 匹配0次或多次"a",返回['aaa', ''] console.log(text1.match(/a+/g)); // 匹配1次或多次"a",返回['aaa'] console.log(text1.match(/a?/g)); // 匹配0次或1次"a",返回['a', 'a', 'a', ''] // 限定重复次数的量词 let text2 = "aaaaa"; console.log(text2.match(/a{3}/g)); // 匹配恰好3次"a",返回['aaa'] console.log(text2.match(/a{2,}/g)); // 匹配至少2次"a",返回['aaaaa'] console.log(text2.match(/a{2,4}/g)); // 匹配至少2次,至多4次"a",返回['aaaa'] // 懒惰量词 let text3 = "aaaab"; console.log(text3.match(/a*?b/g)); // ['aaaab'] console.log(text3.match(/a+?b/g)); // ['aaaab'] console.log(text3.match(/a??b/g)); // ['ab']
重点注意:一定以控制台为准!
对于懒惰量词来说,复杂场景的AI正则和控制台的输出完全不一样,因为对于懒惰量词来说,直面意义上是尽可能少地匹配,但是控制台的输出与直面意思确实有所不同,这里小编调研了两天,没有找到比较合适的解答,所以暂时也就先这样了。
分组与捕获
-
括号
()(捕获组):将部分正则表达式分组并捕获(如(abc)匹配"abc")\1(反向引用):引用前面捕获的组(如(a)\1匹配重复字符,如"aa")
-
非捕获组
(?:):定义非捕获组(如(?:abc)匹配"abc",但不捕获)
-
代码示例
// () 匹配组 let text = "Hello, my name is John."; let regex = /(name is) (\w+)/; let match = text.match(regex); console.log(match[0]) // 'name is John' console.log(match[1]) // 'name is' console.log(match[2]) // 'John' // \1 反向引用 let text = "He said said."; let regex = "/(\b\w+\b)\1/"; // \b 匹配单词边界 let match = text.match(regex); console.log(match[0]); // "said said" console.log(match[1]); // "said" // (?:) 非捕获组 let text = "abc123 xyz456"; let regex = /(?:abc|xyz)(\d+)/; let match = text.match(regex); console.log(match[0]); // "abc123" console.log(match[1]); // "123"再引入一点, 有
g和 没有g的差别是蛮大的- 没有
g时:正则表达式会在字符串中查找第一个匹配,并返回一个数组,包含匹配的整个字符串以及捕获组内容。 - 有
g时:正则表达式会在整个字符串中查找所有匹配项,但只返回每个完整匹配的字符串,而不包含匹配组。 - 如果要获取捕获组,就需要使用
matchAll
let text = "abc123 xyz456"; let regex1 = /(?:abc|xyz)(\d+)/; // 不带 g 标志 let regex2 = /(?:abc|xyz)(\d+)/g; // 带 g 标志 let match1 = text.match(regex1); let match2 = text.match(regex2); console.log(match1); // ["abc123", "123"] console.log(match2); // ["abc123", "xyz456"] let groups = [...text.matchAll(regex2)]; console.log(groups) /* [ [ "abc123", "123" ], [ "xyz456", "456" ] ] */ - 没有
位置匹配(锚点)
-
开始与结束
^:匹配字符串的开始(如^abc匹配以"abc"开头的字符串)$:匹配字符串的结束(如abc$匹配以"abc"结尾的字符串)
-
单词边界
\b:匹配单词边界(如\bword\b匹配独立的单词 "word")\B:匹配非单词边界
逻辑或选择
- 管道符
|:表示"或"操作(如a|b匹配"a"或"b")
零宽断言(Lookahead 和 Lookbehind)
- 正向零宽断言(Lookahead)
-
(?=...):检查某个模式是否出现在当前位置的后面,但不会将此模式包含在匹配结果中。(如\d(?=px)匹配后面跟着"px"的数字) -
代码示例
let text = "100px 200px 300em"; let regex = /\d+(?=px)/g; // 匹配后面跟着一个"px"的数字 let match = text.match(regex); // ["100", "200"]
-
- 负向零宽断言(Negative Lookahead)
-
(?!...):检查当前位置后面不存在某个模式的情况。(如\d(?!px)匹配后面不跟"px"的数字) -
代码案例
let text = "100px 200em 300px"; let regex = /\d+(?!px)/g; // 匹配后面不跟"px"的数字 let match = text.match(regex); // ["10", "200", "30"]
-
- 正向零宽后发断言(Lookbehind)
-
(?<=...):检查当前位置前面是否存在某个模式,但不会将此模式包含在匹配结果中。(如(?<=\$)\d+匹配前面没有"$"的数字) -
代码示例
let text = "$100 a200 ¥300"; let regex = /(?<=\$)\d+/g; // 匹配前面有"$"的数字 let match = text.match(regex); // ["100"]
-
- 负向零宽后发断言(Negative Lookbehind)
-
(?<!):检查当前位置前面不存在某个模式的情况。(如(?<!\$)\d+匹配前面没有"$"的数字) -
代码示例
let text = "$100 200 ¥300"; let regex = /(?<!\$)\d+/g; let match = text.match(regex); // ["00", "200", "300"]
-
贪婪与惰性匹配
-
贪婪模式:默认情况下,正则表达式是贪婪的
- 贪婪模式会尽可能地匹配满足条件的字符,直到无法继续为止。这是正则表达式的默认行为。
-
惰性模式:通过在量词后面添加
?,使正则变为惰性匹配- 惰性模式会尽可能少地匹配字符。可以确保匹配的字符串是符合条件的最短子串。
-
代码示例
- 示例1:匹配 HTML 标签内容
// ## 贪婪模式 // 贪婪匹配会匹配 <p> 和 </p> 标签之间的所有内容,包含第二个 <p> 标签及其内容。 const text = '<p>This is a paragraph.</p><p>This is another paragraph.</p>'; const regex = /<p>.*<\/p>/g; const match = text.match(regex); console.log(match) // ['<p>This is a paragraph.</p><p>This is another paragraph.</p>'] // ## 惰性匹配 // 惰性匹配会分别匹配每队 <p> 和 </p> 标签之间的内容。 const regex2 = /<p>.*?<\/p>/g; const match2 = text.match(regex2); console.log(match2) // [ '<p>This is a paragraph.</p>', '<p>This is another paragraph.</p>' ]- 示例2:提取网址中的协议
// ## 贪婪模式 // 贪婪模式会捕获所有符合条件的字符,导致输出不完整的URL const text = "http://example.com and https://example.com"; const regex = /http.*:\/\//g; const match = text.match(regex); console.log(match) // ['http://example.com and https://'] // ## 惰性匹配 // 惰性匹配会分别匹配协议部分 const regex2 = /http.*?:\/\//g; const match2 = text.match(regex2); console.log(match2) // ['http://', 'https://']- 示例3:提取括号中的内容
// ## 贪婪模式 // 贪婪模式会捕获从第一个 ( 到最后一个 ) 之间的所有内容 const text = "here is (some text) and (another one)."; const regex = /\(.*\)/g; const match = text.match(regex); console.log(match) // ['(some text) and (another one)'] // ## 惰性匹配 // 惰性匹配会逐一匹配每对括号内的内容 const regex2 = /\(.*?\)/g; const match2 = text.match(regex2); console.log(match2) // ['(some text)', '(another one)'] -
注意事项
- **贪婪与惰性匹配的选择:**在匹配精确内容时,优先考虑使用惰性匹配,尤其在HTML或多段内容的处理上,避免贪婪匹配带来的过多捕获。
- 性能考量:贪婪匹配由于一次性匹配所有内容,通常效率更高;惰性匹配需要逐步确认符合条件的最短字符串,可能在大文本中影响性能。
高级技巧与优化
-
回溯问题
- JavaScript的正则引擎也会经历回溯,特别实在处理复杂的正则表达式时,避免性能瓶颈。
- 处理回溯问题的技巧:
- 使用非贪婪量词:在JavaScript中,可以通过在量词后面添加
?来指定非贪婪匹配。.*?会尽量少地匹配字符。 - 明确的边界:使用
^和$来限制匹配的开始和结束位置,减少不必要的匹配尝试。 - **简化子模式:**避免使用可选的模式和重复的模式。例如,将
(a|b|c|d)替换为([a-d])。 - 减少交替选择:避免在正则表达式中使用过多的交替选择
|。
- 使用非贪婪量词:在JavaScript中,可以通过在量词后面添加
-
正则表达式的效率优化
- 优化 JavaScript 中的正则表达式可以通过简化模式和调整方式来实现。
- 效率优化技巧
- 使用原子组: 使用原子组
(?:)不创建捕获组,可以提高效率,减少回溯。 - 避免重复计算:使用命名组
(?<name>)来提高可读性,避免在复杂模式中出现重复。 - **限制字符集:**使用具体字符集(如
[0-9])而非通配符(如.),可以提高匹配速度。 - **使用固定长度模式:**尽量使用固定长度的模式,避免可变长度的匹配,以提高性能。
- 使用原子组: 使用原子组
正则表达式在不同语言中的应用
-
JavaScrip
- 使用
RegExp对象和方法(如.test()、.match()、.matchAll())
1、test()
方法的书面名称是正则表达式匹配测试方法,它是JavaScript中用于检测字符串是否与正则表达式匹配的一个方法。结果返回一个布尔值(true、false),表示是否匹配成功。
注意:简单快速的匹配测试,它不返回具体内容匹配,只是用于测试是否匹配。
2、match()
match()是JavaScript字符串对象的一个方法,用来根据提供的正则表达式在字符串中搜索匹配,并返回匹配到的结果。基本用法
str.match(regex);str是要进行搜索的字符串。regex是要使用的正则表达式。
返回值
- 如果正则表达式带有全局标志
g:- 返回一个包含所有匹配结果的数组,如果没有匹配到任何内容,则返回
null
- 返回一个包含所有匹配结果的数组,如果没有匹配到任何内容,则返回
- 如果正则表达式没有全局标志
g:- 返回一个包含第一个匹配项和捕获组的数据,或者没有匹配到任何内容,则返回
null
- 返回一个包含第一个匹配项和捕获组的数据,或者没有匹配到任何内容,则返回
示例用法
- 单次匹配(无全局标志
g)
const text = "hello world!"; const result = text.match(/o/); // 输出: ['o', index:4, input: 'hello world!', groups: undefined] /* 返回的数组包含匹配到的`o`及其位置信息 索引0:匹配到的字符"o" `index:4`: 表示匹配的位置是索引4 input: 表示原始字符串"hello world!" 如果有命名捕获组,则 groups 会包含对应的值,这里是 undefined */- 全局匹配(带
g标识)
const text = "hello world!"; const result = text.match(/o/g) // ['o', 'o'] /* 正则表达式 `/o/g` ,会匹配所有出现的"o" 返回一个数组,包含所有匹配到的字符 */- 带有捕获组的匹配
const text = "hello 12345!"; const result = text.match(/(\d+)/); // ['12345', '12345', index: 6, input: 'hello 12345!', groups: undefined] /* 正则表达式 `(\d+)` 中使用了捕获组来匹配数字。 返回的数组中,除了匹配的结果 12345 外,第二个 12345 是捕获组的内容 */- 全局匹配所有的数字
const text = "phone: 12345, ZIP: 67890"; const result = text.match(/\d+/g); // ['12345', '67890'] /* 使用 `/\d+/g` 正则表达式全局匹配所有数字序列。 返回的数组包含所有匹配到的数字序列。 */使用
match()与matchAll()match()只能在全局模式下返回所有匹配项的数组,而matchAll()可以返回所有匹配项的迭代器,其中包括每个匹配项的详细信息(例如捕获组)。如果需要捕获组合全局匹配,可以使用
matchAll()const text = "phone: 12345, ZIP: 67890"; const matches = text.matchAll(/\d+/g); for(const matcj of matches)( console.log(match) ) /* // [ '12345', index: 7, input: 'Phone: 12345, ZIP: 67890', groups: undefined ] // [ '67890', index: 21, input: 'Phone: 12345, ZIP: 67890', groups: undefined ] */总结:
text.match()是用来在字符串中匹配符合正则表达式的内容- 它可以返回单个匹配结果(无
g)或所有匹配结果(带g) - 通过结合捕获组,
match()可以提取特定部分的信息。如果需要处理更复杂的全局匹配,可以使用matchAll()。
- 使用