正则表达式

正则表达式

一直以来都避免接触正则 😠,因为感觉都是啥,看不懂,好难看,不想学 🥺。不过还是有必要了解学习一下的,至少要能看懂,知道在什么时候使用它,当然具体的表达式可以直接百度或者问 GPT,看得懂意思就好。

正则表达式是匹配模式,要么匹配字符,要么匹配位置

搞懂位置能干啥?

  1. 数字的千分位分割法

    123456789 -> 123,456,789

  2. 手机号 3-4-4 分割

    12345678901 -> 123-4567-8901

  3. 密码合法性校验

    密码长度是 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符

上面也都是很常见的需求,正则可以很方便的解决他们

什么是位置

位置可以理解为相邻字符之间的位置,可以类比为空字符串,在字符的首尾、间隙用空字符连接

'he' === '' + 'h' + '' + 'e' + ''

有哪些位置

正则中常用来表示位置的符号主要有:
^$\b\B?=p(?!p)(?<=p)(?<!p)

^

匹配行的开头

如果要在 hello 开头赛一个笑脸 😊,就可以这么写

let str = 'hello'
console.log(str.replace(/^/, '😊'))
// 😊hello

$

匹配行的结尾

同理在 hello 的末尾赛一个笑脸 😊

console.log('hello'.replace(/$/, '😊'))

\b

单词的边界

匹配一个单词边界,即字和空格之间的位置
比如想把xxx_1.mp4变成❤️xxx_1❤️.❤️mp4❤️,就可以这么写

let str = 'xxx_1.mp4'
console.log(str.replace(/\b/, '❤️'))
// ❤️xxx_1.mp4 没有g,只替换第一个
console.log(str.replace(/\b/g, '❤️'))
// ❤️xxx_1❤️.❤️mp4❤️

\B

非单词的边界,就是和\b 相反

直接看例子吧:

let str = 'xxx_1.mp4'
console.log(str.replace(/\B/, '❤️'))
// x❤️xx_1.mp4 没有g,只替换第一个
console.log(str.replace(/\B/g, '❤️'))
// x❤️x❤️x❤️_❤️1.m❤️p❤️4

(?=p)

p 为正向先行断言,表示 p 前面的位置

比如想把xxx_1.mp4变成❤️xxx_1.mp4,就可以这么写

let str = 'xxx_1.mp4'
console.log(str.replace(/(?=xxx)/, '❤️'))
// ❤️xxx_1.mp4

(?!p)

p 为负向先行断言,表示不在 p 前面的位置

let str = 'xxx_1.mp4'
console.log(str.replace(/(?!xxx)/, '❤️'))
// x❤️xx_1.mp4
console.log(str.replace(/(?!xxx)/g, '❤️'))
// x❤️x❤️x❤️_❤️1❤️.❤️m❤️p❤️4

(?<=p)

p 为正向后行断言,表示 p 后面的位置

let str = 'xxx_1.mp4'
console.log(str.replace(/(?<=xxx)/, '❤️'))
// xxx❤️_1.mp4

(?<!p)

p 为负向后行断言,表示不在 p 后面的位置

let str = 'xxx_1.mp4'
console.log(str.replace(/(?<!xxx)/, '❤️'))
// ❤️xxx_1.mp4
console.log(str.replace(/(?<!xxx)/g, '❤️'))
// ❤️x❤️x❤️x_❤️1❤️.❤️m❤️p❤️4❤️

尝试做题

数字的千分位分割法

将 123456789 转化为 123,456,789

从后往前,每 3 个数字前面加一个逗号,这很符合(?=p)

  1. 先吧最后一个逗号弄出来
let price = '123456789'
let priceReg = /(?=\d{3}$)/
console.log(price.replace(priceReg, ','))
// 123456,789
  1. 弄出所有的逗号
let price = '123456789'
let priceReg = /(?=(\d{3})+$)/g
// (/d{3})就是匹配三个为一组的数字
console.log(price.replace(priceReg, ','))
// ,123,456,789
  1. 去掉第一个逗号
let price = '123456789'
let priceReg = /(?!^)(?=(\d{3})+$)/g
// (?!^)来要求不能是首位
console.log(price.replace(priceReg, ','))
// 123,456,789

手机号 3-4-4 分割

12345678901 -> 123-4567-8901

let phone = '12345678901'
let phoneReg = /(?=(\d{4})+$)/g
console.log(phone.replace(phoneReg, '-'))
// 123-4567-8901

手机号 3-4-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

这个就是和从前往后

  1. 先弄第一个 -
