JS正则表达式使用
简介
大多数程序语言都有正则表达式,根据语言不同也都存在部分去呗,这里简单介绍一下在js层面,对正则的一些使用,其他语言可以作为参考
主要作用其实是作为一个开发正则时的示例,因为相对来说平时使用正则的机会是比较少的,而正则又是非常复杂的,不同符号的不同组合都有不同的含义,不经常用到就非常容易遗忘,这个时候看看笔记辅助工具就能快速上手遗忘的部分,也可以作为一个学习参考。正则并不局限于只做校验,也能帮助我们从冗余数据中拿到自己想要的数据
主要参考
大多数内容来自之前写的文章,做了一些调整补充
通用工具
以正则大全中网址正则为例:
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/
-
图解正则:将复杂的正则转化分解,如图,对复杂的正则非常有用,哪怕是层层叠加也可以直观的看到每一步在干什么,有没有问题。但是这个工具也存在部分局限,对于部分正确正则无法解析
-
在线正则验证:可以在各语言环境下检验正则匹配,并分解匹配项,右边有释义。虽然是英文但是理解含义来说可能比mdn\w3c的翻译版更准确,而且检测正确性来说应该是几个网站里最准确的
- 正则大全:现成正则,平时写正则不多的,直接用这个做校验也够了,他也有vscode插件安装后可以非常方便的引入常用正则
规则
1、最先匹配上的优先,例
'1wer123s'.replace(/1/,'') //"wer123s"
2、如果需要完全匹配字符需要加上^开始$结尾,否则定义的正则都是只要包含就行
let reg = /1/;
let str = '157788';
reg.test(str); //true
创建
let reg = /abc/;
let reg = new RegExp("abc");
修饰符g i m
放在正则最后面,可以叠加,比如//gi 就是全局匹配并忽略大小写,除了gi,其他都不常使用。
- g 表示全局匹配
- i 忽略大小写
- m 多行匹配(如下示例,每行都会单独匹配/^mtest/m,这种情况^就不是完全匹配字符串了,不加上起始标识符整体就是全局匹配没区别)
- s 单行匹配
- u 使用 unicode 码的模式进行匹配。js提供了一些事件来获取unicode码,有需要可以自行查阅。使用需要注意某些情况
- y 粘性标志,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始,即粘性
let reg = /1/g;
let str = '1516';
str.split(reg); // ["", "5", "6"]
let reg = /mtest/g;
let reg2 = /mtest/s;
let reg3 = /^mtest$/m;
let str = `123
mtest
46`;
str.match(reg) // ['mtest']
str.match(reg2) // ['mtest', index: 4, input: '123\nmtest\n46', groups: undefined]
str.match(reg3) // ['mtest', index: 4, input: '123\nmtest\n46', groups: undefined]
/\u{61}/u.test('a') // true,不加u修饰符,正则表达式无法识别\u{61}这种表示法
let s = 'aaa_aa_a';
let r1 = /a+/g;
let r2 = /a+/y;
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aa"]
r1.exec(s) // ["aaa"]
r2.exec(s) // null
带有sticky标志的正则表达式将会从源字符串的RegExp.prototype.lastIndex位置开始匹配,也就是进行“粘性匹配”。
分组()和|或
|表示或,如果分组中没有|,就必须匹配分组内完整的字符串
let reg = /^(1|2)/;
let str = '22$15';
reg.test(str); //true
let reg = /^(12)/;
let str = '22$15';
reg.test(str); // false
当正则有使用分组时,可以通过RegExp.后跟的数字表示是匹配的正则中第几个分组。
let reg = /^(1)\d*(9)$/;
let str = '1345789';
reg.test(str); //true
RegExp.$1 // '1'
RegExp.$2 // '9'
灵活使用分组可以方便快速解决我们很多问题,是非常重要的一个特性
捕获
let reg = /^(1)\d*(9)$/;
let str = '1345789';
reg.exec(str); // ['1345789', '1', '9', index: 0, input: '1345789', groups: undefined]
reg.test(str); // true
RegExp.$1 // '1'
RegExp.$2 // '9'
非捕获
匹配 'x' 但是不记住匹配项。这种括号叫作非捕获括号,使得你能够定义与正则表达式运算符一起使用的子表达式。
let reg = /^(?:1)\d*(9)$/;
let str = '1345789';
reg.exec(str); // ['1345789', '9', index: 0, input: '1345789', groups: undefined]
reg.test(str); // true
RegExp.$1 // '9',
RegExp.$2 // ''
需要转义的特殊字符
特殊字符:() [] {} ^ $ * ? \ | + . 需要用\转义。其实就是在正则使用场景有特殊含义的就需要转义
let reg = /\$/;
let str = '15$';
reg.test(str); //true
元字符(特殊含义缩写字符)
特殊字符(换行等)\t \n \f ...
***注意:***空格符没有特殊字符,直接输入空格即可
let reg = / /g;
let str = `a b`;
reg.test(str); // true
let reg = / /g;
let str = `ab`;
reg.test(str); // false
| 符号 | 含义 |
|---|---|
| \t | 制表符 |
| \n | 换行符 |
| \f | 换页符 |
| \r | 回车符 |
| \0 | NUL 字符 |
| \v | 垂直制表符 |
| \xxx | 以八进制数 xxx 规定的字符 |
| \xdd | 查找以十六进制数 dd 规定的字符。 |
| \u{xxxx} | 匹配一个四位十六进制数 xxxx 规定的 Unicode 字符,使用需要添加u修饰符 |
let reg = /\n/;
let str = `a
b`;
reg.test(str); // true
类 (字符集缩写). \d \s \w ...
| 类 | 字符集 | 含义 |
|---|---|---|
| . | [^\n\r] | 非换行、回车符 |
| \d | [0-9] | 数字 |
| \D | [^0-9] | 非数字 |
| \s | [ \t\n\x0B\f\r] | 空白(\x0B表示垂直tab,无法书写),含空格 |
| \S | [^ \t\n\x0B\f\r] | 非空白 |
| \w | [a-zA-Z_0-9] | 单词字符(字母数字下划线) |
| \W | [^a-zA-Z_0-9] | 非单词 |
let reg = /\d/;
let str = '15$';
reg.test(str); //true
断言(位置相关操作)
分界符^ $ \b \B
-
^ 以某字符开头
-
$ 已某字符结尾
let reg = /^1/; let str = '16+19'; reg.test(str); // true
| 字符 | 含义 |
|---|---|
| ^n | 以n开头 |
| n$ | 以n结尾 |
| \b | 单词边界(单词字符的边界)例:2a+3b=5c,边界就是2a,3b,5c左右两边的位置 |
| \B | 非单词边界,如上的例子,非单词边界就是2和a,3和b,5和c之间的位置 |
let str = 'm13';
let reg1 = /\bm1/; // \b放左就表示匹配字符m1其左边是边界
let reg2 = /m1\b/; // \b放右就表示匹配字符m1其右边是边界
reg1.test(str); // true,
reg2.test(str); //false,
现行&后发断言
| 模式 | 含义 |
|---|---|
x(?=y) | x后紧跟y, 匹配'x'仅仅当'x'后面跟着'y'.这种叫做先行断言。 例如,/Jack(?=Sprat)/会匹配到'Jack'仅当它后面跟着'Sprat'。/Jack(?=Sprat|Frost)/匹配‘Jack’仅当它后面跟着'Sprat'或者是‘Frost’。但是‘Sprat’和‘Frost’都不是匹配结果的一部分。 |
| (?<=y)`x | x前紧跟y, 匹配'x'仅当'x'前面是'y'.这种叫做后行断言。 例如,/(?<=Jack)Sprat/会匹配到' Sprat '仅仅当它前面是' Jack '。/(?<=Jack|Tom)Sprat/匹配‘ Sprat ’仅仅当它前面是'Jack'或者是‘Tom’。但是‘Jack’和‘Tom’都不是匹配结果的一部分。 |
x(?!y) | x后不跟y, 仅仅当'x'后面不跟着'y'时匹配'x',这被称为正向否定查找。 例如,仅仅当这个数字后面没有跟小数点的时候,/\d+(?!.)/ 匹配一个数字。正则表达式/\d+(?!.)/.exec("3.141")匹配‘141’而不是‘3.141’ |
(?<!y)x | x前不跟y, 仅仅当'x'前面不是'y'时匹配'x',这被称为反向否定查找。 例如, 仅仅当这个数字前面没有负号的时候,/(?<!-)\d+/ 匹配一个数字。 /(?<!-)\d+/.exec('3') 匹配到 "3". /(?<!-)\d+/.exec('-3') 因为这个数字前有负号,所以没有匹配到。 |
let str = 'foofoo';
let reg1 = /foo{2,3}/;
let reg2 = /(?:foo){2,3}/;
reg1.test(str); // false
reg2.test(str); // true
先行断言(前瞻)
| 符号 | 含义 |
|---|---|
| x(?=y) | x其后紧跟 y |
| x(?!y) | x后不跟y |
let str="Is this all there is";
let reg=/is(?= all)/g;
reg.test(str) // true
let str="Is this all there is";
let reg=/is(?=all)/g;
reg.test(str) // false
后发断言(后顾)
| 符号 | 含义 |
|---|---|
| (?<=y)x | x前紧跟y |
| (?<!y)x | x前不跟y |
字符集[]
基础
let reg = /[a13kc]/;
let str = '12$15';
reg.test(str); //true
范围字符集
常用
- [a-z] 小写子母集合,也可以从中间开始取[b-d]表示bcd
- [A-Z] 大写子母集合
- [0-9] 数字集合
特殊含义字符
^非
[^123] 表示不包含1的字符集
\b退格符
[\b]表示退格符(非元字符的单词边界)
注意:
除了 \,-,^, ] 之外的非字母数字字符在字符类中都没有特殊含义,不需要转义
组合拼接[0-9]
常用
[a-zA-Z] 子母集合
[\u4e00-\u9fa5] 中文集合 (汉字Unicode编码)
量词? * + {}
| 符号 | 出现次数(x) |
|---|---|
| ? | 0 || 1 {0,1} |
| * | x>=0 {0,} |
| + | x>=1 {1,} |
| {n} | 0 || n |
| {n,m} | n<= x <=m (中间不能有空格,有空格就不能识别为量词) |
| {n,} | x >= n |
let reg = /\$+/;
let str = '15$16$$';
str.split(reg); // ["15", "16", ""]
/^\d{6}$/.test('123456') // true,带上^$才能标识只匹配6个数字,不带就表示字符串包含6个数字就为true
模式
参考较多
性能优化探究影响性能的因素NFA自动机的回溯
注意:
1、同贪婪模式一样,独占模式一样会匹配最长。不过在独占模式下,正则表达式尽可能长地去匹配字符串,一旦匹配不成功就会结束匹配而不会回溯,可用于性能优化
2、js不支持独占模式,会报错
| 贪婪(量词, 匹配尽可能多的字符串) | 懒惰(量词?, 一旦找到匹配就会停止) | 独占(量词+,不支持) |
|---|---|---|
| X? | X?? | X?+ |
| X* | X*? | X*+ |
| X+ | X+? | X++ |
| X{n} | X{n}? | X{n}+ |
| X{n,} | X{n,}? | X{n,}+ |
| X{n,m} | X{n,m}? | X{n,m}+ |
贪婪模式(最长匹配).+ , .*
1、量词默认贪婪模式(会先匹配最长的,匹配不上再回溯,往前推。所以量词要避免嵌套否则会导致指数级回溯)
let reg = /b{1,3}/;
let str = 'aaabbc';
str.match(reg) // ['bb', index: 3, input: 'aaabbc', groups: undefined]
2、/a.*b/表示匹配a到b的最长字符串
let reg = /a.*b/; // .在元字符类(下文)中表示非(换行&回车符),.*即表示存在>=0个字符,放在中间会触发最长匹配
let str = '15a$a16b$b$bc';
str.match(reg) // ['a$a16b$b$b', index: 2, input: '15a$a16b$b$bc', groups: undefined]
str.split(reg); // ["15", "c"]
str.replace(reg, '-'); //'15-c'
懒惰模式(最短匹配) .*?
1、量词后加?,变成懒惰模式
let reg = /b{1,3}?/;
let str = 'abbc';
reg.test(str) //true
let reg = /b{1,3}?/; // 懒惰
let str = 'aaabbc';
str.match(reg) // 先匹配最短['b', index: 3, input: 'aaabbc', groups: undefined]
str.split(reg) // ['aaa', '', 'c']
let reg = /b{1,3}/; // 贪婪
let str = 'aaabbbc';
str.match(reg) // 先匹配最长['bbb', index: 3, input: 'aaabbbc', groups: undefined]
2、/a.*?b/表示匹配a到b的最短字符串
let reg = /a.*?b/;
let str = '15a$a16b$b$bc';
str.split(reg); // ["15", "$b$bc"]
性能
正则也是存在性能问题的,某些优化不好的正则会不断回溯,占用cpu很长时间去匹配,影响页面效果。
以下案例都是比较简单很容易发现,但是在非常复杂且长的正则中出现了这种情况就比较难发现,所以平时写代码的时候就要多关注
console.time('start');
console.log(/(A+A+)+B/.exec('AAAAAAAAAA')); // (A+A+)+量词嵌套导致回溯
console.timeEnd('end')
在使用量词匹配时尽量使用[最长](#贪婪模式(最长匹配).+ , .*)或[最短](#懒惰模式(最短匹配).+? , .*?)匹配
js正则相关方法
传入方法的数字一般会匹配去对应的字符串
下面两种方法即指的是谁调用的这些方法,有时候可能会记混这个方法倒是正则调用还是字符串调用,可以通过方法英文含义区分,正则调用一般是检测,字符串调用一般是查找匹配
RegExp 正则对象方法
test
含有与 RegExpObject 匹配的文本,则返回 true,否则返回 false。
RegExpObject.test(string)
exec
字符串中的正则表达式的匹配。返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
RegExpObject.exec(string)
/\d/.exec('21ssfr') // ["2", index: 0, input: "21ssfr", groups: undefined]
let reg = /\[(.*?)\]/g;
let str = `<div>[开心]addff[强壮]24</div>`
reg.exec(str); // (2) ['[开心]', '开心', index: 5, input: '<div>[开心]addff[强壮]24</div>', groups: undefined]
reg.exec(str); // ['[强壮]', '强壮', index: 14, input: '<div>[开心]addff[强壮]24</div>', groups: undefined]
let str = '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}';
let reg = /\{\{\s*(.+?)\s*\}\}/g;
str.match(reg); // ['{{ day.value }}', '{{ day.weather }}']
reg.exec(str); // ['{{ day.value }}', ' day.value ', index: 11, input: '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}', groups: undefined]
reg.exec(str); // ['{{ day.weather }}', ' day.weather ', index: 29, input: '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}', groups: undefined]
// matchAll替代多次执行exec,返回迭代器
Array.from(str.matchAll(reg)); // (2) [Array(2), Array(2)]
/*
0: (2) ['{{ day.value }}', ' day.value ', index: 11, input: '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}', groups: undefined]
1: (2) ['{{ day.weather }}', ' day.weather ', index: 29, input: '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}', groups: undefined]
length: 2
[[Prototype]]: Array(0)
*/
注意:
1、此数组的第 0 个元素是与正则表达式相匹配的文本,如果有分组的话第 1 个元素是与 RegExpObject 的第 1 个子表达式(括号分组)相匹配的文本,如果有第二个分组的话第 2 个元素是与 RegExpObject 的第 2 个子表达式相匹配的文本
即返回的只有第0个元素才是完全匹配的文本,后面两个(非index、input等属性)都只是子表达式(括号中的分组捕获)匹配的结果
2、当正则表达式设置 g 标志位时,可以多次执行 exec 方法来查找同一个字符串中的成功匹配。当你这样做时,查找将从正则表达式的 lastIndex 属性指定的位置开始。(test() 也会更新 lastIndex 属性)。注意,即使再次查找的字符串不是原查找字符串时,lastIndex 也不会被重置,它依旧会从记录的 lastIndex 开始。
3、exec叠加g标志,多次执行可以用matchAll代替
4、
String 字符串对象方法
search
stringObject.search(regexp|string)
返回值
stringObject 中第一个与 regexp 相匹配的子串的起始位置。
注释:如果没有找到任何匹配的子串,则返回 -1。
说明
search() 方法不执行全局匹配,它将忽略标志 g。它同时忽略 regexp 的 lastIndex 属性,并且总是从字符串的开始进行检索,这意味着它总是返回 stringObject 的第一个匹配的位置
let str = '11te3st123'
let reg = /1/;
str.search(reg); // 0
match
match() 方法可在字符串内检索指定的值,或找到一个或多个(有无全局匹配g标识)正则表达式的匹配。
该方法类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。
同exec,如果正则中存在分组,match返回的数组第一个值是匹配的字符串,后续的值都是匹配的子表达式(分组)
stringObject.match(string | regexp)
例
let str = 'test123';
let reg = /^test/;
str.match(reg); // ["test", index: 0, input: "test123", groups: undefined]
let searchStr = 'st';
str.match(searchStr); // ["st", index: 2, input: "test123", groups: undefined]
let str = '11te3st123';
let reg = /1/g;
str.match(reg); // ["1", "1", "1"]
matchAll
使用 matchAll ,就可以不必使用 while 循环加 exec 方式(且正则表达式需使用 /g 标志)。使用 matchAll 会得到一个迭代器的返回值,配合 for...of, array spread, 或者 Array.from() 可以更方便实现功能:
let str = '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}';
let reg = /\{\{(.+?)\}\}/g;
str.match(reg); // ['{{ day.value }}', '{{ day.weather }}']
reg.exec(str); // ['{{ day.value }}', ' day.value ', index: 11, input: '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}', groups: undefined]
reg.exec(str); // ['{{ day.weather }}', ' day.weather ', index: 29, input: '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}', groups: undefined]
// matchAll,返回迭代器
Array.from(str.matchAll(reg)); // (2) [Array(2), Array(2)]
/*
0: (2) ['{{ day.value }}', ' day.value ', index: 11, input: '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}', groups: undefined]
1: (2) ['{{ day.weather }}', ' day.weather ', index: 29, input: '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}', groups: undefined]
length: 2
[[Prototype]]: Array(0)
*/
replace
www.w3school.com.cn/jsref/jsref…
replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
stringObject.replace(regexp/substr,replacement)
replacement: 字符串或函数,函数参数类似reg.exec(str)返回的数组字符串,第一项是匹配正则的字符串,后续为匹配子表达式(分组)的内容
let str="Every man in the world! Every woman on earth!";
patt=/man/g;
str2=str.replace(patt,"person"); // Every person in the world! Every woperson on earth!
patt=/(wo)?man/g; // ?量词匹配0 || 1
patt.compile(patt);
str2=str.replace(patt,"person"); // Every person in the world! Every person on earth!
第二个参数使用特殊字符$:
| 字符 | 替换文本 |
|---|---|
| 2、...、$99 | 与 regexp 中的第 1 到第 99 个子表达式(分组)相匹配的文本。 |
| $& | 与 regexp 相匹配的子串。 |
| $` | 位于匹配子串左侧的文本。 |
| $' | 位于匹配子串右侧的文本。 |
| $$ | 插入一个 "$" |
let reg = /(\w+)\+(\w+)/;
let str = 'a+b';
str.replace(reg, '$2, $1'); // "b, a"
str.replace(reg, '$$ $2'); // $ b
RegExp.$1 // "a"
RegExp.$2 // "b"
let reg = /(\w+)\+(\w+)/;
let str = "--a+b==";
str.replace(reg, "$`, $'"); // 把匹配上正则的a+b替换为左侧,右侧
'----, ===='
第二个参数使用函数
函数参数:
- 正则匹配的字符
- 捕获的字符(分组的捕获)
- 正则匹配字符的索引
- 字符串主题
let reg = /\w+/g; // 加上g修饰符才会执行多次
let str = 'a+b+c';
str.replace(reg,(match, char, index, str) => {
console.log(match) // 分三次返回a、b、c
return 1
}) // 1+1+1
replace和replaceAll
- replace不加
g字符那么只会匹配一次 - replace和replaceAll匹配全局都必须加上
g - replaceAll不加上加上
g会报错
结论:replace能实现replaceAll的所有效果,replaceAll只能用于全局匹配
split
split() 方法用于把一个字符串分割成字符串数组。
stringObject.split(string|reg,howmany)
howmany: 可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。
let str = '11te3st123';
let reg = /1/
str.split(reg, 3); // ["", "", "te3st"].length <= 3
正则案例
电话:
/^1[3-9]\d{9}$/
网址:
1、会排除特殊字符,也就是地址后面携带的url参数也不会被匹配到
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/
2、如果要匹配后面携带的参数这样简单匹配会更好,不会排除后续参数
/(ht|f)tps?:\/\/[^\s]*/gi
邮箱:
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
问题:
用正则怎么解析出字符串内的模板变量
let str = '嗨,您好,今天是星期 {{ day.value }}';
let reg = /\{\{.+?\}\}/;
reg.test(str);
let str = '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}';
let reg = /\{\{\s*(.+?)\s*\}\}/g;
str.match(reg); // ['{{ day.value }}', '{{ day.weather }}']
reg.exec(str); // ['{{ day.value }}', 'day.value', index: 11, input: '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}', groups: undefined]
reg.exec(str); // ['{{ day.weather }}', 'day.weather', index: 29, input: '嗨,您好,今天是星期 {{ day.value }},天气{{ day.weather }}', groups: undefined]
用正则怎么解析富文本拿到纯文本
let str = `<div class="test">
23456
<span>1234</span>
</div>`
let reg = /<.*?>/g
str.replace(reg, '').replace(/\s/, '');