发布时间:2025-12-09 16:25:12 浏览次数:4
首先给出正则最基础的知识点概述,好让同学们能回想起一些之前学过的知识点,能更好地进行阅读
这里分享一个在线练习正则的网站,同学们可以在这里进行练习 --> [ 在线练习 ]
笔记中例子中如特殊没有指出,则默认使用 /g 标志(修饰符)全局搜索作为示范
正则表达式是一组由字母和符号组成的特殊文本,它可以用来从文本中找出满足你想要的格式的句子。通俗的讲就是按照某种规则去匹配符合条件的字符串
一个正则表达式是一种从左到右匹配主体字符串的模式。 “Regular expression”这个词比较拗口,我们常使用缩写的术语“regex”或“regexp”。 正则表达式可以从一个基础字符串中根据一定的匹配模式替换文本中的字符串、验证表单、提取字符串等等
首先先给出最最基础部分的匹配规则,这个是肯定要会的
| \d 匹配数字 | * 0个或者更多 | ^一行的开头 |
| \w 匹配word(数字、字母) | + 1个或更多,至少1个 | $一行的结尾 |
| \W 匹配非word(数字、字母) | ? 0个或1个,一个Optional | \b 单词"结界"(word bounds) |
| \s 匹配white space(包括空格、tab等) | {min,max}出现次数在一个范围内 | |
| \S 匹配非white space(包括空格、tab等) | {n}匹配出现n次的 | |
| . 匹配任何,任何的字符 |
此处暂时看不懂没关系,后面会进行详细的语法介绍,此处只是让我们对正则表达式更有概念,感觉不好理解可以跳过,学完再回来看
此示例图解析部分主要摘录自 comer的60分钟正则从入门到深入,本人觉得其图画的挺好的,且确实刚开始可以稍微看下正则具体应用,方便后续理解,便摘录下来
这个正则比较复杂,画符念咒的地方太多了,一一分析:
好了这个正则解释完了,接下来用它做什么呢?
我们可以验证日期的合法性
结合URL分组替换所用到的分组特性,我们可以轻松写出日期格式化的方法
改造下这个正则
/^(\d{4})[/-](\d{1,2})[/-](\d{1,2})$/轻松的可以拿到 group#1 #2 #3 的内容,对应 $1 $2 $3
正则表达式其实就是在执行搜索时的格式,它由一些字母(也可以是汉字)和数字组合而成。
例如:一个正则表达式 学习的汪 H,它表示一个规则:由学开始,接着是习,…最后H。它是组个字符与输入的正则表达式作比较,同时大小写敏感
"学习的汪 H" =>努力学习的汪 hongjilin //无符合匹配字符串努力学习的汪 Hongjilin //其中的 [ 学习的汪 H ] 高亮结果示例: 这里分享一个在线练习正则的网站,同学们可以在这里进行练习 --> [ 在线练习 ]
正则表达式主要依赖于元字符。 元字符不代表他们本身的字面意思,他们都有特殊的含义。一些元字符写在方括号中的时候有一些特殊的意思
以下是一些元字符的列举:
| . | 句号匹配任意单个字符除了换行符。 |
| [ ] | 字符种类。匹配方括号内的任意字符。 |
| [^ ] | 否定的字符种类。匹配除了方括号里的任意字符 |
| * | 匹配>=0个重复的在*号之前的字符。 |
| + | 匹配>=1个重复的+号前的字符。 |
| ? | 标记?之前的字符为可选. |
| {n,m} | 匹配num个大括号之前的字符或字符集 (n <= num <= m). |
| (xyz) | 字符集,匹配与 xyz 完全相等的字符串. |
| | | 或运算符,匹配符号前或后的字符. |
| \ | 转义字符,用于匹配一些保留的字符 `[ ] ( ) { } . * + ? ^ $ \ |
| ^ | 从开始行开始匹配. |
| $ | 从末端开始匹配. |
.是元字符中最简单的例子。 .匹配任意单个字符,但不匹配换行符。 例如,表达式 [...o ] 匹配3个(几个点就几位)任意字符后面跟着是 [ o ] 的字符串。
'...o' => //此处给出三个点 就是前三位为任意努力学习的汪 hongjilin //其中的 [ 汪 ho ] 高亮努力学习的汪 Hongjilin //其中的 [ 汪 Ho ] 高亮字符集也叫做字符类。 方括号用来指定一个字符集。 在方括号中使用连字符来指定字符集的范围。 在方括号中的字符集不关心顺序。 例如,表达式 [ 学习的汪 [Hh] ] 匹配 [ 学习的汪 h ] 和 [ 学习的汪 H ] 。
"学习的汪 [Hh]" =>努力学习的汪 hongjilin //其中的 [ 学习的汪 h ] 高亮努力学习的汪 Hongjilin //其中的 [ 学习的汪 H ] 高亮前面我们说过点运算符,那同学们是否会有个疑惑, . 被用来匹配任意字符,那么作为字符串中的句号.,又该用什么匹配呢?
方括号的句号就表示句号。 表达式 lin[.] 匹配 lin.字符串
"lin[.]" =>努力学习的汪 hongjilins 努力学习的汪 Hongjilin.一般来说 ^ 表示一个字符串的开头,但它用在一个方括号的开头的时候,它表示这个字符集是否定的。 例如,表达式[^地]学习的[^帅] 匹配一个字符串为 [ 学习的 ]的, 同时前面一位字符串不能为地,后面一位字符串不能为帅
"[^地]学习的[^帅]" => 努力学习的汪 hongjilins //只有此处高亮努力学习的帅汪 Hongjilin. 帅气地学习的正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;另一个是行终止符(line terminator character)。
所谓行终止符,就是该字符表示一行的终结。以下四个字符属于“行终止符”。
上面代码中,因为.不匹配\n,所以正则表达式返回false。
但是,很多时候我们希望匹配的是任意单个字符,这时有一种变通的写法。
/foo[^]bar/.test('foo\nbar') // true当然,这种解决方案毕竟不太符合直觉, ES2018 引入s修饰符,使得.可以匹配任意单个字符。
/foo.bar/s.test('foo\nbar') // true后面跟着元字符 +,* or ? 的,用来指定匹配子模式的次数。 这些元字符在不同的情况下有着不同的意思。
*号匹配 在*之前的字符出现大于等于0次。 例如,表达式 a* 匹配0或更多个以a开头的字符。表达式[a-z]* 匹配一个行中所有以小写字母开头的字符串。
"[a-z]*" =>Hong ji lin VERY shuai //部分高亮HONGJILINHAOSHUAI //全部不亮hongjilinhaoshuai //全部高亮*号搭配 .号
*字符和.字符搭配可以匹配所有的字符.*。 *和表示匹配空格的符号\s连起来用,如表达式\s*学习\s*匹配0或更多个空格开头和0或更多个空格结尾的cat字符串。
"\s*学习\s*" => //0~无限次,所以只要有[ 学习 ]都会被匹配,同时会被匹配的还有其紧靠的无限次的空格努力 学习的汪 hongjilins //[ 学习 ]前一个空格,后面无空格努力 学习 的汪hongjilins //[ 学习 ]前后多个空格努力学习的帅汪 Hongjilin. //[ 学习 ] 前后无空格+号匹配+号之前的字符出现 >=1 次。 例如表达式学习.+汪 匹配以中文(也可以是字母)学习开头以 [汪] 结尾,中间跟着至少一个字符的字符串。
"学习.+汪" => 努力学习的汪 hongjilins 努力学习的帅汪 Hongjilin. 努力学习 66 汪 Hongjilin. 努力的学习汪 //此行无匹配结果在正则表达式中元字符 ? 标记在符号前面的字符为可选,即出现 0 或 1 次。 例如,表达式 学习的[帅]?汪 匹配字符串 学习的汪 和 学习的帅汪。
"学习的[帅]?汪" => 努力学习的汪 hongjilins努力学习的帅汪 Hongjilin. 努力的学习汪 //无匹配结果努力学习的帅气汪 Hongjilin. //无匹配结果在正则表达式中 {} 是一个量词,常用来限定一个或一组字符可以重复出现的次数。 例如, 表达式 [0-9]{2,3} 匹配最少 2 位最多 3 位 0~9 的数字。
"[0-9]{2,3}" => 努力学习的1汪1努力学习的233汪努力学习的4个4444汪努力学习的5个55555汪我们可以省略第二个参数。 例如,[0-9]{2,} 匹配至少两位 0~9 的数字。
"[0-9]{2,}" => 努力学习的1汪1努力学习的233汪努力学习的4个4444汪努力学习的5个55555汪如果逗号也省略掉则表示重复固定的次数。 例如,[0-9]{2} 匹配2位数字
"[0-9]{2}" => 努力学习的1汪1努力学习的233汪努力学习的4个4444汪努力学习的5个55555汪特征标群是一组写在 (...) 中的子模式。(...) 中包含的内容将会被看成一个整体,和数学中小括号( )的作用相同。例如, 表达式 (ab)* 匹配连续出现 0 或更多个 ab。如果没有使用 (...) ,那么表达式 ab* 将匹配连续出现 0 或更多个 b 。再比如之前说的 {} 是用来表示前面一个字符出现指定次数。但如果在 {} 前加上特征标群 (...) 则表示整个标群内的字符重复 N 次。
我们还可以在 () 中用或字符 | 表示或。例如,(学习|打工)的汪 匹配 学习的汪 或 打工的汪 .
"(学习|打工)的汪 (hong){2}" => 努力学习的汪 hongjilins努力学习打工的汪 hongjilins努力打工的汪 honghongjilins努力学习打工的汪 honghongjilins或运算符就表示或,用作判断条件。
举个栗子: (学习|打工)的(汪|打工人) 进行匹配
"(学习|打工)的(汪|打工人)" =>努力打工的汪 hongjilins努力学习的打工人 honghongjilins努力学习打工的打工人汪 hongjilins反斜线 \ 在表达式中用于转码紧跟其后的字符。用于指定 { } [ ] / \ + * . $ ^ | ? 这些特殊字符。如果想要匹配这些特殊字符则要在其前面加上反斜线 \。
例如 . 是用来匹配除换行符外的所有字符的。如果想要匹配句子中的 . 则要写成 \. 以下这个例子 \.?是选择性匹配.
"(学习|打工)的汪\.? hong\??" =>努力打工的汪. hong努力学习的汪? hong努力学习的汪 hong?在正则表达式中,想要匹配指定开头或结尾的字符串就要使用到锚点。^ 指定开头,$ 指定结尾。
通常也会搭配标志(修饰符)相关知识点使用
由于还未说到标志相关知识,此处例子仍使用 [ /g ]全局搜索,如果对此有疑惑的可以留着疑问看下方的 五、标志
^ 用来检查匹配的字符串是否在所匹配字符串的开头。
例如,在 abc 中使用表达式 ^a 会得到结果 a。但如果使用 ^b 将匹配不到任何结果。因为在字符串 abc 中并不是以 b 开头。
例如,^(学习|打工)的汪 进行匹配
"^(学习|打工)的汪" => //注意:下列字符串要分四次匹配,因为即使换行了,后三行字符串本质上都不在字符串开头//或者标志换成 /m 而不是 /g 因为此处还未说到标志,所以默认大家使用/g全局搜索学习的汪 hong打工的汪 hong努力打工的汪 hong努力学习的汪同理于 ^ 号,$ 号用来匹配字符是否是最后一个。
例如,学习的(汪|打工人)$ 匹配以 [ 汪 ] 或者 [ 打工人 ] 结尾的字符串。
"学习的(汪|打工人)$" => //注意:下列字符串要分四次匹配,因为即使换行了,前三行字符串本质上都不在字符串结尾//或者标志换成 /m 而不是 /g 因为此处还未说到标志,所以默认大家使用/g全局搜索努力学习的汪 努力学习的打工人努力学习的打工人 hongjilins努力学习的汪_ //此处 _ 模拟表示空格\b : 单词边界:指[a-zA-z0-9]之外的字符,举个栗子:\bis\b
'\bis\b'=>My name is hongjilinmy name@is@hong jilinmyname学is习hongjilinmynameishongjilin //只有此处不被匹配这些简写字符集,简洁明了且非常常用,但是也因为这样当初没有仔细去记,用时还得查阅资料十分不便,建议背下来
正则表达式提供一些常用的字符集简写。如下:
| . | 除换行符外的所有字符 |
| \w | 匹配所有字母数字,等同于 [a-zA-Z0-9_] |
| \W | 匹配所有非字母数字,即符号,等同于: [^\w] |
| \d | 匹配数字: [0-9] |
| \D | 匹配非数字: [^\d] |
| \s | 匹配所有空格字符,等同于: [\t\n\f\r\p{Z}] |
| \S | 匹配所有非空格字符: [^\s] |
| \f | 匹配一个换页符 |
| \n | 匹配一个换行符 |
| \r | 匹配一个回车符 |
| \t | 匹配一个制表符 |
| \v | 匹配一个垂直制表符 |
| \p | 匹配 CR/LF(等同于 \r\n),用来匹配 DOS 行终止符 |
先行断言和后发断言都属于非捕获簇(不捕获文本 ,也不针对组合计进行计数)。 先行断言用于判断所匹配的格式是否在另一个确定的格式之前,匹配结果不包含该确定格式(仅作为约束)。
例如,我们想要获得所有跟在 $ 符号后的数字,我们可以使用正后发断言 (?<=\$)[0-9\.]*。 这个表达式匹配 $ 开头,后面可以跟着单个字符:[0~9]、[.]、[?],这些字符可以出现大于等于 0 次。
"(?<=\$)[0-9\.\?]*" =>$0.,1,2,3,$4,5,6,$?7,8,$..9.9?//0. //4 //?7 //..9.9?零宽度断言如下:
| ?= | 正先行断言-存在 |
| ?! | 负先行断言-排除 |
| ?<= | 正后发断言-存在 |
| ?<! | 负后发断言-排除 |
?=... 正先行断言,表示第一部分表达式之后必须跟着 ?=...定义的表达式。
返回结果只包含满足匹配条件的第一部分表达式(即不会返回先行断言匹配部分的内容)。 定义一个正先行断言要使用 ()。在括号内部使用一个问号和等号: (?=...)。
正先行断言的内容写在括号中的等号后面。 例如,表达式 学习的汪(?=\shong) 首先匹配 [ 学习的汪 ],然后在括号中我们又定义了正先行断言 (?=\shong) ,即 [ 学习的汪 ]后面紧跟着 [ (空格)hong ]。
"学习的汪(?=\shong)" => //此处断言中的可以再加如`+` 、`*` ......,此处举其中一个栗子说明努力学习的汪 hong //只有此处被匹配到 返回: [学习的汪] -->断言中的匹配项作为约束不会返回努力学习的汪 帅努力学习的汪hong //此处后面没有空格负先行断言 ?! 用于筛选所有匹配结果,筛选条件为 其后不跟随着断言中定义的格式。 正先行断言 定义和 负先行断言 一样,区别就是 = 替换成 ! 也就是 (?!...)。
表达式 学习的汪(?!\s+hong) 首先匹配 [ 学习的汪 ],然后在括号中我们又定义了负先行断言 (?!\shong) ,即 [ 学习的汪 ]后面不跟着 [ (空格)hong ]。
"学习的汪(?!\s+hong)" => 努力学习的汪 hong //只有此处不被匹配到努力学习的汪 帅努力学习的汪hong正后发断言 记作(?<=...) 用于筛选所有匹配结果,筛选条件为 其前跟随着断言中定义的格式。 例如,表达式 (?<=[学习|打工])的(汪|打工人) 匹配 [汪] 和 [打工人] ,且需要其前方跟着 [学习] 或 [打工] 。
"(?<=[学习|打工])的(汪|打工人)" => 努力学习的汪努力打工的打工人努力学习汪负后发断言 记作 (?<!...) 用于筛选所有匹配结果,筛选条件为 其前 不 跟随着断言中定义的格式。 例如,表达式 (?<![学习|打工])的(汪|打工人) 匹配 [汪] 和 [打工人] ,且需要其前方 不 跟着 [学习] 或 [打工] 。
"/(?<![学习|打工])的(汪|打工人)/g" => 努力学习的汪努力打工的打工人努力学习汪标志也叫模式修正符,因为它可以用来修改表达式的搜索结果。 这些标志可以任意的组合使用,它也是整个正则表达式的一部分。
| i | 忽略大小写。 |
| g | 全局搜索。 |
| m | 多行修饰符:锚点元字符 ^ $ 工作范围在每行的起始。 |
修饰符 g 常用于执行一个全局搜索匹配,即(不仅仅返回第一个匹配的,而是返回全部)。 例如,表达式 /学习的汪/g 表示搜索 任意字符(除了换行)+ 学习的汪,并返回全部结果。
"/学习的汪/g" => 努力学习的汪努力学习的汪非常努力学习的汪修饰语 i 用于忽略大小写。 例如,表达式 /Hong/gi 表示在全局搜索 Hong,在后面的 i 将其条件修改为忽略大小写,则变成搜索 [hong(忽略大小写)],g 表示全局搜索。
"/Hong/gi" => //默认情况下是大小写敏感的,但此处这样标志后,就成为忽略大小写hongjilinHongjilinHONGJILINhOngjilin多行修饰符 m 常用于执行一个多行匹配。
像之前介绍的 (^,$) 用于检查格式是否是在待检测字符串的开头或结尾。但我们如果想要它在每行的开头和结尾生效,我们需要用到多行修饰符 m。
例如,表达式 /学习的(汪|打工人)$/gm 表示 [学习的] 后面跟着 [汪] 或 [打工人] ,末尾可选除换行符外任意字符。根据 m 修饰符,现在表达式匹配每行的结尾。–>此处举出上方锚点的例子
"/学习的(汪|打工人)$/gm" => //在之前说到 锚点 时提到,如果是 /g 只能一行一行匹配,而如果换成这个就可以直接匹配努力学习的汪努力学习的打工人努力学习的打工人 hongjilins努力学习的汪_ //此处 _ 模拟表示空格正则表达式默认采用贪婪匹配模式,在该模式下意味着会匹配尽可能长的子串。
"/(.*汪)/" => 努力学习的汪 非常认真读书的汪 的汪 的汪我们可以使用 ? 将贪婪匹配模式转化为惰性匹配模式。
"/(.*?汪)/" => 努力学习的汪 非常认真读书的汪 的汪 的汪正则中常用方法主要分为两类,一类是:RegExp对象方法;一类是:支持正则表达式的String对象的方法。
此处主要列举常用的
RegExp对象方法:exce()、test() 、compile()
介绍:方法用于检测一个字符串是否匹配某个模式;如果字符串中有匹配的值返回 true ,否则返回 false。
const str="努力学习的汪";//查找"学习"const reg1=/学习/g;const result1=reg1.test(str);console.log(result1); //true//查找 "打工"const reg2=/打工/g;const result2=reg2.test(str);console.log(result2); //false该方法的作用是能够对正则表达式进行编译,被编译过的正则在使用的时候效率会更高,适合于对一个正则多次调用的情况下,如果对一个正则只使用一两次,那么该方法没有特别显著的效应。
const reg=/[abc]/gi;console.log(reg.test('a'));//truereg=/[cde]/gi;console.log(reg.test('a')); //falsereg.compile(reg);console.log(reg.test('a')); //false被编译的正则和没有编译的正则在测试结果上没有任何区别,只是多次调用的效率上会更高一些
字符串对象共有 4 个方法,可以使用正则表达式:match()、replace()、search()和split()。
ES6 将这 4 个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。
用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。
如果找到任何匹配的子串,则返回 该子串在原字符串中的第一次出现的位置。
如果没有找到任何匹配的子串,则返回 -1。
用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。原字符串不变,创建一个新的字符串
创建一个新的字符串,原字符串不变
用于把一个字符串按符合匹配条件的方式分割成一个字符串数组。不改变原字符串
重点难点
在 ES5 中,RegExp构造函数的参数有两种情况。
第一种情况是,参数是字符串,这时第二个参数表示正则表达式的修饰符(flag 标志)。
var regex = new RegExp('xyz', 'i');// 等价于var regex = /xyz/i;第二种情况是,参数是一个正则表示式,这时会返回一个原有正则表达式的拷贝。
var regex = new RegExp(/xyz/i);// 等价于var regex = /xyz/i;但是,ES5 不允许此时使用第二个参数添加修饰符,否则会报错。
var regex = new RegExp(/xyz/, 'i');// Uncaught TypeError: Cannot supply flags when constructing one RegExp from anotherES6 改变了这种行为。如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
new RegExp(/abc/ig, 'i').flags// "i"上面代码中,原有正则对象的修饰符是ig,它会被第二个参数i覆盖。
字符串对象共有 4 个方法,可以使用正则表达式:match()、replace()、search()和split()。
ES6 将这 4 个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。
ES6 对正则表达式添加了u修饰符,含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。
/^\uD83D/u.test('\uD83D\uDC2A') // false/^\uD83D/.test('\uD83D\uDC2A') // true上面代码中,\uD83D\uDC2A是一个四个字节的 UTF-16 编码,代表一个字符。但是,ES5 不支持四个字节的 UTF-16 编码,会将其识别为两个字符,导致第二行代码结果为true。加了u修饰符以后,ES6 就会识别其为一个字符,所以第一行代码结果为false。
一旦加上u修饰符号,就会修改下面这些正则表达式的行为:
点(.)字符在正则表达式中,含义是除了换行符以外的任意单个字符。对于码点大于0xFFFF的 Unicode 字符,点字符不能识别,必须加上u修饰符。
var s = '𠮷';/^.$/.test(s) // false/^.$/u.test(s) // true上面代码表示,如果不添加u修饰符,正则表达式就会认为字符串为两个字符,从而匹配失败。
ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上u修饰符,才能识别当中的大括号,否则会被解读为量词。
/\u{61}/.test('a') // false/\u{61}/u.test('a') // true/\u{20BB7}/u.test('𠮷') // true上面代码表示,如果不加u修饰符,正则表达式无法识别\u{61}这种表示法,只会认为这匹配 61 个连续的u。
使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的 Unicode 字符。
/a{2}/.test('aa') // true/a{2}/u.test('aa') // true/𠮷{2}/.test('𠮷𠮷') // false/𠮷{2}/u.test('𠮷𠮷') // trueu修饰符也影响到预定义模式,能否正确识别码点大于0xFFFF的 Unicode 字符。
/^\S$/.test('𠮷') // false/^\S$/u.test('𠮷') // true上面代码的\S是预定义模式,匹配所有非空白字符。只有加了u修饰符,它才能正确匹配码点大于0xFFFF的 Unicode 字符。
利用这一点,可以写出一个正确返回字符串长度的函数。
function codePointLength(text) {var result = text.match(/[\s\S]/gu);return result ? result.length : 0;}var s = '𠮷𠮷';s.length // 4codePointLength(s) // 2有些 Unicode 字符的编码不同,但是字型很相近,比如,\u004B与\u212A都是大写的K。
/[a-z]/i.test('\u212A') // false/[a-z]/iu.test('\u212A') // true上面代码中,不加u修饰符,就无法识别非规范的K字符。
没有u修饰符的情况下,正则中没有定义的转义(如逗号的转义\,)无效,而在u模式会报错。
/\,/ // /\,//\,/u // 报错上面代码中,没有u修饰符时,逗号前面的反斜杠是无效的,加了u修饰符就报错。
正则实例对象新增unicode属性,表示是否设置了u修饰符。
const r1 = /hello/;const r2 = /hello/u;r1.unicode // falser2.unicode // true上面代码中,正则表达式是否设置了u修饰符,可以从unicode属性看出来。
提前剧透,y修饰符号相当于 /g隐含了头部匹配的标志^。带着这个思维就相对容易理解下方的粘连一次
除了u修饰符,ES6 还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。实际上相当于默认加了一个^
y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
var s = 'aaa_aa_a';var r1 = /a+/g;var r2 = /a+/y;r1.exec(s) // ["aaa"] -->此时剩余字符串[_aa_a]r2.exec(s) // ["aaa"] -->此时剩余字符串[_aa_a]r1.exec(s) // ["aa"]r2.exec(s) // null -->y必须从第一个位置开始匹配,实际上相当于默认加了一个`^`,所以匹配不到上面代码有两个正则表达式,一个使用g修饰符,另一个使用y修饰符。这两个正则表达式各执行了两次,第一次执行的时候,两者行为相同,剩余字符串都是_aa_a。由于g修饰没有位置要求,所以第二次执行会返回结果,而y修饰符要求匹配必须从头部开始,所以返回null。
如果改一下正则表达式,保证每次都能头部匹配,y修饰符就会返回结果了。
var s = 'aaa_aa_a';var r = /a+_/y;r.exec(s) // ["aaa_"]r.exec(s) // ["aa_"]上面代码每次匹配,都是从剩余字符串的头部开始。
使用lastIndex属性,可以更好地说明y修饰符。
const REGEX = /a/g;// 指定从2号位置(y)开始匹配REGEX.lastIndex = 2;// 匹配成功const match = REGEX.exec('xaya');// 在3号位置匹配成功match.index // 3// 下一次匹配从4号位开始REGEX.lastIndex // 4// 4号位开始匹配失败REGEX.exec('xaya') // null上面代码中,lastIndex属性指定每次搜索的开始位置,g修饰符从这个位置开始向后搜索,直到发现匹配为止。
y修饰符同样遵守lastIndex属性,但是要求必须在lastIndex指定的位置发现匹配。
const REGEX = /a/y;// 指定从2号位置开始匹配REGEX.lastIndex = 2;// 不是粘连,匹配失败REGEX.exec('xaya') // null// 指定从3号位置开始匹配REGEX.lastIndex = 3;// 3号位置是粘连,匹配成功const match = REGEX.exec('xaya');match.index // 3REGEX.lastIndex // 4实际上,y修饰符号隐含了头部匹配的标志^。
/b/y.exec('aba')// null上面代码由于不能保证头部匹配,所以返回null。y修饰符的设计本意,就是让头部匹配的标志^在全局匹配中都有效。
下面是字符串对象的replace方法的例子。
const REGEX = /a/gy;'aaxa'.replace(REGEX, '-') // '--xa'上面代码中,最后一个a因为不是出现在下一次匹配的头部,所以不会被替换。
单单一个y修饰符对match方法,只能返回第一个匹配,必须与g修饰符联用,才能返回所有匹配。
'a1a2a3'.match(/a\d/y) // ["a1"]'a1a2a3'.match(/a\d/gy) // ["a1", "a2", "a3"]y修饰符的一个应用,是从字符串提取 token(词元),y修饰符确保了匹配之间不会有漏掉的字符。
const TOKEN_Y = /\s*(\+|[0-9]+)\s*/y;const TOKEN_G = /\s*(\+|[0-9]+)\s*/g;tokenize(TOKEN_Y, '3 + 4')// [ '3', '+', '4' ]tokenize(TOKEN_G, '3 + 4')// [ '3', '+', '4' ]function tokenize(TOKEN_REGEX, str) {let result = [];let match;while (match = TOKEN_REGEX.exec(str)) {result.push(match[1]);}return result;}上面代码中,如果字符串里面没有非法字符,y修饰符与g修饰符的提取结果是一样的。但是,一旦出现非法字符,两者的行为就不一样了。
tokenize(TOKEN_Y, '3x + 4')// [ '3' ]tokenize(TOKEN_G, '3x + 4')// [ '3', '+', '4' ]上面代码中,g修饰符会忽略非法字符,而y修饰符不会,这样就很容易发现错误。
与y修饰符相匹配,ES6 的正则实例对象多了sticky属性,表示是否设置了y修饰符。
var r = /hello\d/y;r.sticky // true正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;另一个是行终止符(line terminator character)。
所谓行终止符,就是该字符表示一行的终结。以下四个字符属于“行终止符”。
上面代码中,因为.不匹配\n,所以正则表达式返回false。
但是,很多时候我们希望匹配的是任意单个字符,这时有一种变通的写法。
/foo[^]bar/.test('foo\nbar') // true这种解决方案毕竟不太符合直觉, ES2018 引入s修饰符,使得.可以匹配任意单个字符。
/foo.bar/s.test('foo\nbar') // true这被称为dotAll模式,即点(dot)代表一切字符。所以,正则表达式还引入了一个dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式。
const re = /foo.bar/s;// 另一种写法// const re = new RegExp('foo.bar', 's');re.test('foo\nbar') // truere.dotAll // truere.flags // 's'/s修饰符和多行修饰符/m不冲突,两者一起使用的情况下,.匹配所有字符,而^和$匹配每一行的行首和行尾。
ES6 为正则表达式新增了flags属性,会返回正则表达式的修饰符。
// ES5 的 source 属性// 返回正则表达式的正文/abc/ig.source// "abc"// ES6 的 flags 属性// 返回正则表达式的修饰符/abc/ig.flags// 'gi'ES2018 引入了一种新的类的写法\p{...}和\P{...},允许正则表达式匹配符合 Unicode 某种属性的所有字符。
const regexGreekSymbol = /\p{Script=Greek}/u;regexGreekSymbol.test('π') // true上面代码中,\p{Script=Greek}指定匹配一个希腊文字母,所以匹配π成功。
Unicode 属性类要指定属性名和属性值。
\p{UnicodePropertyName=UnicodePropertyValue}对于某些属性,可以只写属性名,或者只写属性值。
\p{UnicodePropertyName}\p{UnicodePropertyValue}\P{…}是\p{…}的反向匹配,即匹配不满足条件的字符。
注意,这两种类只对 Unicode 有效,所以使用的时候一定要加上u修饰符。如果不加u修饰符,正则表达式使用\p和\P会报错,ECMAScript 预留了这两个类。
由于 Unicode 的各种属性非常多,所以这种新的类的表达能力非常强。
const regex = /^\p{Decimal_Number}+$/u;regex.test('𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼') // true上面代码中,属性类指定匹配所有十进制字符,可以看到各种字型的十进制字符都会匹配成功。
\p{Number}甚至能匹配罗马数字。
// 匹配所有数字const regex = /^\p{Number}+$/u;regex.test('²³¹¼½¾') // trueregex.test('㉛㉜㉝') // trueregex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true下面是其他一些例子。
// 匹配所有空格\p{White_Space}// 匹配各种文字的所有字母,等同于 Unicode 版的 \w[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]// 匹配各种文字的所有非字母的字符,等同于 Unicode 版的 \W[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]// 匹配 Emoji/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu// 匹配所有的箭头字符const regexArrows = /^\p{Block=Arrows}+$/u;regexArrows.test('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true正则表达式使用圆括号进行组匹配。
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;上面代码中,正则表达式里面有三组圆括号。使用exec方法,就可以将这三组匹配结果提取出来。
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;const matchObj = RE_DATE.exec('1999-12-31');const year = matchObj[1]; // 1999const month = matchObj[2]; // 12const day = matchObj[3]; // 31组匹配的一个问题是,每一组的匹配含义不容易看出来,而且只能用数字序号(比如matchObj[1])引用,要是组的顺序变了,引用的时候就必须修改序号。
ES2018 引入了具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。
const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;const matchObj = RE_DATE.exec('1999-12-31');const year = matchObj.groups.year; // 1999const month = matchObj.groups.month; // 12const day = matchObj.groups.day; // 31上面代码中,“具名组匹配”在圆括号内部,模式的头部添加 [ “问号 + 尖括号 + 组名” ] (?<year>),然后就可以在exec方法返回结果的groups属性上引用该组名。同时,数字序号(matchObj[1])依然有效。
具名组匹配等于为每一组匹配加上了 ID,便于描述匹配的目的。如果组的顺序变了,也不用改变匹配后的处理代码。
如果具名组没有匹配,那么对应的groups对象属性会是undefined。
const RE_OPT_A = /^(?<as>a+)?$/;const matchObj = RE_OPT_A.exec('');matchObj.groups.as // undefined'as' in matchObj.groups // true上面代码中,具名组as没有找到匹配,那么matchObj.groups.as属性值就是undefined,并且as这个键名在groups是始终存在的。
有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。
let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');one // footwo // bar字符串替换时,使用$<组名>引用具名组。
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')// '02/01/2015'上面代码中,replace方法的第二个参数是一个字符串,而不是正则表达式。
replace方法的第二个参数也可以是函数,该函数的参数序列如下。
'2015-01-02'.replace(re, (matched, // 整个匹配结果 2015-01-02capture1, // 第一个组匹配 2015capture2, // 第二个组匹配 01capture3, // 第三个组匹配 02position, // 匹配开始的位置 0S, // 原字符串 2015-01-02groups // 具名组构成的一个对象 {year, month, day}) => {let {day, month, year} = groups;return `${day}/${month}/${year}`;});具名组匹配在原来的基础上,新增了最后一个函数参数:具名组构成的一个对象。函数内部可以直接对这个对象进行解构赋值。
如果要在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>的写法。
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;RE_TWICE.test('abc!abc') // trueRE_TWICE.test('abc!ab') // false数字引用(\1)依然有效。
const RE_TWICE = /^(?<word>[a-z]+)!\1$/;RE_TWICE.test('abc!abc') // trueRE_TWICE.test('abc!ab') // false这两种引用语法还可以同时使用。
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/;RE_TWICE.test('abc!abc!abc') // trueRE_TWICE.test('abc!abc!ab') // false如果一个正则表达式在字符串里面有多个匹配,现在一般使用g修饰符或y修饰符,在循环里面逐一取出。
var regex = /t(e)(st(\d?))/g;var string = 'test1test2test3';var matches = [];var match;while (match = regex.exec(string)) {matches.push(match);}matches// [// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"],// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"],// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]// ]上面代码中,while循环取出每一轮的正则匹配,一共三轮。
ES2020 增加了String.prototype.matchAll()方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。
const string = 'test1test2test3';const regex = /t(e)(st(\d?))/g;for (const match of string.matchAll(regex)) {console.log(match);}// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"]// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]上面代码中,由于string.matchAll(regex)返回的是遍历器,所以可以用for...of循环取出。相对于返回数组,返回遍历器的好处在于,如果匹配结果是一个很大的数组,那么遍历器比较节省资源。
遍历器转为数组是非常简单的,使用...运算符和Array.from()方法就可以了。
// 转为数组的方法一[...string.matchAll(regex)]// 转为数组的方法二Array.from(string.matchAll(regex))输入非数字内容则无法键入(替换为空)