前言
正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象。这些模式被用于 RegExp 的 exec 和 test 方法, 以及 String 的 match、matchAll、replace、search 和 split 方法。
本章介绍 JavaScript 正则表达式。
通过使用正则表达式,可以:
- 
测试字符串内的模式,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。 
- 
替换文本, 可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。 
- 
基于模式匹配从字符串中提取子字符串, 可以查找文档内或输入域内特定的文本。 
基础用法
创建一个正则表达式
使用一个正则表达式字面量,其由包含在斜杠之间的模式组成,如下所示:
var re = /ab+c/;
复制代码脚本加载后,正则表达式字面量就会被编译。当正则表达式保持不变时,使用此方法可获得更好的性能。
或者调用 RegExp 对象的构造函数,如下所示:
var re = new RegExp("ab+c");
复制代码在脚本运行过程中,用构造函数创建的正则表达式会被编译。如果正则表达式将会改变,或者它将会从用户输入等来源中动态地产生,就需要使用构造函数来创建正则表达式。
js里使用正则
| 方法 | 描述 | 
|---|---|
| exec | 一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回 null)。 | 
| test | 一个在字符串中测试是否匹配的RegExp方法,它返回 true 或 false。 | 
| match | 一个在字符串中执行查找匹配的String方法,它返回一个数组,在未匹配到时会返回 null。 | 
| matchAll | 一个在字符串中执行查找所有匹配的String方法,它返回一个迭代器(iterator)。 | 
| search | 一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。 | 
| replace | 一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。 | 
| split | 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的 String方法。 | 
- 
Exec 和 match的区别 - 
分别是RegExp类和String类的方法 
- 
exec 只会匹配第一个符合的字符串(意味着g对其不起作用), match 是否返回所有匹配的数组跟正则表达式里是否带着g有关系 
 
