无法避免的正则表达式

前言

什么是正则表达式?

我们在日常开发中总是能遇到一些需要校验的功能,例如校验邮箱、手机号等等,或者对一些字符串做替换处理等,正则表达式总是避不开的。个人理解正则就是用来匹配字符,校验字符规则,或者是提取一段字符中的内容。本文只介绍正则的一些基本使用。

匹配字符

匹配模糊长度

js 复制代码
const regexp = /ab{1,5}c/g  
const str = "abc abbc abbbc abbbbbbbc"  
console.log(str.match(regexp)) // [ 'abc', 'abbc', 'abbbc' ]  

通过 {m,n} 的形式来设置匹配的长度范围,以上代码匹配的就是 b 出现 1 - 5 次。而且 {m,n}中,可以省略,代表匹配出现至少 m 次,或者只写 m,{m} 代表匹配只出现 m 次。

匹配模糊字符

js 复制代码
const regexp = /a[bcde]a/g  
const str = "aba aca ada aaa"  
console.log(str.match(regexp)) // [ 'aba', 'aca', 'ada' ]  

通过 [] 的形式来设置可能出现的字符,以上代码匹配的就是 bcde 中出现任意一个。

排除某些字符用 ^

js 复制代码
const regexp = /a[^bcde]c/g  
const str = "abc acc adc aac"  
console.log(str.match(regexp)) // [ 'aac' ]  

通过 ^ 来设置不匹配哪些字符,以上代码匹配的就是 除了 bcde 字符外任意字符

多分支匹配

js 复制代码
const regexp = /a|b|cd|efg/g  
const str = "adbdcdefg"  
console.log(str.match(regexp)) // [ 'a', 'b', 'cd', 'efg' ]  

多分支匹配通过 | 实现,意思是匹配其中任意一个即可,和 JS 中的 类似,另外需要注意当前面的分支匹配上后就不会继续进行匹配了。

匹配位置

锚字符

  • ^ 匹配开头
  • $ 匹配结尾
  • \b 单词字符和非单词字符之间,也就是 \w 和 \W 之间
  • \B 把 \b 取反
  • (?=p) p 前的位置
  • (?=p) 除了p 前的位置

开头结尾 ^ $

js 复制代码
const regexp = /^ab$/g  
var string = "ab";  
console.log(string.match(regexp)) // [ 'ab' ]  

以上代码就是匹配以 a 开头,b 结尾的字符串,另外字符串有一个方法 replace 可以通过匹配位置进行替换。

js 复制代码
var string = "abdefg";  
console.log(string.replace(/^|$/g, '#')) // #abdefg#  

通过匹配字符串的开头和结尾位置,替换成 #。

js 复制代码
var string = "a\nb\nd\ne\nf\ng";  
console.log(string.replace(/^|$/gm, '#'))  
/*  
#a#  
#b#  
#d#  
#e#  
#f#  
#g#  
*/  

需要注意的是,在多行模式匹配下,开头和结尾是每行的开头结尾。

单词边界 \b \B

js 复制代码
var string = "Hello,[JS] Hello World";  
console.log(string.replace(/\b/g, '#')) // #Hello#,[#JS#] #Hello# #World#  

单词边界就是 \w 和 \W 之间,也就是所有数字、字符串以及下划线 和 其他字符之间,也就是下图箭头所指的位置。注意看箭头两边的字符分别是\w 和 \W,另外要注意的是开头和结尾也是 \W。

那 \B 就很好理解了,就是除了 \w 所指位置的其他位置。

先行断言

(?=p)(正向先行断言)指 p 前面的位置,这个 p 就是一个子模式,先用 p 进行匹配,然后再取他前面的位置

js 复制代码
const string = "Hello,[JS] Hello World";  
console.log(string.replace(/(?=\w)/g, '#')) // #H#e#l#l#o,[#J#S] #H#e#l#l#o #W#o#r#l#d  

(?!p)(负向先行断言)就是和 (?=p) 相反,就是先匹配非 p 然后取前面的位置

js 复制代码
console.log(string.replace(/(?!\w)/g, '#')) // Hello#,#[JS#]# Hello# World#  

(?<=p) 指 p 后面的位置

js 复制代码
console.log(string.replace(/(?<=\w)/g, '#')) // H#e#l#l#o#,[J#S#] H#e#l#l#o# W#o#r#l#d#  

(?>!p)(?<=p) 相反

js 复制代码
console.log(string.replace(/(?<!\w)/g, '#')) // #Hello,#[#JS]# #Hello #World  

实践

