正则表达式
# 正则表达式
RegExp: regular expression
学正则就是用来制定规则
用处:用来处理字符串的规则
- 只能处理字符串
- 它是一个规则,可以验证字符串是否符合某个规则(
test),也可以把字符串中符合规则的内容捕获到(match/exec..) - 匹配:
test - 捕获:
exec
let str = 'god good study, day day up!'
let reg = /\d+/
reg.test(str) // => false
str = '2019-9-18'
reg.exec(str) // =>['2019',index:0,inputs: '原始字符串'];
1
2
3
4
5
6
2
3
4
5
6
# 编写正则表达式
# 创建方式有两种
// => 字面量创建方式(两个斜杠之间包起来的,就是用来描述规则的元字符)
let reg1 = /\d+/
// => 构造函数模式创建,两个参数:元字符字符串,修饰符字符串(都是字符串)
let reg2 = new RegExp('\\d+')
1
2
3
4
5
2
3
4
5
# 两种创建正则方式的区别
- 构造函数因为传递的是字符串,
\需要写两个才代表斜杠
let reg = /\d+/g
reg = new RegExp('\\d+', 'g')
1
2
2
- 正则表达式中的部分内容是变量存储的值
// 1.两个斜杠中间包起来的都是元字符
let name = "coderly";
reg = /^@"+name+"@$/;
console.log(reg.test("@coderly@")); // =>false
// 2.如果有变量作为正则元字符的一部分,必须用构造函数的方式,因为它传递的规则是字符串
reg = new RegExp("^@"+type+"@$");
1
2
3
4
5
6
7
2
3
4
5
6
7
# 正则表达式由两部分组成
# 元字符
常用元字符:
1、量词元字符,设置出现的次数
*:零到多次 \d*+:一到多次 \d+?:零次或者一次 \d?{n}:出现n次 \d{n}{n, }:出现n到多次 \d{n, }{n, m}:出现n到m次 \d{n, m}
特殊元字符:
2、特殊元字符,单个或者组合在一起代表特殊的含义
\:转义字符(普通 -> 特殊 -> 普通 ).: 除\n( 换行符)以外的任意字符^:以哪一个元字符作为开始$:以哪一个元字符作为结束\n:换行符\d:0~9 之间的一个数字\D:非 0~9 之间的一个数字(大写和小写的意思是相反的)\w:数字、字母、下划线中的任意一个字符\s:一个空白字符(包含空格、制表符、换行符等)\t:一个制表符(一个TAB键:四个空格)|:或,例如x|y:表示x或者y中的一个字符[xyz]:x或者y或者 z 中的一个字符[^xy]:除了x、y以外的任意字符[a-z]:制定a-z这个范围中的任意字符,例如:[0-9a-zA-Z_]=== \w:表示0-9中的任一字符或者a-z中的任一字符或者A-Z中的任一字符或者下划线[^a-z]:上一个的取反“非”():正则中的分组(?: ):只匹配不捕获(?=):正向预查(?!):负向预查
# 修饰符
常用修饰符(img):
- i :=>ignoreCase 忽略单词大小写匹配
- m :=>multiline 可以进行多行匹配
- g :global 全局匹配
;/A/.test('lalala') // false
;/A/i.test('lalala') // true
1
2
2
# 例子
^和$
// ^/$两个都不加,字符串中包含符合规则的内容即可
let reg1 = /\d+/;// 只要字符串中包含数字就满足条件
// ^/$两个都加,字符串只能是和规则一致的内容
let reg2 = /^\d+$/
1
2
3
4
5
2
3
4
5
-\
// => .不是小数点,是除\n外的任意字符
let reg = /^2.3$/;
console.log(reg.test("2@3")); // => true
console.log(reg.test("2.3")); // => true
// => 把特殊符号转换为普通的
let reg2 = /^2\.3$/;
console.log(reg2.test("2@3")); // => false
console.log(reg2.test("2.3")); // => true
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
x|y
// ---- 直接x|y会存在很乱的优先级问题,一般我们写的时候都伴随着小括号进行分组,因为小括号改变处理的优先级 => 小括号:分组
let reg = /^18|29$/
console.log(reg.test('18')) // true
console.log(reg.test('29')) // true
console.log(reg.test('189')) // true
console.log(reg.test('129')) // true
console.log(reg.test('182')) // true
console.log(reg.test('82')) // false
// 现在这样就是只能是18或者29中的一个
let reg2 = /^(18|29)$/
console.log(reg2.test('18')) // true
console.log(reg2.test('29')) // true
console.log(reg2.test('189')) // false
console.log(reg2.test('129')) // false
console.log(reg2.test('182')) // false
console.log(reg2.test('82')) // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[]
// 1. 中括号中出现的字符一般都代表本身的含义
// => 表示匹配括号内的多个
let reg = /[@+]+$/
console.log(reg2.test('@@')) // true
console.log(reg2.test('@+')) // true
// => 表示匹配括号内的一个
let reg2 = /^[@+]$/
console.log(reg2.test('@')) // true
console.log(reg2.test('+')) // true
console.log(reg2.test('@@')) // false
console.log(reg2.test('@+')) // false
// 表示匹配数字,而不是匹配\或者d中的一个,\d在中括号中表示0-9的数字
let reg3 = /^[\d]$/
console.log(reg3.test('d')) // false
console.log(reg3.test('\\')) // false
console.log(reg3.test('9')) // true
// 2. 中括号中不存在多位数
// => 匹配1或者8,而不是18
let reg4 = /^[18]$/
console.log(reg4.test('1')) // true
console.log(reg4.test('8')) // true
console.log(reg4.test('18')) // false
// => 1或者0-2或者9,而不是匹配10-29中的数值
let reg5 = /^[10-29]$/
console.log(reg5.test('1')) // true
console.log(reg5.test('9')) // true
console.log(reg5.test('0')) // true
console.log(reg5.test('2')) // true
console.log(reg5.test('10')) // false
// => 用\ 转义(),没有实现将10-29分组改变优先级,而是()变成了总括号内的一部分。
let reg6 = /^[\(10-29\)]$/
console.log(reg6.test('(')) // true 。此时匹配(发现为true)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 正则的捕获
实现捕获的前提是:当前正则要和字符串匹配(如果不匹配结果捕获是null)
# 实现正则捕获的方法
# 正则RegExp.prototype上的方法
exec
let str = 'month06day14huors15'
let reg = /\d+/
console.log(reg.exec(str))
// [ 0: "06", groups: undefined, index: 5, input: "month06day14huors15", length: 1 ]
1
2
3
4
2
3
4
- 捕获到的结果是
null或者一个数组 - 第一项:本次捕获到的内容
- 其余项:对应小分组本次单独捕获的内容
index:当前捕获内容在字符串中的起始索引input:原始字符串- 每执行一次 exec 只能捕获到一个符合正则规则的结果
reg.lastIndex:当前正则下一次匹配的起始索引位置
- 懒惰性捕获的原因:默认 情况下
lastIndex的值不会被修改,每一次都是从字符串开始位置查找,所以找到的永远只是第一个 - 设置全局匹配修饰符
g后,第一次匹配到后lastIndex会自己修改 - 所以解决懒惰性就用 全局
g
let str = 'month06day14huors15'
let reg = /\d+/g
console.log(reg.exec(str))
1
2
3
2
3
自己实现非懒惰的捕获
!(function() {
function execAll(str) {
// 进来后的第一件事,是验证当前正则是否设置了 g, 不设置则不能进行循环捕获,否则会导致死循环
if (!this.global) return this.exex(str)
let arr = [],
res = this.exec(str)
while (str) {
arr.push(res)
res = this.exec(str)
}
return arr.length === 0 ? null : arr
}
return (RegExp.prototype.execAll = execAll)
})()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
test
- 默认是匹配,但是也可以进行捕获
let str = '2020年06月14日'
let reg = /(\d+)/g
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 2020
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 16
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 14
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 14,存储的是上次捕获的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
RegExp.$1 - RegExp.$9:获取当前本次正则匹配后,第一个到第九个分组的信息
# 字符串String.prototype上支持正则表达式处理的方法
replace
- 字符串中实现替换的方法(一般都是伴随正则一起使用的)
案例:把时间字符串进行处理
let time = '2020-06-14' // 变为 2020年06月14日
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/g
// 方法一
time = time.replace(reg, '$1年$2月$3日')
console.log(time) // 2020年06月14日
// 还可以这样处理
// 1. 首先拿 reg 和 time 进行匹配捕获,能匹配到几次就会把传递的函数执行几次(而且是匹配一次就执行一次)
// 2. 不仅把方法执行,而且 replace 还给方法传递了实参信息(和 exec 捕获的内容一致的信息:大正则匹配的内容,小分组匹配的信息)
// 3. 使用函数时,当前大正则匹配的内容替换为函数中返回的内容
time = time.replace(reg, (_, ...arr) => {
console.log(_) //2020-06-14
console.log(arr) // ["2020", "06", "14", 0, "2020-06-14"]
return ` ${arr[0]}年${arr[1]}月${arr[2]}日`
})
console.log(time) // 2020年06月14日
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
案例 2:单词首字母大写
let str = 'good good study, day day up!'
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g
str = str.replace(reg, (_, ...arg) => {
let $1 = arg[0].toUpperCase()
return $1 + _.substring(1)
})
1
2
3
4
5
6
2
3
4
5
6
案例 3:验证一个字符串中出现最多的字母,多少次?
let str = 'coderly,day day up!'
str = str
.split('')
.sort((a, b) => a.localeCompare(b))
.join('')
let reg = /([a-zA-Z])\1+/g
let ary = str.match(reg)
ary.sort((a, b) => b.length - a.length)
console.log(ary) // ["ddd", "yyy", "aa"]
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
match
字符串的match匹配到的是一个数组
'month06day14huors15'.match(/\d+/)
// [0: "06", groups: undefined, index: 5, input: "month06day14huors15", length: 1]
'month06day14huors15'.match(/\d+/g)
// ["06", "14", "15"]
1
2
3
4
5
2
3
4
5
splite
# 分组捕获和分组引用
# 分组捕获
- 第一项: 大正则匹配的结果
- 其余项: 每一个小分组单独匹配捕获的结果
- 如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于
?:来处理 ?:只匹配不捕获,(?:\d{2})
let str = '2020-06-14'
let reg = /(\d{4})-(\d{2})-(\d{2})/
console.log(reg.exec(str))
console.log(str.match(reg))
// [ 0: "2020-06-14", 1: "2020", 2: "06", 3: "14", groups: undefined, index: 0, input: "2020-06-14", length: 4 ]
1
2
3
4
5
2
3
4
5
- 既要捕获到
{\d},也要把里面的数字也单独捕获到
let str = '{2020}年{06}月{14}日'
let reg = /{(\d+)}/g
let arrBig = [],
arrSmall = [],
res = reg.exec(str)
while (res) {
let [big, small] = res
arrBig.push(big)
arrSmall.push(small)
res = reg.exec(str)
}
console.log(arrBig)
console.log(arrSmall)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 分组引用
- 分组引用就是通过 "\数字" 让其代表和对应分组出现一模一样的内容;
let res = 'book'
1
# 正则捕获的贪婪性
- 默认情况下,正则捕获的时候,是按照当前正则所匹配的最长结果来获取的
- 取消正则捕获贪婪性:在量词元字符后面设置
?,此时按照正则匹配的最短结果来获取
let str = '2020年06月14日'
let reg = /\d+/g
console.log(str.match(reg)) // ["2020", "06", "14"]:贪婪性,默认按匹配最长捕获
let str = '2020年06月14日'
let reg = /\d+?/g
console.log(str.match(reg)) // ["2", "0", "2", "0", "0", "6", "1", "4"]:取消贪婪性,按匹配最短捕获
1
2
3
4
5
6
7
2
3
4
5
6
7
# ? 在正则中的作用
- 左边是非量词元字符:本身代表量词元字符,出现 0-1 次
- 左边是量词元字符:取消捕获时候的贪婪性
(?:):只匹配不捕获(?=):正向预查(?!):负向预查
# 常用的正则表达式
# 1. 验证是否为有效数字
- 规则分析,
(0, 1, 12, 12.5, 12.0, -1, -12.5, +9)(09不是有效数字)- 可能出现+、-,也可能不出现。
(+|-)?
- 可能出现+、-,也可能不出现。
- 一位 0-9 都可以,多为首位不能为 0。
(\d | ([1-9]\d+))
- 一位 0-9 都可以,多为首位不能为 0。
- 小数部分可能有可能没有,一旦有后面必须有小数点+数字。
(\.\d+)?
- 小数部分可能有可能没有,一旦有后面必须有小数点+数字。
let reg = /^(+|-)?(\d|([1-9]\d+))(\.\d+)?$/
1
# 2. 验证密码
- 规则分析:
- 数字、字母、下划线
\w
- 数字、字母、下划线
- 6~16 位
{6,16}
- 6~16 位
let reg = /^\w{6,16}$/
1
# 3. 验证真实姓名的
- 规则分析:
- 汉字
/^[\u4E00-\u9FA5]$/
- 汉字
- 名字长度 2-10 位
- 可能有译名 ·汉字
/^(·[\u4E00-\u9FA5]){2,10}{0,2}$/
- 可能有译名 ·汉字
let reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10}){0,2}$/
1
# 4. 验证邮箱的
- 规则分析:
- 开头是数字字母下划线(1 到多位)
/^\w$/
- 开头是数字字母下划线(1 到多位)
- 还可以是 - 数字字母下划线或者 .数字字母下划线,整体零次到多次
- 3.邮箱的名字由“数字、字母、下划线、-、.”几部分组成,但是
-和.不能连续出现也不能作为开始,(liu-yu-x,liu.yu.x,liu-yu.x)\w+((-\w+)|(\.\w+))* - @后面紧跟着:数字、字母(1 多多位)
@[A-Za-z0-9]+
- @后面紧跟着:数字、字母(1 多多位)
let reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-))[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/
1
# 5.身份证号码
【注意】一代身份证 15 位现在用不了
- 规则分析:
- 一共十八位
- 最后一位可能是 X
// 1. 身份证前六位: 省市县
// 2.中间八位:年月日
// 3. 最后四位:
// 最后一位 => x 或者数字
// 倒数第二位 =>偶数 女,奇数 男
// 其余的是通过算法算出来的
// let reg = /^d{17}(\d|x)$/;
// => 小括号分组的第二个作用:分组捕获,不仅可以把大正则匹配的信息捕获到,还可以单独捕获到每个小分组的内容
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|x)$/;
reg.exec("130828199012040617")
结果:
["130828199012040617", "130828", "1990", "12", "04", "1", "7", index: 0, input: "130828199012040617", groups: undefined]
// 捕获结果是一个数组,包含每一个小分组单独获取的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
上次更新: 2021/09/13, 15:11:59