讲个小笑话
昨天下完班,良久才回到家,就因为公司楼下的停车场设计的和迷宫一样,每次都要找良久,能力发现,我没有车
o(╥﹏╥)o。
欢送关注”前端胖头鱼“公众号,兴许你为素未谋面,但很可能相见恨晚噢。
前言
已经我一度对正则表达式有种恐怖和厌恶感,为啥?因为总感觉这玩意很难,很干燥,看到他人写出贼牛逼的正则
,我想啥时候我能像他们一样优良。直到我看到了这三个知识点。。。
只须要花10分钟工夫,你能够播种
- 正则表达式中的地位匹配原理与常识
- 正则表达式中的字符串匹配原理与常识
- 正则表达式中的括号的妙用
- 14个常见正则表达式解析帮忙了解知识点
置信我,看完这篇文章,对于工作中90%以上的正则问题你都能找到解决思路和计划。
置信我,看完这篇文章,对于工作中90%以上的正则问题你都能找到解决思路和计划。
置信我,看完这篇文章,对于工作中90%以上的正则问题你都能找到解决思路和计划。
默念三声
正则表达式是匹配模式
,要么匹配字符
,要么匹配地位
正则表达式是匹配模式
,要么匹配字符
,要么匹配地位
正则表达式是匹配模式
,要么匹配字符
,要么匹配地位
1.搞懂地位无能啥?
题目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})+$)/gconsole.log(price.replace(priceReg, ',')) // ,123,456,789
第三步,去掉首位的逗号,
下面曾经基本上实现需求了,然而还不够,首位会呈现,那怎么把首位的逗号去除呢?想想后面是不是有一个常识正好满足这个场景? 没错(?!p),就是他了,两者联合就是从后往前每三个数字的地位前增加逗号,然而这个地位不能是^首位。
let price = '123456789'let priceReg = /(?!^)(?=(\d{3})+$)/gconsole.log(price.replace(priceReg, ',')) // 123,456,789
题目2:手机号3-4-4宰割
将手机号18379836654转化为183-7983-6654
有了下面数字的千分位宰割法,做这个题置信会简略很多,也就是从后往前找到这样的地位:
每四个数字前的地位,并把这个地位替换为-
let mobile = '18379836654'let mobileReg = /(?=(\d{4})+$)/gconsole.log(mobile.replace(mobileReg, '-')) // 183-7983-6654
题目3:手机号3-4-4宰割扩大
将手机号11位以内的数字转化为3-4-4格局
回忆一下这样的场景,有一个表单须要收集用户的手机号,用户是一个个数字输出的,咱们须要在用户输出11位手机号的过程中把其转化为3-3-4格局。即
123 => 1231234 => 123-412345 => 123-45123456 => 123-4561234567 => 123-456712345678 => 123-4567-8123456789 => 123-4567-8912345678911 => 123-4567-8911
这样用(?=p)就不太适合了,例如1234就会变成-1234。
想想后面的知识点有适宜解决这种场景的吗?是的(?<=p)
第一步, 将第一个-弄出来
const formatMobile = (mobile) => { return String(mobile).replace(/(?<=\d{3})\d+/, '-') }console.log(formatMobile(123)) // 123console.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)) // 123console.log(formatMobile(1234)) // 123-4console.log(formatMobile(12345)) // 123-45console.log(formatMobile(123456)) // 123-456console.log(formatMobile(1234567)) // 123-4567console.log(formatMobile(12345678)) // 123-4567-8console.log(formatMobile(123456789)) // 123-4567-89console.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')) // falseconsole.log(reg.test('hello1')) // trueconsole.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')) // falseconsole.log(reg.test('aaaaaa')) // falseconsole.log(reg.test('AAAAAAA')) // falseconsole.log(reg.test('1a1a1a')) // trueconsole.log(reg.test('1A1A1A')) // trueconsole.log(reg.test('aAaAaA')) // trueconsole.log(reg.test('1aA1aA1aA')) // true
2. 字符串匹配原来这么简略
两种含糊匹配
正则如果只有准确匹配,那么便齐全没有了意义
横向
一个正则可匹配的字符串的长度不是固定的,能够是多种状况,通过量词+、*、?、{m,n},可实现横向匹配
let reg = /ab{2,5}c/let str = 'abc abbc abbbc abbbbc abbbbbc abbbbbbc'str.match(reg) // [ 'abbc', 'abbbc', 'abbbbc', 'abbbbbc' ]
纵向
一个正则匹配的字符串,具体到某一位字符时,能够不是某个确定的字符串,能够有多种可能,实现形式是字符组( 其实多选分支|也能够实现 )
let reg = /a[123]c/let str = 'a0b a1b a2b a3b a4b'str.match(reg) // [ 'a1b', 'a2b', 'a3b' ]
字符组
不要被名字给糊弄了,尽管他叫做字符组,但其实只是代表一个字符的可能性
范畴表示法
[123456abcdefABCDEF] => [1-6a-fA-F]
排除字符组
某位字符能够是任何货色,然而就是不能是xxx, 应用^符号
问题:如何要示意除了某个单词之外的任意货色呢?
[^abc]
常见简写模式
\d // 数字\D // 非数字\w // [0-9a-zA-Z_]\W // [^0-9a-zA-Z_]\s // [\t\v\n\r\f]\S // [^\t\v\n\r\f].
量词
量词 & 简写
1. {m,} // 至多呈现m次2. {m} // 呈现m次3. ? // 呈现0次或者1次,等价于{0,1} 4. + // 至多呈现1次,等价于{1,} 5. * // 呈现人一次,等价于{0,}
贪心匹配 VS 惰性匹配
正则自身是贪心的,会尽可能的多匹配合乎模式的字符
let regex = /\d{2,5}/glet string = '123 1234 12345 123456'// 贪心匹配// string.match(regex) // [ 123, 1234, 12345, 12345 ]// 惰性匹配let regex2 = /\d{2,5}?/g// string.match(regex) // [ 12, 12, 34, 12, 34, 12, 34, 56 ]
量词前面加一个?,即变成了惰性匹配
贪心量词 惰性量词{m,n} {m,n}?{m,} {m,}?? ??+ +?* *?
多选分支
一个模式能够实现横向和纵向的含糊匹配,而多选分支能够反对多个子模式任选其一,模式是(p1|p2|p3)
let regex = /good|nice/let string = 'good idea, nice try.'// string.match(regex) // [ 'good', 'nice' ]// 留神,用/good|goodbye/去匹配'goodbye' 匹配到的是good// 因为分支构造是惰性的,后面的匹配上了,前面的就不再尝试了
案例剖析
1.匹配id
// 1let regex = /id=".*?"/ // 想想为什么要加? 不加的话 连前面的class都会匹配到let string = '<div id="container" class="main"></div>';console.log(string.match(regex)[0]);// 2let regex = /id="[^"]*"/ let string = '<div id="container" class="main"></div>'; console.log(string.match(regex)[0]);
2.匹配16进制的色彩值
// 要求匹配如下色彩/*#ffbbad#Fc01DF#FFF#ffE*/let regex = /#([a-fA-F\d]{6}|[a-fA-F\d]{3})/glet string = "#ffbbad #Fc01DF #FFF #ffE";console.log(string.match(regex))// ["#ffbbad", "#Fc01DF", "#FFF", "#ffE"]
3.匹配24小时制工夫
/* 要求匹配 23:59 02:07*/// 解析:// 第一位:能够是0、1、2// 第二位:当第一位位0或者1的时候,能够是0到9、第一位是2的时候,只能够是0到3// 第三位:固定是冒号:// 第四位:能够是0到5// 第五位:0到9let regex = /^([01]\d|2[0-3]):[0-5]\d$/console.log(regex.test('23:59')) // trueconsole.log(regex.test('02:07'))// true// 衍生题,能够是非0let regex = /^(0?\d|1\d|2[0-3]):(0?|[1-5])\d/console.log( regex.test("23:59") ) // trueconsole.log( regex.test("02:07") ) // trueconsole.log( regex.test("7:09") ) // true
4.匹配日期
/* 要求匹配 yyyy-mm-dd格局的日期 留神月份、和日的匹配*/let regex = /\d{4}-(0\d|1[0-2])-(0[1-9]|[12]\d|3[01])/console.log( regex.test("2017-06-10") ) // trueconsole.log( regex.test("2017-11-10") ) // true
3. 括号的神奇作用
括号的作用是提供了分组(括号内的正则是一个整体,即提供子表达式),便于咱们援用它
分组
如何让量词作用于一个整体?
let reg = /(ab)+/glet string = 'ababa abbb ababab'console.log(string.match(reg)) // ["abab", "ab", "ababab"]
分支构造
分支构造有点像编程外面或的概念||
/*匹配 I love JavaScriptI love Regular Expression*/let reg = /I love (JavaScript|Regular Expression)/console.log(reg.test('I love JavaScript')) // trueconsole.log(reg.test('I love Regular Expression')) // true
分组援用
通过括号创立子表达式,能够进行数据提取和弱小的替换操作,也能够通过js来援用分组内容
提取数据
/*提取年月日2021-08-14*/let reg = /(\d{4})-(\d{2})-(\d{2})/console.log('2021-08-14'.match(reg))// ["2021-08-14", "2021", "08", "14", index: 0, input: "2021-08-14", groups: undefined]// 第二种解法,通过全局的$1...$9读取 援用的括号数据let reg = /(\d{4})-(\d{2})-(\d{2})/let string = '2021-08-14'reg.test(string)console.log(RegExp.$1) // 2021console.log(RegExp.$2) // 08console.log(RegExp.$3) // 14
数据替换
/*将以下格局替换为mm/dd/yyy2021-08-14*/// 第一种解法let reg = /(\d{4})-(\d{2})-(\d{2})/let string = '2021-08-14'// 第一种写法let result1 = string.replace(reg, '$2/$3/$1')console.log(result1) // 08/14/2021// 第二种写法let result2 = string.replace(reg, () => { return RegExp.$2 + '/' + RegExp.$3 + '/' + RegExp.$1})console.log(result2) // 08/14/2021// 第三种写法let result3 = string.replace(reg, ($0, $1, $2, $3) => { return $2 + '/' + $3 + '/' + $1})console.log(result3) // 08/14/2021
反向援用(很重要)
除了通过js援用分组的内容,也能够通过正则来援用分组内容
/* 写一个正则反对以下三种格局 2016-06-12 2016/06/12 2016.06-12*/let regex = /(\d{4})([-/.])\d{2}\1\d{2}/var string1 = "2017-06-12";var string2 = "2017/06/12";var string3 = "2017.06.12";var string4 = "2016-06/12";console.log( regex.test(string1) ); // trueconsole.log( regex.test(string2) ); // trueconsole.log( regex.test(string3) ); // trueconsole.log( regex.test(string4) ); // false
留神
援用不存在的分组会怎么?
- 即匹配的就是\1 \2自身
分组前面有量词会怎么?
- 分组前面如果有量词,分组最终(留神是分组,不是说整体)捕捉的数据是最初一次的匹配
'12345'.match(/(\d)+/) // ["12345", "5", index: 0, input: "12345", groups: undefined]/(\d)+ \1/.test('12345 1') // false/(\d)+ \1/.test('12345 5') // true
非捕捉性括号
下面应用的括号都会匹配他们匹配到的数据,以便后续援用,所以也能够称为捕捉型分组和捕捉型分支。
如果想要括号最原始的性能,但不会援用它,也就是既不会呈现在API援用里,也不会呈现在正则援用里,能够应用
非捕捉性括号(?:p)
// 非捕捉型援用let reg = /(?:ab)+/gconsole.log('ababa abbb ababab'.match(reg)) // ["abab", "ab", "ababab"]// 留神这里,因为是非捕捉型分组,所以应用match办法时,不会呈现在数组的1地位了let reg = /(?:ab)+/console.log('ababa abbb ababab'.match(reg)) // ["abab", index: 0, input: "ababa abbb ababab", groups: undefined]let reg = /(ab)+/console.log('ababa abbb ababab'.match(reg)) // ["abab", "ab", index: 0, input: "ababa abbb ababab", groups: undefined]
案例
1.trim办法模仿
// 1. 提取两头要害字符, 应用的分组援用const trim1 = (str) => { return str.replace(/^\s*(.*?)\s*$/, '$1')}// 2. 去掉结尾和结尾的空字符const trim2 = (str) => { return str.replace(/^\s*|\s*$/g, '')}
2.将每个单词的首字母大写
要害是要找到每个单词的首字母
// my name is epeliconst titleize = (str) => { return str.toLowerCase().replace(/(?:^|\s)\w/g, (c) => c.toUpperCase())} console.log(titleize('my name is epeli')) // My Name Is Epeli// 拓展,横向转驼峰,例如base-act-tab => BaseActTab'base-act-tab'.replace(/(?:^|-)(\w)/g, ($0, $1) => $1.toUpperCase()) // BaseActTab
3.驼峰化
// -moz-transform => MozTransformconst camelize = (str) => { return str.replace(/[-_\s]+(\w)/g, (_, $1) => $1.toUpperCase()) }console.log(camelize('-moz-transform')) // MozTransform
4.中划线化
// MozTransform => -moz-transformconst dasherize = (str) => { return str.replace(/[A-Z]/g, ($0) => ('-' + $0).toLowerCase())}console.log(dasherize('MozTransform')) // -moz-transform
5.HTML本义和反本义
// html本义规定见https://blog.wpjam.com/m/character-entity/const escapeHTML = (str) => { const escapeChars = { '<': 'lt', '>': 'gt', '"': 'quot', ''': '#39', '&': 'amp' } let regexp = new RegExp(`[${Object.keys(escapeChars).join('')}]`, 'g') // 为了失去字符组[<>"'&] return str.replace(regexp, (c) => `&${escapeChars[ c ]};`)}console.log( escapeHTML('<div>Blah blah blah</div>')) // <div>Blah blah blah</div>// 反本义const unescapseHTML = (str) => { const htmlEntities = { nbsp: ' ', lt: '<', gt: '>', quot: '"', amp: '&', apos: ''' } return str.replace(/&([^;]+);/g, ($0, $1) => { return htmlEntities[ $1 ] || '' })}console.log(unescapseHTML('<div>Blah blah blah</div>')) // <div>Blah blah blah</div>
6.匹配成对的标签
/* 匹配 <title>regular expression</title> <p>laoyao bye bye</p> 不匹配 <title>wrong!</p>*/let reg = /<([^>]+)>.*?</\1>/gconsole.log(reg.test('<title>regular expression</title>')) // trueconsole.log(reg.test('<p>laoyao bye bye</div>')) // false
相约再见
强烈推荐老姚的正则表达式迷你小书,笔者也是读了这本书之后缓缓开始对正则有些了解,不再抗拒它,这篇文章次要也是基于这本书的内容做了总结。
参考
- JS正则表达式残缺教程(略长)
- 三十分钟包会——正则表达式
- 聊聊让人头疼的正则表达式