const formatPhone = phone => String(phone).replace(/(?<=\d{3})\d+/, '-')
console.log(formatPhone(123)) // 123
console.log(formatPhone(1234)) // 123-
  1. 第二个
const formatPhone = phone =>
String(phone)
.slice(0, 11)
.replace(/(?<=\d{3})\d+/, str => `-${str}`)
.replace(/(?<=[\d-]{8})\d{1,4}/, str => `-${str}`)
// 先匹配的123xxxx, xxxx -> -xxxx
// 然后匹配的 '123-xxxx'后面的内容
console.log(formatPhone(123)) // 123
console.log(formatPhone(1234)) // 123-4
console.log(formatPhone(12345)) // 123-45
console.log(formatPhone(123456)) // 123-456
console.log(formatPhone(1234567)) // 123-4567
console.log(formatPhone(12345678)) // 123-4567-8
console.log(formatPhone(123456789)) // 123-4567-89
console.log(formatPhone(12345678911)) // 123-4567-8911

校验密码合法性

密码长度是 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符

  1. 6-12 位
  2. 数字、小写、大写
  3. 至少 2 种

先写 12 俩条件

let reg = /^[a-za-Z\d]{6-12}$/

条件 3

let reg = /^(?![a-z]+$)(?![A-Z]+$)(?!\d+$)[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

字符串匹配

俩种模糊匹配

横向

通过+*?{m,n},可实现横向匹配

let reg = /ab{2,5}c/g
let str = 'abc abbc abbbc abbbbc abbbbbc abbbbbbc'
console.log(str.match(reg))
// ['abbc', 'abbbc', 'abbbbc', 'abbbbbc']

纵向

通过[],可实现纵向匹配

let reg = /a[123]b/g
let str = 'a0b a1b a2b a3b a4b'
console.log(str.match(reg))
// ['a1b', 'a2b', 'a3b']

字符组

虽然叫字符组,不过也有可能只有一个字符

范围表示法

[123456abcdefABCDEF] => [1-6a-fA-F]

排除字符组

使用^来排除

let reg = /[^abc]/g
let str = 'a0b23'
console.log(str.replace(reg, '❤️'))
// a❤️b❤️❤️

常见简写

\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. {m,n} // 出现m-n次
3. ? // 出现0次或者1次,等价于{0,1}    
4. + // 至少出现1次,等价于{1,} 
5. * // 出现>=0次,等价于{0,}  

贪婪匹配 惰性匹配

贪婪会尽可等多匹配符合模式的字符

// 贪婪匹配
let regex = /\d{2,5}/g
let 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  ]

多选分支

let regex = /good|nice/g
let string = 'good idea, nice try.'
string.match(regex) // [ 'good', 'nice' ]

题目

  1. 匹配 id
// 1
let regex1 = /id=".*?"/
// 想想为什么要加? 不加的话 连后面的class都会匹配到
let string = '<div id="container" class="main"></div>'
console.log(string.match(regex1)[0])
// 2
let regex2 = /id="[^"]*"/
console.log(string.match(regex2)[0])
  1. 匹配 16 进制颜色值
// 要求匹配如下颜色
/*
#ffbbad
#Fc01DF
#FFF
#ffE
*/

let regex = /#([a-fA-F\d]{6}|[a-fA-F\d]{3})/g
let string = '#ffbbad #Fc01DF #FFF #ffE'

console.log(string.match(regex))
//  ["#ffbbad", "#Fc01DF", "#FFF", "#ffE"]
  1. 匹配 24 小时制时间
/*
  要求匹配
  23:59
  02:07
*/
// 解析:
// 第一位:可以是0、1、2
// 第二位:当第一位位0或者1的时候,可以是0到9、第一位是2的时候,只可以是0到3
// 第三位:固定是冒号:
// 第四位:可以是0到5
// 第五位:0到9

let regex = /^([01]\d|2[0-3]):[0-5]\d$/

console.log(regex.test('23:59')) // true
console.log(regex.test('02:07')) // true

// 衍生题,可以是非0
let regex = /^(0?\d|1\d|2[0-3]):(0?|[1-5])\d/

console.log(regex.test('23:59')) // true
console.log(regex.test('02:07')) // true
console.log(regex.test('7:09')) // true

括号的作用

分组

让量词作用于一个整体

let reg = /(ab)+/g
let string = 'ababa abbb ababab'

console.log(string.match(reg))
// ["abab", "ab", "ababab"]

分支结构

let reg = /I love (JavaScript|Regular Expression)/

console.log(reg.test('I love JavaScript'))
// true
console.log(reg.test('I love Regular Expression'))
// true

总结

感觉还是不好理解,不过能看懂一些符号的含义了 ☹️

参考链接