️ 正则钥匙:开启高效编程之门

前言

📚 日常开发过程中,有哪些地方用到了正则?
🔍 你是否经常根据需求在网上搜索正则表达式,拷贝后发现不适用,然后不得不重复搜索?
🚀 协作开发的过程中,碰到别人写的正则,你是不是看的一头雾水,不知道他写的是什么意思?
🎯 别担心,本文将带你从零开始,一步一步地学习,让你爱上正则表达式。


正式学习之前,先贴两个实用的网站,一个是正则在线调试,一个是正则闯关,可以帮助你更好的学习正则表达式。

本文会涉及一些小题目,答案会公布在评论区里,大伙可以先自行思考,再去评论区里找答案。

入门

基础使用

单词匹配,直接将单词写在正则中即可。

  • 题目1:匹配出 I love javascript 中的 javascript

修饰符

我们经常看到的一些正则后带了字母,如:/abc/ig,其中ig就是修饰符的意思。

修饰符含义描述
gglobal - 全局匹配查找所有的匹配项。
iignore - 不区分大小写将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。
mmulti line - 多行匹配使边界字符 ^$ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。
sdotall - 特殊字符圆点 . 中包含换行符 \默认情况下的圆点 .是匹配除换行符 \n之外的任何字符,加上 s 修饰符之后, . 中包含换行符 \n
uunicode - 开启完整的Unicode支持不常用,可以参考:https://zh.javascript.info/regexp-unicode
ysticky - 粘性只会从lastIndex位置开始匹配,且如果设置了全局标识(g)的话会被忽略。不常用,可以参考:https://zh.javascript.info/regexp-sticky

字符组

在使用正则时,有时需要在一个地方匹配多个字符,这时就需要使用字符组。

字符组含义描述
[ABC]字符匹配匹配 […] 中所有的字符,可以为数字、字母、特殊字符及Unicode编码
[^ABC]取反匹配匹配除了 […] 中指定字符以外的所有字符
[a-z]区间匹配匹配 a-z 区间的字符,同理还有 A-Z 和 0-9
  • 题目2:匹配出 I love javascript 中的 av
  • 题目3:匹配出 I love javascript 中除了 av 的字符
  • 题目4:匹配出 I Love Javascript0123456789 中所有的大写字母和数字

快捷方式

对于很多常用的字符组,正则表达式提供了一些快捷方式,可以减少我们的输入量。

快捷方式描述
\w匹配字母、数字、下划线。等价于 [A-Za-z0-9_]
\W匹配非字母、数字、下划线。等价于 [^A-Za-z0-9_]
\d匹配任意一个阿拉伯数字(0 到 9)。等价于 [0-9]
\D匹配任意一个非阿拉伯数字。等价于 [^0-9]
\s匹配所有空白符,包括换行,等价于 [ \f\n\r\t\v]
\S匹配所有非空白符
.匹配所有字符,除换行符(修饰符s打开后也包括换行符)
\b匹配单词边界
\B匹配非单词边界
\f匹配一个换页符。等价于 \x0c\cL
\n匹配一个换行符。等价于 \x0a\cJ
\r匹配一个回车符。等价于 \x0d\cM
\t匹配一个制表符。等价于 \x09\cI
\v匹配一个垂直制表符。等价于 \x0b\cK

匹配元字符本身(转义符)

