前言
“ 醉过才知酒浓 🍷 ,爱过才知情重。❤️ ”
对于正则既爱又“恨”,爱是通过几句简单的code就能实现强大的功能,“恨”是难以真正的掌控它。
每次需要用到复杂点的正则表达式我们都要通过搜索引擎查询,而且每次查出来的结果还有出入,比较费时费力,因而对此做了一番整理。如果以后需要用到正则直接来这里就能找到,是不是会很爽。
🍔 阅读本文,你可以收获到以下知识 🍔:
- 了解如何
校验
自己写的正则 是否正确(减少或避免潜在bug)! - 能快速
搜索
到自己所需的正则! - 能掌握几个让你
少装插件的正则
- 能全面了解正则
实例方法
、高级模式
、基础用法
(查缺补漏
) - 对应的模块提供了对用的链接,能快速了解到更详细的用法 文章略长,建议先收藏 ⭐️(Ctrl + D 或 command + D)⭐️
先来些干货,再讲基础
🍞 检验你的正则是否正确!
先给大家介绍在线正则表达式图形化工具,利用此工具,你的正则能匹配到哪些字符 就能一目了然
。
下面是手机号/^1[34578]\d{9}$/
正则的展示点击尝试
🍞 搜索你所需要的正则!
这个搜索工具既支持网页搜索,也可在vscode上直接搜索。大部分日常使用的正则都可以在此搜索到。
- 网页上搜索正则表达式
- 下面展示的是在vscode编辑器上的使用 详见github使用说明[能支持web / vscode / idea / Alfred Workflow多平台]
🍞 能让你少装插件的正则!
1. 匹配html字符串中的指定标签
示例1:匹配所有 img 标签,并替换 src 属性(富文本编辑文章中常有的场景)
- 使用函数替换
// 需要匹配的字符串
let str='<div><img src=\'123\' /><div><img src="456"/></div></div>'
// 匹配所有图片及src属性的正则表达式
let reg = /<img [^>]*src=['"]([^'"]+)[^>]*>/gi
// 替换后重新赋值
str = str.replace(reg, function(match, ...args){
console.log(`匹配到的img标签:${match}`, `匹配到该标签的src值:${args}`)
return match
})
// 输出:
匹配到的img标签:<img src='123' /> 匹配到该标签的src值:123,5,当前匹配到的完整字符
匹配到的img标签:<img src="456"/> 匹配到该标签的src值:456,27,当前匹配到的完整字符
示例2:匹配字符串中的所有video标签、
然后获取到其src属性链接中的sn值、
然后再给video标签增加poster属性,其值就是sn的值。
(其实就是给video标签新增封面的属性,其值就是src属性上的sn值)
var str='<div><video src="http:xxx.com?sn=a-b-c-d" /></div>'
var replacement = (match, $1, $2, $3)=>`${$1}${$2} poster="${$2.match(/sn=([\w-_]+)&?/i)[1]}" ${$3}>`
var result = str.replace(/(<video [^>]*src=['"])([^'"]+sn=[^'"]+['"])([^>]*)>/gi, replacement)
console.log(result)
// 输出:'<div><video src="http:xxx.com?sn=a-b-c-d" poster="a-b-c-d" /></div>'
- 使用$符号替换($1-$99)
// 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本
var name = "Doe, John";
name.replace(/(\w+)\s*, \s*(\w+)/, "$2 $1");
// 输出 'John Doe'
var name = '"a", "b"';
name.replace(/"([^"]*)"/g, "'$1'");
// 输出 "'a', 'b'" (给配置到的ab加上了单引号)
要点:
使用函数替换时
1.如果没有任何匹配,则不会执行后面的function
函数。
2.如果匹配多次,函数会依次执行多次,return
的结果替换当前所匹配的值。
3.浏览器会自动编译字符串中的转义符'\',不需额外处理。
4.args
参数,会返回每一个子匹配项(即每一个括号的匹配)、然后是当前匹配文本的第一个字符位置索引、最后一项为文本本身使用'$1-$99'替换时
非常强大
,方便 更多细节请查看MDN
2. 匹配URL字符串,获取每个独立的部分
能让你少装一个插件的正则!
- 将对象转换成
url
参数,一句话搞定(不需要额外引入插件来处理啦
)
// queryObject为所要转换的对象
Object.entries(queryObject).map(([key, value])=>key + '=' + value).join('&')
- 匹配url字符串中的每个部分(
一句正则即可
)
var reg = /^(https?:)\/\/([0-9a-z.]+)(:[0-9]+)?([/0-9a-z.]+)?(\?[0-9a-z&=]+)?(#[0-9-a-z]+)?/i
var stringUrl = 'https://juejin.cn/user/4353721776234743/posts?id=123#test'
let [href, protocol, hostname, port, pathname, search, hash] = reg.exec(stringUrl)
console.log({href, protocol, hostname, port, pathname, search, hash})
// ** 输出结果和location对应的值一致,感兴趣的小伙伴可以copy后在控制台尝试。 **
输出结果和location
对应的值一致,感兴趣的小伙伴可以copy
后在控制台尝试。
3. 简写if判断条件
说到这个用到的人应该就更少了。正确的使用它,可以让我们少写代码,请看:
// 改造前
if(
userName === '迪丽热巴' ||
userName === '赵丽颖' ||
userName === '白百何' ||
userName === '关晓彤' ||
userName === '刘亦菲'
) {
......
}
// 改造后一句话
if(/^迪丽热巴|赵丽颖|白百何|关晓彤|刘亦菲$/.test(userName)){
......
}
需要注意的是:if语句中使用一定要绝对命中,通常需要使用'^'和'$'符号来匹配,另外也可以使用数组的includes等方式来进行唯一匹配
4. 识别浏览器内核及访问终端等
// userAgent的匹配涉及较多,
// 此处仅简单示例展示
function parseUA(userAgent) {
const u = userAgent || navigator.userAgent;
return {
isIOS: /iOS|iPad|iPhone/i.test(u),
isAndroid: /Android/i.test(u),
isMobile: /iOS|iPad|iPhone|Android|windows Phone/i.test(u),
isQQ: /qq/i.test(u), // 容易误匹配
isWeixin: /micromessenger/i.test(u),
isWeibo: /weibo/i.test(u),
isMac: /mac/i.test(u),
isWondows: /Windows NT/i.test(u)
}
}
5. 去除token字符
// 将token或id_token替换为''
url.replace(/[\?|&](id_)?token=[\w|\d|\.|\-|:]+/, '')
其它: /^https?://[^\u4e00-\u9fa5\s]+/.test(value) /^https?://[^\u4e00-\u9fa5\s]+/.test(value)
🍞 正则实例方法(简介、常用)
test
老生常谈的方法,用来查看正则表达式与指定的字符串是否匹配。返回 true
或 false
。
let str = 'hello world!';
let result = /^hello/.test(str);
console.log(result);
// true
match
执行后返回的数据类型:
- 如果有匹配返回数组类型,否则返回null
- 在全局模式匹配:匹配到的每一项,依次显示在数组中。(无数组属性)
- 在非全局模式匹配:数组的第 0 个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本。并且会返回数组属性,其index是匹配到的字符位置,input是字符串的引用。
'abc'.match(/e/)
// null
'abc'.match(/b/)
// ['b', index: 1, input: 'abc', groups: undefined]
'a1b2'.match(/\d/g)
// ['1', '2']
exec
主要应用场景:
1.用于文本检索
2.匹配多表达式时,以数据形式一次性返回(如之前提到的匹配URL字符串)
3.返回更多更全面的匹配相关的信息
匹配模式、结果:
- 如果未找到匹配,则返回值为 null。
- 在非全局匹配模式下,此函数的作用和match函数是一样的(请参考match方法)。
- 在全局模式会相对比较复杂,如下:
// 全局匹配示例
var str = 'ab1ab2'
var reg = /b\d/g
console.log(reg.exec(str))
// 输出:['b1', index: 1, input: 'ab1ab2ab3ab4', groups: undefined]
console.log(reg.lastIndex)
// 输出:3
console.log(reg.exec(str))
// 输出:['b2', index: 4, input: 'ab1ab2ab3', groups: undefined]
console.log(reg.lastIndex)
// 输出:6
console.log(reg.exec(str))
// 输出:null
console.log(reg.lastIndex)
// 输出:0
注:在全局模式下,每次匹配后返回
当前匹配文本的第一个字符的位置
,通过reg.lastIndex
会匹配文本的最后一个字符的下一个位置。因此开始和结束的索引值并不在一个对象上。
在继续匹配时会使用lastIndex
作为新的起点
可以手动设置lastIndex的值设置全局匹配的起点,默认为0
匹配结束后会返回null,再次匹配时会从第一个开始,一直循环匹配(一般通过null来判断是否终止匹配)
- 在匹配子表单式时(可以参考前面提到的匹配URL字符串)
// 简单示例
var str='abc123'
var reg=/c(\d{3})/
console.log(reg.exec(str))
// 输出:['c123', '123', index: 2, input: 'abc123', groups: undefined]
// 其中 'c123' 为完整匹配,'123'为括号内的字表达式
如果有匹配,则数组的第一项为完整的匹配结果,后面依次为每个子表达式的匹配结果。
如果没匹配则返回null
如果有全局匹配,同上面的全局匹配示例,会已数组形式返回每一次匹配到的结果
search
用户检索字符串的位置
可传入需要检索的字符串
var str = 'abcdefg'
var result = str.search('cd')
console.log(result) // 输出:2
可传入正则表达式检索
var str = 'abcdefg'
var result = str.search(/cd/)
console.log(result) // 输出:2
replace/replaceAll
基础的了解可以参考前面讲到的《匹配html字符串中的指定标签》,这个主要分析一下和replaceAll的差异。
表单式为:str.replaceAll(regexp/substr,replacement)
- replaceAll如果用正则检索,必须使用g(全局修饰符),否则会提示错误(
replaceAll called with a non-global RegExp argument
) - replaceAll如果用字符串检索,则会替换所有匹配到的字符串
- 如果其
replacement
为回调函数时,同样也会执行每一次的匹配。但回调中的参数需要注意,请看实例:
1. 无子表单式
var str = 'abcb'
// 使用正则必须加全局修饰符
var result = str.replaceAll(/b/g, function(...args){
console.log(args)
return args[0]
})
// args的值会依次输出
// ['b', 1, 'abcb']
// ['b', 3, 'abcb']
2. 有子表达式
var str = 'ab1cb2'
// 使用正则必须加全局修饰符
var result = str.replaceAll(/b(\d)/g, function(...args){
console.log(args)
return args[0]
})
// args的值会依次输出
// ['b1', '1', 1, 'ab1cb2']
// ['b2', '2', 4, 'ab1cb2']
结论:
- replacement为回调函数时,其参数会根据有无子表达式而不一样
- 无子表达式时,参数依次为(当前匹配结果、当前匹配文本的第一个字符的位置、完整字符)
- 无子表达式时,参数依次为(当前匹配结果、若干匹配的子项结果、当前匹配文本的第一个字符的位置、完整字符)
split
(用于把一个字符串分割成字符串数组。)
表单式为:stringObject.split(separator,howmany)
separator可为字符串或正则
howmany为数字,指定返回的数组的最大长度。
"hello".split("") //可返回 ["h", "e", "l", "l", "o"]
"hello".split("", 3) //可返回 ["h", "e", "l"]
🍞 高级模式
下面的演示中会使用到str.match(reg)
来输出匹配到的字符串,以便更清晰的理解。
注意: 反向[肯定|否定]预查,在ios设备、Safari和IE浏览器中不兼容,将提示语法错误
SyntaxError: Invalid regular expression: invalid group specifier name
; 正向预查没有该问题
正向肯定预查(?=pattern)
var reg = /Windows(?=95|98|NT|2000)/
'Windows95'.match(reg) // 匹配到:['Windows']
'Windows666'.match(reg) // 匹配到:null
reg.test('Windows95') // true
reg.test('Windows2000') // true
reg.test('Windows3.1') // false
只能正向匹配?=后面的字符
正向否定预查
var reg = /Windows(?!95|98|NT|2000)/
'Windows95'.match(reg) // 匹配到:null
'Windows666'.match(reg) // 匹配到:['Windows']
reg.test('Windows95') // false
只能正向匹配非?!后面的字符
反向肯定预查(看示例就明白了)
var reg = /(?<=95|98|NT|2000)Windows/
'95Windows'.match(reg) // 匹配到:['Windows']
'666Windows'.match(reg) // 匹配到:null
reg.test('95Windows') // true
reg.test('66Windows') // false
// 项目示例(在路径拼接中,将连续的两个以上的'/'替换为单斜杠(但排除以'//'开头的))
var reg = /(?<=[\w-_]+)[\/]{2,}/gi
'//localhost:3000//list/'.replace(reg, '/') // 输出 '//localhost:3000/list/'
'content//news//detail//'.replace(reg, '/') // 输出 'content/news/detail/'
反向否定预查(看示例就明白了)
var reg = /(?<!95|98|NT|2000)Windows/
'95Windows'.match(reg) // 匹配到:null
'666Windows'.match(reg) // 匹配到:['Windows']
reg.test('95Windows') // false
reg.test('66Windows') // ture
贪婪模式
在匹配成功的前提下,尽可能多的去匹配
1. 尽可能多的匹配
var str = 'axxxa'
var reg = /a(\w+)/
reg.test(str) // (\w)匹配到了'xxxa',返回true
2. 不得不匹配
var str = 'axxxa'
var reg = /a(\w+)a/
reg.test(str) // (\w)匹配到了'xxx'(让出了最后一个a),返回true
惰性模式(非贪婪模式)
也叫惰性模式,在匹配成功的前提下,尽可能少的去匹配
1. 尽可能少的匹配
var str = 'axxxa'
var reg = /a(\w+?)/
reg.test(str) // 匹配到了'ax',返回true
2. 不得不匹配
var str = 'axxxa'
var reg = /a(\w+?)a/
reg.test(str) // 匹配到了'axxxa',返回true
说明:
- 在量词后加'?',则可以使匹配次数不定的表达式尽可能少的匹配
- 如果是可匹配可不匹配的表达式,则尽可能的 "不匹配"
- 如果少匹配就会导致整个表达式匹配失败的时候,非贪婪模式会最小限度的再匹配一些,以使整个表达式能匹配成功。
非捕获分组
语法:(?:pattern)
说明:在JavaScript的正则表达式中,(?:pattern)
是一个非捕获分组(non-capturing group)。其作用是将pattern
作为一个整体进行分组,但是与捕获分组不同的是,它不会保存对应的子匹配结果。它是ECMAScript标准的一部分,其兼容性非常好。
const regex = /(?:foo)bar/;
const str = "foobar";
const result = str.match(regex);
console.log(result);
// 输出: ["foobar", index: 0, input: "foobar", groups: undefined]
由于foo
是在非捕获分组中,它不会作为一个单独的组返回在结果数组中。
🍞 通用的正则表达式【参考】
中文匹配
let regex = [\u4e00-\u9fa5]
11位手机号码匹配
let regex = /^1[34578]\d{9}$/g;
日期验证 (YYYY-MM-DD)
let regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
身份证匹配
let regex = /^[1-9]\d{7}(?:0\d|10|11|12)(?:0[1-9]|[1-2][\d]|30|31)\d{3}$/
搜索查询更多正则表达式
这里有正则大全,可以搜索到你想要的正则表达式 点击查询更多
🍞 基础使用
必须转义的元字符
( [ { \ ^ $ | ) ? * + . ] }
常用符号
元字符 | 描述 |
---|---|
. | 查找单个字符(除了换行和行结束符) |
\w | 匹配字母、数字、下划线。等价于 [A-Za-z0-9_] |
\W | 匹配一个非单字字符(和\w相反) |
\d | 匹配数字 |
\D | 匹配非数字字符 |
\s | 匹配空白字符 |
\S | 匹配非空白字符 |
\b | 匹配单词边界 |
\B | 匹配非单词边界 |
\0 | 查找 NUL字符[\x00-\x7F] ,如/\0/.test('\x00') // true |
\n | 查找换行符 |
\f | 查找换页符 |
\r | 查找回车符 |
\t | 查找制表符 |
\v | 查找垂直制表符 |
\xxx | 查找以八进制数 xxxx 规定的字符 |
\xdd | 查找以十六进制数 dd 规定的字符 |
\uxxxx | 查找以十六进制 xxxx规定的 Unicode 字符 |
\uhhhh | 匹配一个四位十六进制数表示的 UTF-16 代码单元。 |
量词
量词 | 描述 |
---|---|
^ | 匹配开头,在多行检测中,会匹配一行的开头 |
$ | 匹配结尾,在多行检测中,会匹配一行的结尾 |
* | 匹配前面的子表达式零次或多次。 |
+ | 匹配前面的子表达式一次或多次。 |
? | 匹配前面的子表达式零次或一次。 |
{n} | n 是一个非负整数。匹配确定的 n 次。 |
{n,} | n 是一个非负整数。至少匹配n 次。 |
{n,m} | m 和 n 均为非负整数,最少匹配 n 次且最多匹配 m 次 |
更多基础详情请参考MDN
🍞 其它问题
在开发过程中遇到正则表达式不友好,可能导致页面崩溃的问题,如:
const url = 'https://abc.defghijklml.cn/landing.html?kkPageId=eoMpazq0RY+StP0V4l/+MA==&id=xm3cps-sfxz01'
const httpReg = /^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~_\/])+$/
if(httpReg.test(url)) {
console.log('没进来')
} else {
console.log('还是没进来')
}
如上,导致崩溃的原因:
- 正则表达式不明确。表达式中的“.”是明确表示的域名点,而不是任意字符
- url的链接过长,和不明确的正则匹配时导致,导致CPU严重不足(cpu瞬间100%)
优化处理:
- 修改正则表搜索(加转义符)
- url使用短链接
const httpReg = /^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~_\/])+$/
改为
const httpReg = /^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(([A-Za-z0-9-~]+)\.)+([A-Za-z0-9-~_\/])+$/
🍞 相关链接参考
在线正则表达式图形化工具[校验正则 推荐]
搜索查询更多正则表达式[搜索正则 推荐]
正则表达式手册[其UI一般🤗,但其说明及示例短小精湛,容易快速理解]
MDN详情请参考
正则表达式不要背
作者:
tager
相关文章地址:https://juejin.cn/user/4353721776234743/posts
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。