前言
正则表达式是大多数程序员同学成长道路上的一个坎,迈过去了则能遇见更好的自己,从此编码如有神助, 迈不过去则容易形成死循环,遇到要写正则的场景需要谷歌百度。
其实正则没有那么复杂难懂,难的是我们面对它恐惧的心理得不到克服。
相信我,看完这篇文章,对于正则的位置匹配,你可以收获不一样的风景噢。
搞懂位置能干啥?
题目1:数字的千分位分割法
将123456789转化为123,456,789
题目2:手机号3-4-4分割
将手机号18379836654转化为183-7983-6654
题目3:验证密码的合法性
密码长度是6-12位,由数字、小写字符和大写字母组成,但必须至少包括2种字符
这些题时常出现在面试中,日常业务也少不了它的身影。搞懂位置,不仅能搞定面试,业务你也将写的飞起
啥是位置?
正则表达式是匹配模式,要么匹配字符,要么匹配位置。那什么是位置呢?
如下图箭头所指,位置可以理解为相邻字符之间的位置。
咱们可以和空字符串进行类比, 字符的首尾、间隙都可以用空字符串进行连接。
'hello' === '' + 'h' + '' + 'e' + '' + 'l' + '' + 'l' + '' + 'o' + '' // true
有哪些位置?
正则中常用来表示位置的符号主要有:
^、$、\b、\B、?=p、(?!p)、(?<=p)、(?<!p)
接下来咱们就一个个把他们全整明白。
^
脱字符,匹配行的开头
例如要在hello的开头塞一个笑脸(😄 )怎么搞,这个肯定难不倒你
let string = 'hello'
console.log(string.replace(/^/, '😄')) // 😄hello
$
美元符号,匹配行的结尾
同理想在hello的结尾塞一个笑脸(😄 )呢?
let string = 'hello'
console.log(string.replace(/$/, '😄')) // hello😄
这两个表示首尾位置的符号,相信大家一定都很熟悉。
\b
单词的边界,具体讲有三点规则。
① \w和\W之间的位置
② ^与\w之间的位置
③ \w与$之间的位置
比如藏在你们电脑上学习教程文件夹中的某一集种子长这样xxx_love_study_1.mp4,想要把他变成❤️xxx_love_study_1❤️.❤️mp4❤️怎么搞呢?
其实只需要执行一行代码就行
'xxx_love_study_1.mp4'.replace(/\b/g, '❤️') // ❤️xxx_love_study_1❤️.❤️mp4❤️
画图理解就是
\B
非单词的边界,也就是\b反着来的意思,它的规则如下:
① \w与\w之间的位置
② \W与\W之间的位置
③^与\W之间的位置
④\W与$之间的位置
同样还是用学习教程文件夹中的种子,稍稍改造一下,当执行这行代码之后,会输出啥?
'[[xxx_love_study_1.mp4]]'.replace(/\B/g, '❤️')
....
没错,满满的都是爱啊!!!,都快看不清名字了。
❤️[❤️[x❤️x❤️x❤️_❤️l❤️o❤️v❤️e❤️_❤️s❤️t❤️u❤️d❤️y❤️_❤️1.m❤️p❤️4]❤️]❤️
画图解释如下
(?=p)
符合p子模式前面的那个位置。换句话说是,有一个位置,紧跟其后需要满足p子模式。也有一个学名叫正向先行断言。
还是这个例子xxx_love_study_1.mp4,要在xxx(xxx可以指代任何你喜欢的那个TA)前面塞一个❤️,怎么写呢?
是这样吗? 不是的,这样会导致你的xxx都不见了,那还要❤️做什么呢?
'xxx_love_study_1.mp4'.replace('xxx', '❤️') // ❤️_love_study_1.mp4
利用(?=p)就可以很方便这这件事(可以想想和上面有什么不同?)
'xxx_love_study_1.mp4'.replace(/(?=xxx)/g, '❤️') // ❤️xxx_love_study_1.mp4
画图理解
(?!p)
(?=p)反过来的意思,可以理解为(?=p)匹配到的位置之外的位置都是属于(?!p)的,它也有一个学名叫负向先行断言。
'xxx_love_study_1.mp4'.replace(/(?!xxx)/g, '❤️')
// (?=xxx)的输出
❤️xxx_love_study_1.mp4
// (?!xxx)的输出
x❤️x❤️x❤️_❤️l❤️o❤️v❤️e❤️_❤️s❤️t❤️u❤️d❤️y❤️_❤️1❤️.❤️m❤️p❤️4❤️
仔细对比一下,是不是除了(?=xxx)匹配到最前面那个位置,其他位置都是(?!xxx)匹配到的啦。
(?<=p)
符合p子模式后面(注意(?=p)表示的是前面)的那个位置。换句话说是,有一个位置,其前面的部分需要满足p子模式。
依然是这个例子:我们要在xxx(xxx可以指代任何你喜欢的那个TA)的后面塞一个❤️,怎么写呢?
'xxx_love_study_1.mp4'.replace(/(?<=xxx)/g, '❤️') //xxx❤️_love_study_1.mp4
画图解释
(?<!p)
(?<=p)反过来的意思,可以理解为(?<=p)匹配到的位置之外的位置都是属于(?<!p)的,
'xxx_love_study_1.mp4'.replace(/(?<!xxx)/g, '❤️')
// (?<=xxx)的输出
xxx❤️_love_study_1.mp4
// (?<!xxx)的输出
❤️x❤️x❤️x_❤️l❤️o❤️v❤️e❤️_❤️s❤️t❤️u❤️d❤️y❤️_❤️1❤️.❤️m❤️p❤️4❤️
仔细对比一下,是不是除了(?<=xxx)匹配到后面那个位置,其他位置都是(?<!xxx)匹配到的啦。
真题详解
学习完位置相关的知识,我们来做一下开头的几个题目试试
题目1:数字的千分位分割法
将123456789转化为123,456,789
观察题目的规律就是从后往前,每三个数字前加一个逗号,(需要注意的是开头不需要加逗号,)。是不是很符合
(?=p)的规律呢?p可以表示每三个数字,要添加的逗号所处的位置正好是(?=p)匹配出来的位置。
第一步,尝试先把后面第一个逗号弄出来
let price = '123456789'
let priceReg = /(?=\d{3}$)/
console.log(price.replace(priceReg, ',')) // 123456,789
第二步,把所有的逗号都弄出来
要把所有的逗号都弄出来,主要要解决的问题是怎么表示三个数字一组,也就是3的倍数。我们知道正则中括号可以把一个p模式变成一个小整体,所以利用括号的性质,可以这样写
let price = '123456789'
let priceReg = /(?=(\d{3})+$)/g
console.log(price.replace(priceReg, ',')) // ,123,456,789
第三步,去掉首位的逗号,
上面已经基本上实现需求了,但是还不够,首位会出现,那怎么把首位的逗号去除呢?想想前面是不是有一个知识正好满足这个场景? 没错(?!p),就是他了,两者结合就是从后往前每三个数字的位置前添加逗号,但是这个位置不能是^首位。
let price = '123456789'
let priceReg = /(?!^)(?=(\d{3})+$)/g
console.log(price.replace(priceReg, ',')) // 123,456,789
题目2:手机号3-4-4分割
将手机号18379836654转化为183-7983-6654
有了上面数字的千分位分割法,做这个题相信会简单很多,也就是从后往前找到这样的位置:
每四个数字前的位置,并把这个位置替换为-
let mobile = '18379836654'
let mobileReg = /(?=(\d{4})+$)/g
console.log(mobile.replace(mobileReg, '-')) // 183-7983-6654
题目3:手机号3-4-4分割扩展
将手机号11位以内的数字转化为3-4-4格式
回想一下这样的场景,有一个表单需要收集用户的手机号,用户是一个个数字输入的,我们需要在用户输入11位手机号的过程中把其转化为3-3-4格式。即
123 => 123
1234 => 123-4
12345 => 123-45
123456 => 123-456
1234567 => 123-4567
12345678 => 123-4567-8
123456789 => 123-4567-89
12345678911 => 123-4567-8911
这样用(?=p)就不太合适了,例如1234就会变成-1234。 想想前面的知识点有适合处理这种场景的吗?是的(?<=p)
第一步, 将第一个-弄出来
const formatMobile = (mobile) => {
return String(mobile).replace(/(?<=\d{3})\d+/, '-')
}
console.log(formatMobile(123)) // 123
console.log(formatMobile(1234)) // 123-4
将第二个-弄出来
将第一个-弄出来之后,字符的长度多了一位,原本1234567(这个位置插入-)8,要变成往后移一位
const formatMobile = (mobile) => {
return String(mobile).slice(0,11)
.replace(/(?<=\d{3})\d+/, ($0) => '-' + $0)
.replace(/(?<=[\d-]{8})\d{1,4}/, ($0) => '-' + $0)
}
console.log(formatMobile(123)) // 123
console.log(formatMobile(1234)) // 123-4
console.log(formatMobile(12345)) // 123-45
console.log(formatMobile(123456)) // 123-456
console.log(formatMobile(1234567)) // 123-4567
console.log(formatMobile(12345678)) // 123-4567-8
console.log(formatMobile(123456789)) // 123-4567-89
console.log(formatMobile(12345678911)) // 123-4567-8911
题目4:验证密码的合法性
密码长度是6-12位,由数字、小写字符和大写字母组成,但必须至少包括2种字符
题目由三个条件组成
① 密码长度是6-12位
② 由数字、小写字符和大写字母组成
③ 必须至少包括2种字符
第一步写出条件①和②和正则
let reg = /^[a-zA-Z\d]{6,12}$/
第二步,必须包含某种字符(数字、小写字母、大写字母)
let reg = /(?=.*\d)/
// 这个正则的意思是,匹配的是一个位置,这个位置需要满足`任意数量的符号,紧跟着是个数字`,注意它最终得到的是个位置,而不是数字或者是数字前面有任意的东西
console.log(reg.test('hello')) // false
console.log(reg.test('hello1')) // true
console.log(reg.test('hel2lo')) // true
// 其他类型同理
第三步,写出完整的正则
必须包含两种字符,有下面四种排列组合方式
① 数字和小写字母组合
② 数字和大写字母组合
③ 小写字母与大写字母组合
④ 数字、小写字母、大写字母一起组合(但其实前面三种已经覆盖了第四种了)
// 表示条件①和②
// let reg = /((?=.*\d)((?=.*[a-z])|(?=.*[A-Z])))/
// 表示条件条件③
// let reg = /(?=.*[a-z])(?=.*[A-Z])/
// 表示条件①②③
// let reg = /((?=.*\d)((?=.*[a-z])|(?=.*[A-Z])))|(?=.*[a-z])(?=.*[A-Z])/
// 表示题目所有条件
let reg = /((?=.*\d)((?=.*[a-z])|(?=.*[A-Z])))|(?=.*[a-z])(?=.*[A-Z])^[a-zA-Z\d]{6,12}$/
console.log(reg.test('123456')) // false
console.log(reg.test('aaaaaa')) // false
console.log(reg.test('AAAAAAA')) // false
console.log(reg.test('1a1a1a')) // true
console.log(reg.test('1A1A1A')) // true
console.log(reg.test('aAaAaA')) // true
console.log(reg.test('1aA1aA1aA')) // true
相约再见
以上就是我关于正则“位置”理解的全部内容,有可能不是很准确,欢迎指出,一起学习成长。