前端很多场景其实都会涉及到正则表达式的使用,奈何之前个人正则的知识都是零散的,读完正则表达式迷你书之后受益良多,一来总结一下知识点,二来回顾的时候可以更方便。

根据目录精简整理

# 匹配攻略

# 字符组

匹配只是其中的一个字符,范围表示利用 - 省略和简写。

# 排除字符组

利用 ^,字符组第一个放置 ^ 表示求反

# 常见的简写

字符组 含义
\d 数字 [0-9],表示一位数字。
\D 表示 [^0-9],表示除了数字之外的任意字符。
\w 表示 [0-9a-zA-Z]
\W [^0-9a-zA-Z],非单词字符。
\s [\t\v\n\r\f],表示空白字符,包括空格、水平、垂直制表符、换行、回车、换页符。
\S [^\t\v\n\r\f],非空白符。
. [^\n\r\u2028\u2029],通配符。表示几乎任意字符。

匹配任意字符[\d\D]、[\w\W]、[\s\S]、[^]

# 量词

# 简写

量词 含义
{m,} 至少出现 m 次。
{m} 出现 m 次。
? 等价于 {0,1},出现或者不出现。
+ 等价 {1,},出现至少一次
* 等价 {0,},出现任意次,有可能不出现。

# 贪婪与惰性

  • 贪婪会尽可能多的匹配

  • 惰性会尽可能少的匹配。 具体惰性实现:量词后边加问号就能实现惰性匹配。

    惰性量词 贪婪量词
    {m,n}? {m,n}
    {m,}? {m,}
    ?? ?
    +? +
    *? *

# 多选分支

管道符号代表分割 |,默认惰性匹配,前边的匹配成功后面不会尝试匹配。例如匹配 goodnice,使用 /good|nice/g

const reg = /good|nice/g;
const string = "good idea, nice day!";
console.log(string.match(reg)); // ['good', 'nice']

# 匹配位置

  • ^,匹配开头,在多行匹配中匹配行开头。
  • $,匹配结尾,在多行匹配中匹配行结尾。
  • \b单词边界,具体是 \w\W 之间的位置,也包括 \w^ 之间的位置和 \w$ 之间的位置,看个例子:
"[JS] Lesson_01.mp4".replace(/\b/g, "#");
// '[#JS#] #Lesson_01#.#mp4#'
  • \B,这个比较好理解,就是 \b 的取反,知道了 \b ,所以
"[JS] Lesson_01.mp4".replace(/\B/g, "#");
// "#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4"
  • (?=p),其中 p 是一个子模式,即 p 前面的位置,是位置,也就是在 p 前面做手脚,不是前面的字符,其中 p 可以是想匹配的任意字符。
"hello".replace(/(?=l)/g, "#");
// "he#l#lo"
  • (?!p),是 (?=p) 的反模式。记一个特殊的,不是开头,/(?!^)/
"hello".replace(/(?!l)/g, "#");
// "#h#ell#o#"
  • 必须包含数字/(?=.*[0-9])/
  • 必须包含数字和小写字母/(?=.*[0-9])(?=.*[a-z])/,这样是不会挑位置的,数字和字母的顺序随意都可以匹配。
  • 必须包含小写字母和大写字母/(?=.*[a-z])(?=.*[A-Z])/,同理。
  • 必须包含数字、大、小写字母/(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])/,同理。

# 括号的作用

括号的主要作用就是提供了分组,便于引用。引用分组会有在 js 里引用,在正则表达式里引用。括号里的正则强调其是一个整体。

# 分组

  • 分组:如果要匹配连续出现的单个字符用 /x+/ 即可,但是如果需要匹配连续出现的 'abc',则需要 /(abc)+/,将量词 + 作用于括号这个整体的表达式上。

  • 分组引用:有了括号,可以对数据进行提取以及更强大的替换操作,因为分组可以捕获数据,利用构造函数的全局属性 $1~$9 提取。

  • 如果想要忽略多个分组中的某一个,可以使用?:,假设 (\w+.(com|cn)),不需要第二个 com|cn 的分组的话,可以修改为(\w.(?:com|cn))

