前端很多场景其实都会涉及到正则表达式的使用,奈何之前个人正则的知识都是零散的,读完正则表达式迷你书之后受益良多,一来总结一下知识点,二来回顾的时候可以更方便。
根据目录精简整理
# 匹配攻略
# 字符组
匹配只是其中的一个字符,范围表示利用 -
省略和简写。
# 排除字符组
利用 ^
,字符组第一个放置 ^ 表示求反
。
# 常见的简写
字符组 | 含义 |
---|---|
\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,}
??
?
+?
+
*?
*
# 多选分支
管道符号代表分割 |
,默认惰性匹配,前边的匹配成功后面不会尝试匹配。例如匹配 good
和 nice
,使用 /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 正则表达式迷你书(老姚)