如果我们要匹配元字符本身,比如 {} 或者 \w 这种字符串,那么就要在元字符之前再加一个 \。例如:\n 匹配一个换行符。序列 \\ 匹配 \\( 则匹配 (

开始和结束

字符描述
^在正则开始时使用,匹配输入字符串的结束位置,如果设置了RegExp 对象的 Multiline 属性,^ 也匹配 \n\r 之后的位置。(/^a/ 表示以a为开头)
$匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配任何新行的开始位置。
  • 题目5:匹配网址(low版):以http://为开头,以.com为结尾,快用正则匹配以下用例吧:
// 需要匹配的
http://www.baidu.com
http://regexr-cn.com
// 不能匹配的
http://www
dshadsa://dsads--dsadsa

量词

如果我们想进行多次匹配,那么就需要用到我们的量词。

量词描述
{n}n 是一个非负整数。匹配确定的 n 次。例如,‘o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n,}n 是一个非负整数。至少匹配n 次。例如,‘o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。
{n,m}m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,“o{1,3}” 将匹配 “fooooood” 中的前三个 o。请注意在逗号和两个数之间不能有空格。
*匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。
+匹配前面的子表达式一次或多次。例如,‘zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。
?匹配前面的子表达式零次或一次。例如,“do(es)?” 可以匹配 “do” 或 “does” 。? 等价于 {0,1}。
  • 题目6:匹配手机号:
    • 必须是11位数字
    • 第一位数字必须是1
    • 第二位是3、4、5、7、8
    • 后面9位是0-9的数字
// 需要匹配的
13811113646
15699995750
// 不能匹配的
29711001111
30711001111

贪婪or非贪婪

  • 贪婪匹配:尽可能多的匹配字符,正则中默认匹配就是贪婪匹配。
  • 非贪婪匹配:尽可能少的匹配字符,在量词 (*, +, ?, {n}, {n,}, {n,m})后加一个?,开启非贪婪模式。例如,对于字符串 “oooo”,‘o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。

进阶

分组

()中的内容,代表一个分组,表示一个小整体,也可以将该分组匹配出来的内容提取出来进行使用。

分组-或

有时我们需要对多个词组进行匹配,这时就需要使用分组或(xxx|yyy)

  • 题目7:匹配音频格式的文件(以.jpg .jpeg .gif .png .svg结尾的文件)
// 需要匹配的
123a.jpg
a123.jpeg
dsadsaa.svg
fsaa.png
adas.gif
// 不能匹配的
adas.gif123
a123.mp4
dsaa.rmvb

分组-提取

()中的内容将会被提取出来,在后方可以使用\1\2\3…来引用第几个分组匹配到的内容

  • 题目8:匹配带HTML双标签的字符串
// 需要匹配的
<div>hello</div>
32132<span>world</span>12323
// 不能匹配的
dsahdsa
<div>world</p>
  • 题目9:匹配长度6以内的回文字符串
// 需要匹配的
aa
abba
abccba
abcba
// 不能匹配的
ababab
abxyzba

分组-不提取

有时,我们只想使用()对表达式进行整体的分隔,但并不想让其纳入分组提取的编排中,就可以使用(?:partten)。比如正则中的reg.exec方法(后面会讲),会将分组提取出来,但是我们并不想要这个分组,这时就可以使用(?:partten)

  • 只提取年月日中的年份:(\d{4})-(?:\d{2})-(?:\d{2})

断言

正则表达式中的零宽断言是一种特殊的结构,它在匹配的时候不会消耗字符,只是对匹配位置进行条件判断。这对于一些复杂的模式匹配非常有用,因为它允许你在匹配位置前面或后面添加条件,从而更精确地控制匹配。

正则表达式的先行断言和后行断言一共有 4 种形式:

  • (?=pattern) 零宽正向先行断言(zero-width positive lookahead assertion)
  • (?!pattern) 零宽负向先行断言(zero-width negative lookahead assertion)
  • (?<=pattern) 零宽正向后行断言(zero-width positive lookbehind assertion)
  • (?<!pattern) 零宽负向后行断言(zero-width negative lookbehind assertion)

这里面的 pattern 是一个正则表达式。

如同 ^ 代表开头,$ 代表结尾,\b 代表单词边界一样,先行断言和后行断言也有类似的作用,它们只匹配某些位置,在匹配过程中,不占用字符,所以被称为"零宽"。所谓位置,是指字符串中(每行)第一个字符的左边、最后一个字符的右边以及相邻字符的中间(假设文字方向是头左尾右)。

正向先行断言

  • 题目10:匹配出“我喜欢你 我喜欢 我喜欢我 喜欢 喜欢你”中所有喜欢后面有“你”的“喜欢”
  • 题目11:使用正则校验密码强度,要求如下:
    • 至少一个大写字母
    • 至少一个小写字母
    • 至少一个数字
    • 至少8个字符
// 需要匹配的
wdfqe#wefDdf444
Codejiaonang123
CodeJiaonang@qq1
111111abc11ABc
CodeJiaonang123
// 不能匹配的
qwe
8848
123456
asd123
Adm123

负向先行断言

  • 题目12:匹配出“我喜欢你 我喜欢 我喜欢我 喜欢 喜欢你”中所有喜欢后面没有“你”的“喜欢”

正向后行断言

  • 题目13:匹配出“我喜欢你 我喜欢 我喜欢我 喜欢 喜欢你”中所有喜欢前面有“我”的“喜欢”

负向后行断言

  • 题目14:匹配出“我喜欢你 我喜欢 我喜欢我 喜欢 喜欢你”中所有喜欢前面没有“我”的“喜欢”

小测验-匹配网址(完善版)

https://xwv5.aidoutang.com/dwpush/#/bury/board-detail?id=1

url组成部分是否必须规则
scheme以http://或者https://开头,当然也可以加上ftp等协议
host域名部分由无数个数字、字母、下划线或中划线组成,且最少有一个".“在中间,且”."的前后必须有字符
port":"和至少一位数字组成
path以"/“开头,后面由数字、字母、”_“、”-“、”.“、”/“、”%"组成,当然,后面的内容也可以没有,直接为/
hash以"#/“开头,后面由数字、字母、”_“、”-“、”.“、”/“、”%"组成,当然,后面的内容也可以没有,直接为#/
query以"?“开头,后面由数字、字母、”_“、”-“、”.“、”/“、”%“、”=" 组成,当然,后面的内容也可以没有,直接为?

匹配示例:

http://www.xiwang.com
http://www.xiwang.com/
ftp://ftp.com/123
http://iii.xiwang.com:8080
http://iii.xiwang.com:8080/touch-renewal-center
http://iii.xiwang.com:8080/touch-renewal-center.html
http://iii.xiwang.com:8080/touch-renewal-center?app_blid=30
https://xwv5.aidoutang.com/dwpush#/bury/board-detail
https://xwv5.aidoutang.com/dwpush/#/bury/board-detail?id=1

JS 中的正则

我们学会了正则的怎么写,那么我们又在平时的开发过程中怎么使用呢?

RegExp对象

创建

js中,创建正则有两种方式:

const reg1 =new RegExp('(\\d{4})-(\\d{2})-(\\d{2})','g') // 构造函数实例化方式
const reg2 = /(\d{4})-(\d{2})-(\d{2})/g // 字面量方式创建
  1. 如果采用正则对象方式,RegExg接收的是字符串,\反斜杠是转义字符,\d会变成d,此时需要使用两个反斜杠,即\\d来达到\d效果;但是在字面量方式中,不是字符串,所以使用一个反斜杠即可。
  2. 正则对象方式,可以接收参数,而正则字面量方式不可以。
var regx = new RegExp("^[a-zA-Z0-9]{"+param1+","+param2+"}$","gim");

RegExp.prototype.test()

使用正则匹配字符串,来判断字符串是否满足该正则。

  • 入参:需校验的字符串
  • 返回:匹配到返回true,否则返回false
let reg = new RegExp('(\\d{4})-(\\d{2})-(\\d{2})','g')
console.log(reg.test('2015-12-25'))  // true
console.log(reg.test('2015/12/25'))  // false

RegExp.prototype.exec()

使用正则执行字符串,返回执行结果,能够拿到具体的匹配单元。

  • 入参:需执行的字符串
  • 返回<array|null>,匹配到返回伪数组:{0: matchStr【, 1: group1, 2: group2…】, index: 0, input: ‘’,groups: {…} },匹配不到返回null
参数名称类型含义
0stringmatchStr,匹配到的字符串
1~Nstringgroup1~groupN,正则中设置的捕获分组匹配到的内容,从第一个分组开始依次往后排开
indexnumber匹配到的字符串的首字符在整个字符串中的位置
inputstring参数传入的字符串整体
groupsobject当正则中设置了具名捕获分组:(?\d{4}),那么该分组捕获的内容将会进入到groups中

非全局匹配模式中,每一次匹配都会从字符串的开头开始计算(每次匹配完成后lastIndex都会重置为0)
全局匹配模式中:每一次匹配的都会从上一次匹配完成后的位置开始(从上一次匹配完成后的lastIndex开始)

let str = `1996-09-06 ~ 2024/07/23`;
// 非全局
let reg = /(\d{4})[-/ ](\d{2})[-/ ](\d{2})/;
console.log(reg.lastIndex) // 0
console.log(reg.exec(str));
// ['1996-09-06', '1996', '09', '06', index: 0, input: '1996-09-06 ~ 2024/07/23', groups: undefined]
console.log(reg.lastIndex) // 0
console.log(reg.exec(str));
// ['1996-09-06', '1996', '09', '06', index: 0, input: '1996-09-06 ~ 2024/07/23', groups: undefined]
console.log(reg.lastIndex) // 0
console.log(reg.exec(str));
// ['1996-09-06', '1996', '09', '06', index: 0, input: '1996-09-06 ~ 2024/07/23', groups: undefined]

// 全局
let reg1 = /(\d{4})[-/ ](\d{2})[-/ ](\d{2})/g;
console.log(reg1.lastIndex) // 0
console.log(reg1.exec(str));
// ['1996-09-06', '1996', '09', '06', index: 0, input: '1996-09-06 ~ 2024/07/23', groups: undefined]
console.log(reg1.lastIndex) // 10
console.log(reg1.exec(str));
// ['2024/07/23', '2024', '07', '23', index: 13, input: '1996-09-06 ~ 2024/07/23', groups: undefined]
console.log(reg1.lastIndex) // 23
console.log(reg1.exec(str));
// null

// 带有具名捕获分组的正则
let reg2 = /(?<year>\d{4})[-/ ](?<month>\d{2})[-/ ](?<day>\d{2})/g;
console.log(reg2.exec(str));
// ['1996-09-06', '1996', '09', '06', index: 0, input: '1996-09-06 ~ 2024/07/23', groups: {year: '1996', month: '09', day: '06'}]
console.log(reg2.exec(str));
// ['2024/07/23', '2024', '07', '23', index: 13, input: '1996-09-06 ~ 2024/07/23', groups: {year: '2024', month: '07', day: '23'}]
console.log(reg2.exec(str));
// null

String中的正则

String.prototype.search(reg)

search方法用于检索满足正则条件的字符串在源字符串中的的位置。

  • 入参<string|reg>:查找条件,可以是字符串,也可以是正则,传入字符串将会被转化为正则来处理。
  • 返回:方法返回第一个匹配结果 index,查找不到返回 -1

search() 方法不执行全局匹配,它将忽略标志 g,并且总是从字符串的开始进行检索

let str = `1996-09-06 ~ 2024/07/23`;
let reg = /(\d{4})[-/ ](\d{2})[-/ ](\d{2})/g;
console.log(str.search(reg)); // 0
console.log(str.search(reg)); // 0

String.prototype.match(reg)

用于匹配并获取字符串中满足正则的子串

  • 入参<string|reg>:查找条件,可以是字符串,也可以是正则,传入字符串将会被转化为正则来处理;
  • 返回<array|null>:匹配到返回数组,否则返回null,且正则是否有标志g对结果影响很大
    • 非全局模式下,只返回第一个匹配的内容,结果类似reg.exec()方法的返回。
    • 全局模式下,将所有匹配到的字符串组成为一个数组返回,不会返回每个匹配的groups和index等信息。
let str = `1996-09-06 ~ 2024/07/23`;

// 非全局
let reg = /(\d{4})[-/ ](\d{2})[-/ ](\d{2})/;
console.log(str.match(reg));
// ['1996-09-06', '1996', '09', '06', index: 0, input: '1996-09-06 ~ 2024/07/23', groups: undefined]

// 全局
let reg1 = /(\d{4})[-/ ](\d{2})[-/ ](\d{2})/g;
console.log(str.match(reg1));
// ['1996-09-06', '2024/07/23']

String.prototype.matchAll(reg)

用于匹配并获取字符串中所有满足正则的子串。

  • 入参<string|reg>:查找条件,可以是字符串,也可以是正则,传入字符串将会被转化为正则来处理;
  • 返回:返回一个正则匹配结果的迭代器,可以使用for…of…遍历,或者使用Array.from()和扩展符(…)将其转为数组。其中每一项类似reg.exec()返回的结果
    注意:string.matchAll(reg)中的正则,必须是全局模式的,否则会报错。
let str = `1996-09-06 ~ 2024/07/23`;
let reg = /(\d{4})[-/ ](\d{2})[-/ ](\d{2})/g;
console.log(Array.from(str.matchAll(reg)));
[
  ['1996-09-06', '1996', '09', '06', index: 0, input: '1996-09-06 ~ 2024/07/23', groups: undefined],
  ['2024/07/23', '2024', '07', '23', index: 13, input: '1996-09-06 ~ 2024/07/23', groups: undefined]
]

String.prototype.split(reg)

以传入的正则将字符串拆分为数组。

  • 入参<string|reg>:查找条件,可以是字符串,也可以是正则,传入字符串将会被转化为正则来处理
  • 返回:返回分隔后的内容,如果分隔符是包含捕获括号的正则表达式,则每次分隔符匹配时,捕获括号的结果(包括任何未定义的结果)将被拼接到输出数组中。
let str = `1996-09-06 ~ 2024/07/23`;

let reg = /\d{4}[-/ ]\d{2}[-/ ]\d{2}/;
console.log(str.split(reg));
// [ '', ' ~ ', '' ]

let reg1 = /(\d{4}[-/ ]\d{2}[-/ ]\d{2})/;
console.log(str.split(reg1));
// [ '', '1996-09-06', ' ~ ', '2024/07/23', '' ]

String.prototype.replace()

根据正则匹配到字符串中的内容,进行替换

  • String.prototype.replace(reg|str,replaceStr)
  • String.prototype.replace(reg,function)
    • function 会在每次匹配替换的时候调用,其参数类似于reg.exec()的返回结果
  • 返回:经过替换后的字符串
let str = `1996-09-06 ~ 2024/07/23`;
let reg = /(?<year>\d{4})[-/ ](?<month>\d{2})[-/ ](\d{2})/g;
console.log(str.replace(reg, '日期'));
// 日期 ~ 日期
console.log(str.replace(reg, function(match,group1,group2,group3,index,origin, groups) {
    console.log('匹配到的内容:', match, group1, group2, group3, index, origin, groups);
    // 匹配到的内容: 1996-09-06 1996 09 06 0 1996-09-06 ~ 2024/07/23 {year: '1996', month: '09'}
    // 匹配到的内容: 2024/07/23 2024 07 23 13 1996-09-06 ~ 2024/07/23 {year: '2024', month: '07'}
    return `${groups.year}${groups.month}${group3}`;
}));
// 1996年09月06日 ~ 2024年07月23日
  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值