- 
const str = 'd3aish hello world d5aisy';
const reg = /\dai/g;
//  先看没有g的情况
console.log(str.match(reg));  // ['3ai', '5ai']
console.log(reg.exec(str)); // ['3ai']
// 不带g
const str = 'd3aish hello world d5aisy';
const reg = /\dai/;
//  先看没有g的情况
console.log(str.match(reg));  // ['3ai']
console.log(reg.exec(str)); // ['3ai']
复制代码量词
| Characters | Meaning | 
|---|---|
| x* | 将前面的项“x”匹配0次或更多次。 例如,/bo*/匹配“A ghost booooed”中的“boooo”和“A bird warbled”中的“b”,但在“A goat grunt”中没有匹配。 | 
| x+ | 将前一项“x”匹配1次或更多次。等价于{1,}。 例如,/a+/匹配“candy”中的“a”和“caaaaaaandy”中的“a”。 | 
| x? | 将前面的项“x”匹配0或1次。 例如,/e?le?/匹配angel中的el和angle中的le。 如果立即在任何量词*、+、?或{}之后使用,则使量词是非贪婪的(匹配最小次数),而不是默认的贪婪的(匹配最大次数)。 | 
| x{n} | 其中“n”是一个正整数,与前一项“x”的n次匹配。 例如,/a{2}/ 不匹配“candy”中的“a”,但它匹配“caandy”中的所有“a”,以及“caaandy”中的前两个“a”。 | 
| x{n,} | 其中,“n”是一个正整数,与前一项“x”至少匹配“n”次。 例如,/a{2,}/不匹配“candy”中的“a”,但匹配“caandy”和“caaaaaaandy”中的所有a。 | 
| x{n,m} | 其中,“n”是0或一个正整数,“m”是一个正整数,而m > n至少与前一项“x”匹配,最多与“m”匹配。例如,/a{1,3}/不匹配“cndy”中的“a”,“candy”中的“a”,“caandy”中的两个“a”,以及“caaaaaaandy”中的前三个“a”。注意,当匹配“caaaaaaandy”时,匹配的是“aaa”,即使原始字符串中有更多的“a”。 | 
| x*?x+?x??x{n}?x{n,}?x{n,m}? | 默认情况下,像 *和+这样的量词是“贪婪的”,这意味着它们试图匹配尽可能多的字符串。?量词后面的字符使量词“非贪婪”:意思是它一旦找到匹配就会停止。例如,给定一个字符串“some   new   thing”:/<.*>/will match ”  new  “/<.*?>/will match “” | 
标志符
正则表达式有六个可选参数 ( flags ) 允许全局和不分大小写搜索等。这些参数既可以单独使用也能以任意顺序一起使用, 并且被包含在正则表达式实例中。
| 标志 | 描述 | 
|---|---|
| g | 全局搜索。 | 
| i | 不区分大小写搜索。 | 
| m | 多行搜索。 | 
| s | 允许 .匹配换行符。 | 
| u | 使用unicode码的模式进行匹配。 | 
| y | 执行“粘性( sticky)”搜索,匹配从目标字符串的当前位置开始。 | 
为了在正则表达式中包含标志,请使用以下语法:
var re = /pattern/flags;
复制代码或者
var re = new RegExp("pattern", "flags");
复制代码值得注意的是,标志是一个正则表达式的一部分,它们在接下来的时间将不能添加或删除。
标志符g
const reg = /abc/gi;
const str = 'helloabc';
reg.test(str) // true
reg.test(str) // false
reg.test(str) // true
reg.test(str) // false
const reg = /abc/i;
const str = 'helloabc';
reg.test(str) // true
reg.test(str) // true
reg.test(str) // true
reg.test(str) // true
复制代码全局正则表达式的另一个属性 lastIndex 用于存放上一次匹配文本之后的第一个字符的位置。
RegExp.prototype.exec() 和 RegExp.prototype.test() 方法都以 lastIndex 属性中所存储的位置作为下次正则匹配检索的起点。连续调用这两个方法就可以遍历字符串中的所有匹配文本。
lastIndex 属性可读写,当 RegExp.prototype.exec() 或 RegExp.prototype.test() 再也找不到可以匹配的文本时,会自动把 lastIndex 属性重置为 0。
因此使用这两个方法来检索文本,是可以无限执行下去的。
标志符y
执行“粘性( sticky )”搜索,匹配从目标字符串的当前位置开始
var searchStrings, stickyRegexp;
stickyRegexp = /foo/y;
searchStrings = [
    "foo",
    " foo",
    "  foo",
];
searchStrings.forEach(function(text, index) {
    stickyRegexp.lastIndex = 1;
    console.log("found a match at", index, ":", stickyRegexp.test(text));
});
// found a match at 0 : false
// found a match at 1 : true
// found a match at 2 : false
// 如果把y改成g
// found a match at 0 : false
// found a match at 1 : true
// found a match at 2 : true
复制代码可以理解为必须为在lastIndex开头去匹配,即index为1时开始匹配 /^abc/ ,实现更精准的位置控制。
高级用法
贪婪模式和非贪婪模式
var str='aacbacbc';
var reg=/a.*b/;
var res=str.match(reg);
// aacbacb index为0
console.log(res);
复制代码上例中,匹配到第一个a后,开始匹配.*,由于是贪婪模式,它会一直往后匹配,直到最后一个满足条件的b为止,因此匹配结果是aacbacb
var str='aacbacbc';
var reg=/a.*?b/;
var res=str.match(reg);
// acbacb index为1
console.log(res);
复制代码第一个匹配的是a,然后再匹配下一个字符a时,和正则不匹配,因此匹配失败,index挪到1,接下来匹配成功了ac,继续往下匹配,由于是贪婪模式,尽可能多的去匹配结果,直到最后一个符合要求的b为止,因此匹配结果是acbacb
捕获组
对于要重复单个字符,非常简单,直接在字符后加上限定符即可,例如 a+ 表示匹配1个或一个以上的a,a?表示匹配0个或1个a。
但是我们如果要对多个字符进行重复怎么办呢?此时我们就要用到分组,我们可以使用小括号”()”来指定要重复的子表达式,然后对这个子表达式进行重复,例如:(abc)? 表示0个或1个abc 这里一 个括号的表达式就表示一个分组 。
非捕获组有很多种形式,其中包括:零宽度断言和模式修正符
反向引用
引用的是前面捕获组中的文本而不是正则,也就是说反向引用处匹配的文本应和前面捕获组中的文本相同。如
/(["'])(abc).*\1/
其中使用了分组,\1就是对引号这个分组的引用,它匹配包含在两个引号或者两个单引号中的所有字符串,如,”abc” 或 ” ‘ ” 或 ‘ ” ‘ ,但是请注意,它并不会对” a’或者 ‘a”匹配。平时开发的时候也常用于html标签的匹配
命名捕获组
捕获组其实是分为编号捕获组 Numbered Capturing Groups 和命名捕获组 Named Capturing Groups 的,我们上面说的捕获组,默认指的是编号捕获组。命名捕获组,也是捕获组,只是语法不一样。命名捕获组的语法如下: (?<name>group) 或 (?'name'group) ,其中 name 表示捕获组的名称, group 表示捕获组里面的正则。
非捕获组
语法:(?:Pattern)
如:匹配indestry或者indestries
我们可以使用indestr(y|ies)或者indestr(?:y|ies)
以 (?) 开头的组是纯的 非捕获 组,它不捕获文本 ,也不针对组合计进行计数。就是说, 如果小括号中以?号开头,那么这个分组就不会捕获文本,当然也不会有组的编号 ,因此也不存在反向引用。
我们通过捕获组就能够得到我们想要匹配的内容了,那为什么还要有非捕获组呢?
原因是捕获组捕获的内容是被存储在内存中,可供以后使用,比如反向引用就是引用的内存中存储的捕获组中捕获的内容。而非捕获组则不会捕获文本,也不会将它匹配到的内容单独分组来放到内存中。所以,使用非捕获组较使用捕获组更节省内存。
- 实际应用场景,可以快速提取想要的信息
'https://www.toutiao.com'.match(/(?:https?:\/\/)(.*)/)
// ["https://www.toutiao.com", "www.toutiao.com"]
复制代码断言
零宽度断言
| (?=y) | 匹配’x’仅仅当’x’后面跟着’y’.这种叫做先行断言。 例如,/Jack(?=Sprat)/会匹配到’Jack’仅当它后面跟着’Sprat’。 /Jack(?=Sprat|Frost)/匹配‘Jack’仅当它后面跟着’Sprat’或者是‘Frost’。 但是‘Sprat’和‘Frost’都不是匹配结果的一部分。 | 
| (?<=y)x | 匹配’x’仅当’x’前面是’y’.这种叫做后行断言。 例如,/(?<=Jack)Sprat/会匹配到’ Sprat ‘仅仅当它前面是’ Jack ‘。 /(?<=Jack|Tom)Sprat/匹配‘ Sprat ’仅仅当它前面是’Jack’或者是‘Tom’。但是‘Jack’和‘Tom’都不是匹配结果的一部分。 | 
| x(?!y) | 仅仅当’x’后面不跟着’y’时匹配’x’,这被称为正向否定查找。 例如,仅仅当这个数字后面没有跟小数点的时候,/\d+(?!.)/ 匹配一个数字。 正则表达式/\d+(?!.)/.exec(“3.141”)匹配‘141’而不是‘3.141’ | 
| (?<!y)x | 仅仅当’x’前面不是’y’时匹配’x’,这被称为反向否定查找。 例如, 仅仅当这个数字前面没有负号的时候,/(?<!-)\d+/ 匹配一个数字。 /(?<!-)\d+/.exec(‘3’) 匹配到 “3”。 /(?<!-)\d+/.exec(‘-3’) 因为这个数字前有负号,所以没有匹配到。 | 
这四个非捕获组用于匹配表达式X,但是不包含表达式的文本。

例子
如何把一串整数转换成千位分隔形式,例如10000000000,转换成10,000,000,000。
除了常规的方法,可以使用正则解这个题
const str = "100000000000";
const reg= /(?=(\B\d{3})+$)/g;
console.log(str.replace(reg, ","));
复制代码回溯
原字符串
“Regex”
贪婪匹配过程分析
".*"
第一个 " 取得控制权,匹配正则中的 " ,匹配成功,控制权交给 .*
.取得控制权后,匹配接下来的字符。.代表匹配任何字符,代表可匹配可不匹配,这属于贪婪模式的标识符,会优先尝试匹配,于是接下来从1位置处的R开始匹配,依次成功匹配了R,e,g,e,x,接着继续匹配最后一个字符 " ,匹配成功,这时候已经匹配到了字符串的结尾,所以 .* 匹配结束,将控制符交给正则式中最后的 "
" 取得控制权后,由于已经是到了字符串的结尾,因此匹配失败,向前查找可供回溯的状态,控制权交给 .* ,.* 让出一个字符 " ,再把控制权交给",此时刚好匹配成功。
至此,整个正则表达式匹配完毕,匹配结果为”Regex”,匹配过程中回溯了1次
| “ | “ | 
|---|---|
| “.* | “Re | 
| “.* | “Reg | 
| “.* | “Rege | 
| “.* | “Rege | 
| “.* | “Regex | 
| “.* | “Regex” | 
| “.*” | “Regex” | 
| “.* | “Regex | 
| “.*” | “Regex” | 
回溯陷阱
下面的例子会让你的浏览器的cpu达到100%,就是回溯太多的导致的。
console.time('reg')
var reg =  /(a*)*b/ 
var str = 'a'.repeat(28); // aaaaaaaaaaaaa...
reg.exec(str)
console.timeEnd('reg')
复制代码先简单了解一下正则的实现引擎,主要分为DFA和NFA
DFA与NFA
原因分析
- a*由于贪婪模式可以直接匹配整个字符串, 但是由于b的存在,所以需要回溯,但是无论怎么回溯都不可能成功,但是NFA是机器,会一直不断的进行回溯,由于- (a*)*可以认为是两层的量词组合,所以复杂度会随着字符串的长度指数级的升高。
由于是有限状态机,所以并不会死循环,只是会占用大量的cpu,在一定时间之后会完成。
工具
在线网站:
付费软件:








![[转] IJCAI 2021 | 系统调研168篇文献,领域泛化首篇综述问世-一一网](https://www.proyy.com/skycj/data/images/2021-05-12/c6f53fba36394c20c513e5aea2d6a363.jpg)













![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