// 例如替换日期顺序
const regExp = /(\d{4})-(\d{2})-(\d{2})/;
var timeString = "2020-05-15";
timeString.replace(regExp, "$2/$3/$1");
// "05/15/2020"

// 替换h标题为p
const text = "<h1>hello~</h1>";
text.replace(/<(h[1-6])>([\s\S]+)<\/\1>/gi, "<p>$2</p>");
// 这里第一个分组是(h[1-6]),也就是说对称的匹配和用分组和\1来搞定,对于闭合html标签来说的话。

# 反向引用

假如要求匹配的前后一致,引用之前的第 n 个分组,看个例子

// 匹配日期,只是匹配三种模式,并且年份和月份之间的连接符必须一致
const regExp = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
regExp.test("2020-05-15"); // true
regExp.test("2020/05-15"); // false

其中 \1 代表的就是引用前边的那个分组 (-|\/|\.)\2 \3 也就是分别指代第二和第三个分组。反向引用就是引用前边的分组,当引用到了不存在的分组时,只是匹配反向引用的字符本身。

# 非捕获括号

如果不使用引用,则可以利用非捕获括号 (?:p)(?:p1|p2|p3),如果不用反向引用,对于匹配 'abc' 的连字符,可以修改为 /(?:abc)+/g

一些例子

  • 将每个单词首字母转为大写
const titleize = (str) =>
  str.toLowerCase().replace(/(?:^|\s)\w/g, (c) => c.toUpperCase());
titleize("ni hao a ");
// "Ni Hao A "
  • _-空格 转驼峰
const camelize = (str) =>
  str.replace(/[-_\s]+(.)?/g, (match, c) => (c ? c.toUpperCase() : ""));
camelize("_get_name");
// "GetName"
  • 驼峰转中划线
// 先将大写字母匹配到的字符前边加上-,利用了分组的特性。再将任意多个 _-空格 转为-
const dasherize = (str) =>
  str
    .replace(/([A-Z])/g, "-$1")
    .replace(/[_-\s]+/g, "-")
    .toLowerCase();
dasherize(GetName);
// "-get-name"
  • 匹配成对标签,/<([^>]+)>[\d\D]*<\/\1>/
<title>Reg Exp</title>
<p>bye bye</p>

这里匹配一个开标签,使用 <[^>]+> ,也就是 <> 中非 > 的任意字符。 匹配闭标签,<\/[^>]+>,闭标签中只比开标签多了一个 / 所以需要利用 \ 转义,这其中又因为需要匹配成对的标签,所以可以用前边的知识,反向引用,需要反向引用所以肯定需要分组了,最后我们能够写出来,/<([^>]+)>[\d\D]*<\/\1>/[\d\D] 匹配任意字符。

# 优先级

操作符描述 操作符 优先级
转义符 \ 1
括号、方括号 (...)、(?:...)、(?=...)、(?!...)、[...] 2
量词 {m}、{m, n}、{m, }、?、+、* 3
位置、序号 ^、$、\元字符、一般字符 4
管道符 \| 5

# 需要转义的字符

需要转义的字符是正则中有特殊含义的字符,^、$、.、*、+、?、!、|、\、/、(、)、[、]、{、}、=、!、:、-

# 优化效率

  • 使用具体字符组来替代通配符消除回溯。
  • 不需要分组和反向引用时,使用非捕获分组。
  • 独立出确定字符,/a+/ => /aa*/
  • 提取分支的公共部分,/this|that/ => /th(?:is|at)/
  • 减少分支数量,缩小范围,/red|read/ => /rea?d/

# 参考

JavaScript 正则表达式迷你书(老姚)

最后更新: 2/12/2023, 7:42:22 AM