给金额加上千分位逗号 例如 123456789 -> 123,456,789

解题思路:先选中倒数第三位前的位置替换成逗号,然后出现多次,最后排除开头的位置。

我们要做的就是选中图中箭头的位置

第一步

js 复制代码
const string = "123456789";  
const regexp = /(?=\d{3}$)/g  
console.log(string.replace(regexp, ",")); // 123456,789  

上面的正则匹配的就是以三个数字结尾前的位置,然后替换成逗号。
第二步

js 复制代码
const regexp = /(?=(\d{3})+$)/g  
console.log(string.replace(regexp, ",")); // ,123,456,789  

通过 + 让三个数字匹配多次,达到匹配上每三位数字前的位置,然后替换成逗号,这时发现开头的位置多了一个逗号,我们需要排除掉开始的位置
第三步

js 复制代码
const regexp = /(?!^)(?=(\d{3})+$)/g  
console.log(string.replace(regexp, ",")); // 123,456,789  

通过(?!^)匹配除了开始位置的其他位置,这种方法就排除掉了开头,达到了预期的效果。

影响量词的匹配行为

贪婪匹配

正则在进行量词匹配时,会尽可能地多匹配。

js 复制代码
const regexp = /a{1,3}/g  
const str = "aaa aaaa aaaaa"  
console.log(str.match(regexp)) // [ 'aaa', 'aaa', 'a', 'aaa', 'aa' ]  

惰性匹配(非贪婪匹配)

我们使用 ? 来声明惰性匹配,惰性匹配每次只满足最低要求即可。

js 复制代码
const regexp = /a{1,3}?/g  
const str = "aaa"  
console.log(str.match(regexp)) // [ 'a', 'a', 'a' ]  

分组

我们可以通过()来对正则表达式进行分组,如

js 复制代码
const regexp = /(abc)+/g  
const str = "abcabc abccc"  
console.log(str.match(regexp)) // [ 'abcabc', 'abc' ]  

abc 括起来使他们成为一个分组,+的就是这个分组。

提取分组的数据

我们对正则进行分组匹配后还可以拿到分组匹配到的数据,

js 复制代码
const regexp = /(\d+)([a-z]+)/  
const str = "1231abc"  
console.log(str.match(regexp))  
console.log(regexp.exec(str))  

两个输出结果相同,输出结果:

需要注意的是,在捕获分组的时候,使用全部匹配 /g 就不返回分组信息了。

js 复制代码
const regexp = /(\d+)([a-z]+)/g  
const str = "1231abc"  
console.log(str.match(regexp)) // [ '1231abc' ]  

反向引用

在正则表达式中,如果有一个不确定的字符,后面还需要用到这个相同的字符,就可以用反向引用的方式

js 复制代码
const regexp = /\d{3}([,\-])\d{3}\1\d{3}/  
const str = "123,456,789";  
const str2 = "123-456-789";  
const str3 = "123-456,789";  
  
console.log(regexp.test(str)) // true  
console.log(regexp.test(str2)) // true  
console.log(regexp.test(str3)) // false  

这里我们用 \1 来引用第一个分组的字符,所以当前面是 , 后面也只能是 ,,这样就能做到前后一致。

另外如果出现嵌套括号的情况,我们以左括号出现的顺序为引用的顺序

js 复制代码
const regexp = /^((\d)(\d(\d)))\1\2\3\4$/;  
/*  
* \1 => ((\d)(\d(\d)))  
* \2 => (\d)  
* \3 => (\d(\d))  
* \4 => (\d)  
* */  

如果不希望分组被提取引用,使用非捕获分组 (?:p),我们把上面的代码修改一下看结果。

js 复制代码
const regexp = /(\d+)([a-z]+)/  
const str = "1231abc"  
console.log(str.match(regexp))  

输出结果:

附录

详细查阅: 正则表达式

匹配数量简写

  • {m,} 至少出现 m 次数
  • {m} 只出现 m 次数
  • ? 不出现或者出现一次 等价于 {0,1}
    • 至少出现一次
  • * 有没有都行

匹配范围简写

  • \d 表示数字
  • \D 除了数字外任意字符
  • \w 数字+字母+下划线
  • \W 除了 \w
  • \s 空白字符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符
  • \S 非空白符
  • . 任意字符

修饰符

  • i 不区分大小写
  • g 全局匹配
  • m 多行匹配
  • s 允许 . 匹配换行符。
  • u 使用 unicode 码的模式进行匹配。
  • y 粘性 匹配从目标字符串的当前位置开始。
相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom10 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试