正则表达式 这篇就够用了!【纯干货、查缺补漏】

6,078 阅读12分钟

前言

“ 醉过才知酒浓 🍷 ,爱过才知情重。❤️ ”

对于正则既爱又“恨”,爱是通过几句简单的code就能实现强大的功能,“恨”是难以真正的掌控它。

每次需要用到复杂点的正则表达式我们都要通过搜索引擎查询,而且每次查出来的结果还有出入,比较费时费力,因而对此做了一番整理。如果以后需要用到正则直接来这里就能找到,是不是会很爽。

🍔 阅读本文,你可以收获到以下知识 🍔:

  • 了解如何校验自己写的正则 是否正确(减少或避免潜在bug)!
  • 能快速搜索到自己所需的正则!
  • 能掌握几个让你少装插件的正则
  • 能全面了解正则实例方法高级模式基础用法查缺补漏
  • 对应的模块提供了对用的链接,能快速了解到更详细的用法 文章略长,建议先收藏 ⭐️(Ctrl + D 或 command + D)⭐️

先来些干货,再讲基础

🍞 检验你的正则是否正确!

先给大家介绍在线正则表达式图形化工具,利用此工具,你的正则能匹配到哪些字符 就能一目了然
下面是手机号/^1[34578]\d{9}$/正则的展示点击尝试

valid3.gif


🍞 搜索你所需要的正则!

这个搜索工具既支持网页搜索,也可在vscode上直接搜索。大部分日常使用的正则都可以在此搜索到。

valid_1.gif


🍞 能让你少装插件的正则!

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

用户检索字符串的位置

  1. 可传入需要检索的字符串

var str = 'abcdefg'
var result = str.search('cd')
console.log(result) // 输出:2

  1. 可传入正则表达式检索

var str = 'abcdefg'
var result = str.search(/cd/)
console.log(result) // 输出:2

replace/replaceAll

基础的了解可以参考前面讲到的《匹配html字符串中的指定标签》,这个主要分析一下和replaceAll的差异。
表单式为:str.replaceAll(regexp/substr,replacement)

  1. replaceAll如果用正则检索,必须使用g(全局修饰符),否则会提示错误(replaceAll called with a non-global RegExp argument)
  2. replaceAll如果用字符串检索,则会替换所有匹配到的字符串
  3. 如果其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"]

正则实例方法更多细节请查看 MDN


🍞 高级模式

下面的演示中会使用到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

说明:

  1. 在量词后加'?',则可以使匹配次数不定的表达式尽可能少的匹配
  2. 如果是可匹配可不匹配的表达式,则尽可能的 "不匹配"
  3. 如果少匹配就会导致整个表达式匹配失败的时候,非贪婪模式会最小限度的再匹配一些,以使整个表达式能匹配成功。

非捕获分组

语法:(?: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('还是没进来')
}

如上,导致崩溃的原因:

  1. 正则表达式不明确。表达式中的“.”是明确表示的域名点,而不是任意字符
  2. url的链接过长,和不明确的正则匹配时导致,导致CPU严重不足(cpu瞬间100%)

image.png
优化处理:

  1. 修改正则表搜索(加转义符)
  2. 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
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。