关于正则表达式的细枝末叶、易碎流年

760 阅读3分钟

本文原创:liuchendi

1.正则表达式的基本概念

1.1 捕获/匹配的概念

  1. 匹配:验证当前字符串是否符合某个规则
  2. 捕获:将字符串中符合规则的字符获取到

1.2 正则表达式的创建

字面量方式创建

let reg = /元字符/修饰符

实例方式创建

letg reg = new RegExp('\d+',igm)//实例化创建正则的方式不需要加/(并不包括转义斜杠\),并把修饰符以第二个参数的形式传递给实例


注意:字面量方式创建的正则表达式无法识别变量,但实例方式创建的可以

通过实例方式创建的正则,其中特殊元字符需要再加上一个\,因为字符串中的\也是代表转义的意思,就会默认省略

let reg = new RegExp('\\b${cn}\\b')

正则表达式无法识别连续的单词中的字符或者数字,他会默认把他们当做一个一个单独的字符去识别

let reg = /[12-66]/ //表示1或2-6或5

回车打下的换行还自带一个空格

当你要检测字符串\d时

let reg = /\\d/
reg.test('\\d')
//字符串中的斜杠首先就是一个转义运算符,你要想测试\d
//字符串本身的\还要进行转义

1.3 元字符的分类

  1. 特殊元字符(在正则表达式中有特殊意义的字符)
  2. 量词元字符(限制元字符出现的连续次数)
  3. 其他元字符
  4. 普通元字符

1.3.1 特殊元字符

  1. \    转义元字符(把一些运算符转换成原意)
  2. \d   出现0-9中的任意一位数字
  3. \D   出现除了0-9中的任意一位数字
  4. \w   匹配字母(包括大小写)、数字、下划线内的任意一个字符
  5. \W   匹配除字母(包括大小写)、数字、下划线以外任何一个字符
  6. \s   匹配空白符(空格 换行符)
  7. \S   匹配除空白符以外的任意一个字符
  8. \b   匹配单词的边界
  9. \B   匹配不在边界的字符
  10. ^   匹配字符串的开头(以什么什么开头)
  11. $   匹配以什么什么结尾
  12. \n  匹配换行符
  13. .   匹配除换行符以外的其他字符(相当于/N)

1.3.2 量词元字符

量词限制的是前面紧跟的某个字符出现的次数

  1. ?出现0~1次,可有可无,相当于{0,1}
  2. . 出现0到多次(可有可无)可以连续出现多次。相当于 {0,}
  3. 加号 出现1到多次(至少要出现1次)可以连续出现多次。相当于 {1,}
  4. {n} 连续出现n次 n代表次数
  5. {n,} 连续出现n到多次 至少要连续出现n次 多了不限
  6. {n,m} 连续出现n到m次 至少要连续出现n次 最多连续出现m次
let reg = /^[a-z]$/
console.log(reg.test('aa')) // false  注意:这个是表示一个字母既是开头又是结尾,并且是一个字母

1.3.3 其他元字符

  1. |    x|y:匹配x或y中的任意一个字符,相当于[xy]
  2. []   [xyz]:匹配xyz中的任意一个字符,相当于x|y|z
  3. [^]  [^xyz]:匹配除xyz以外的任意一个字符
  4. [a-z][0-9]:匹配从a-z中的任意一个字母

1.3.4 正则中的修饰符

  1. g    global全局匹配
  2. i    ignoreCase忽略大小写
  3. m    multiline多行匹配

1.4 正则中的小括号

正则当中小括号改变优先级

let reg = /^a|b$/       //以a为开头或者以b为结尾
let reg3 = /^(a|b)$/    //相当于/^a$/或/^b$/

分组捕获

分组引用

let reg = /^1(\d)\1\1$/
console.log(reg.test('1222')) // true
console.log(reg.test('1333')) // true
let reg2 = /^([a-z])([a-z])\2\1$/
console.log(reg2.test('abba')) // true
console.log(reg2.test('baba')) // false

在这里如何计算他是第几个分组,就从左往右看,看他的左括号是第几个

1.5 正则中的中括号

  1. 中括号中部分元字符,代表的是原义 [?] [*] [+] [.]
  2. 中括号限制范围[a-z]、[0-9]:如果是[9-0]会报错,并且由于正则表达式无法识别连续的数字和单词,所以[20-31]实际上识别的是2|[0-3]|1

2.正则表达式的常用方法

2.1 正则的捕获

正则捕获:将匹配到的内容,捕获出来,作为返回值返回给我们。方法的位置:RegExp.prototype.exec()

他的返回值是一个数组,如果没有捕获到返回值为null

  • ["2", index: 3, input: "abc23", groups: undefined]
  • 0: "2" 捕获到内容
  • index: 3 捕获到的起始索引位置
  • input: "abc23"  原始字符串
  • groups 命名匹配

index、groups、input都是是正则捕获到的数组的自带属性,这个数组的长度为1


2.2 正则的懒惰性

每次调用正则捕获或者匹配的时候都是从索引0处开始捕获的

let reg = /\d+/
let str = 'abc111 abc222 abc333'
reg.exec(str)// ["111", index: 3, input: "abc111 abc222 abc333", groups: undefined]

reg.exec(str)// ["111", index: 3, input: "abc111 abc222 abc333", groups: undefined]

reg.exec(str)// ["111", index: 3, input: "abc111 abc222 abc333", groups: undefined]

取消正则的懒惰性,修饰符g(global),会从上一次匹配或者捕获成功的位置继续向后查找

如果捕获到结束边界 会从头再来

let reg = /\d+/g
let str = 'abc111 abc222 abc333'

console.log(reg.exec(str))
// ["111", index: 3, input: "abc111 abc222 abc333", groups: undefined]  test:true
   
console.log(reg.exec(str))
// ["222", index: 10, input: "abc111 abc222 abc333", groups: undefined]  test:true
    
console.log(reg.exec(str))
// ["333", index: 17, input: "abc111 abc222 abc333", groups: undefined]  testLtrue
    
console.log(reg.exec(str))
// null     test:false
    
console.log(reg.exec(str))
// ["111", index: 3, input: "abc111 abc222 abc333", groups: undefined]  test:true

2.3 正则实例下的属性

lastIndex:他是正则实例上的一个属性,下一次开始查找的起始索引

lastIndex的值可以手动修改,但是如果没有修饰符g的时候,修改不会起作用

reg.lastIndex = 9

无论是test还是exec还是混用,当加修饰符/g时,lastIndex属性都是起决定性的因素。

let reg = /\d+/
reg.exec('abc111 abc222 abc333')    //111   index=3
reg.exec('a444 b555 c666')  //555

因为lastIndex是正则实例对象reg上的属性,换字符串并不会影响他下面私有属性的值

global属性是用来标识正则是否有g修饰符的,返回值时true和false

multline属性是否支持多行匹配

ignoreCase属性是否忽略大小写

sourse正则所有的元字符内容,不包括外界的两个斜杠

2.4 字符串的match方法

match支持正则,在他的底层封装的还是exec,当没有取消正则懒惰性的情况下,他的返回值和exec相同,如果匹配不到返回值为null

如果取消了正则的懒惰性,match会以数组的形式将捕获到的结果一次性返回

2.5 正则的贪婪性

正则的贪婪性:每一次捕获的时候总是捕获到满足当前正则的最长内容

取消正则的贪婪性:量词后面加?,按照最短的情况进行匹配

let reg = /\d{2,8}?/g

2.6 分组捕获

数组返回的第一项始终是整个大正则捕获到的内容,如果有分组,会从第二项开始依次排列

let reg = /hello(\d+)/g
let str = 'hello2018 abc111 hello2019 abc222'
console.log(reg.exec(str))
// ["hello2018",2018, index: 0, input: "hello2018 abc111 hello2019 abc222", groups: undefined]
console.log(reg.exec(str))
// ["hello2019",2019, index: 0, input: "hello2018 abc111 hello2019 abc222", groups: undefined]

如果不加g,由于match的底层就是exec,所以返回值和exec相同

如果加了g,由于match是一次性捕获,所以不能捕获分组内容的

取消分组

在小括号的前面加上一个(?:)只匹配,不捕获
let reg = /^(?:start|end)$/

2.7 分组引用

let reg = /^1(\d)\1$/

\1是对分组1的再次引用,这里存在几个小问题,其一是引用的分组表示两个分组的内容相同,第二个是通过引用的分组通过RegExp.$拿不到

let reg = /^([a-z])\1([a-z]\1)$/
//aabb  ccdd

2.8 正向预查、负向预查

正向预查(?=元字符)    不会发生分组捕获 负向预查(?!元字符)    不会发生分组捕获

// 正向预查
// 捕获 后面跟着3或者4的字母
let reg1 = /[a-z](?=3|4)/g
let str1 = 'a1 b2 c3 d4'
console.log(str1.match(reg1)) //["c", "d"]

// 负向预查
// 捕获 后面跟着的不是3或者4的字母
let reg2 = /[a-z](?!3|4)/g
let str2 = 'a1 b2 c3 d4'
console.log(str2.match(reg2)) // ["a", "b"]

捕获的不是预查的内容而是范围内的内容

正向预查或者负向预查如果放在开头的话,作用是限定后面的范围\d,\w,[]

let reg11 = /^((?=3)\d)+$/
console.log(reg1.test('3333')) // true限定数字只能为3

let reg2 = /^((?!4)\d)+$/g
console.log(reg2.test('1123123')) // true 限定数字里不能有4
console.log(reg2.test('11234123')) // false
console.log(reg2.test('11234566')) // false

2.9 m修饰符(了解)

let reg = /^a.+b$/
let str = `a1b
a2b
a3b
`
reg.test(str)//false    因为没有以a为开头,b为结尾的
let reg1 = /^a.+b$/m

reg1.exec(str)//a1b     会把每一行都看作独立的一行进行匹配 
reg1.exec(str)//a2b
reg1.exec(str)//a3b

2.10 字符串的replace、match、split都可以使用正则表达式

split

// let sss = '1a2b3c4'
// console.log(sss.split(/[a-z]/)) // [1, 2, 3, 4]

replace

replace的底层封装的也是exec,相当于自动的对exec进行遍历

let str = 'aaa2018 bbb2019'
let reg = /hello/   //如果加g,匹配到多少次,这个函数就会被执行几次
//他会把exec捕获到的值当做参数传递给这个函数
//这个函数的返回值就是replace捕获到的内容所要替换的值
str.replace(reg,(...rest)=>{
    console.log(rest)
    return 'AA'
})

这个函数的返回值就是匹配到的内容要替换的新内容,有点类似于数组的map方法

let str2 = str.replace(/\d\b/g, a => a * 2)
//第一个参数是正则捕获到的内容,由于没有用扩展运算符,所以穿入的直接是变量而不是数组

3.正则表达式的应用

3.1 时间字符串

方法一

let reg = \(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\
str.replace(reg,`$1$2$3$4$5$6秒`)
str.replace(reg,function(...rest){
    return `${rest[1]}${rest[2]}${rest[3]}日`
})

方法二

let time2 = time.split(/[-\s:]/)//[2019,20,1,11,56,56]
let text = ['年','月','日',' ','时','分','秒']
//forEach拼接

方法三

let ary = time.match(/\d+/g)
let text = ['年','月','日',' ','时','分','秒']
ary.map()//遍历修改

3.2 统计字符串中字母出现的次数

let newStr = str.split('').sort().join('')
let reg = /(\w)\1*/g
let obj = {}
newStr.replace(reg,function(){
   obj[rest[1]] = obj[rest[0].length] 
})

3.3 获取url中的参数

let reg = /[?&]([^?&=]+)=([^?&=]+)/g

3.4 千分符

function format (num) {  
    var reg=/\d{1,3}(?=(\d{3})+$)/g;   
    return (num + '').replace(reg, '$&,');  
}

3.5 匹配手机号

let reg = /^1(36|58)\d{8}$/

3.6 验证有效数字

let reg = /^[+-]?(\d|[1-9]\d+)(\.\d+)?$/

3.7 验证范围内数字23~66

拆分为三段23-29,30-59,60-66

let reg = /(2[3-9])|([3-5]\d)|(6[0-6])/

3.8 验证邮箱

第一部分可以为\w和. - ,但是. -不能作为开头和结尾i,不能连续

第二部分域名只能为数字字母

let reg = /^\w+([-.]\w+)*@[\da-zA-Z]{2,6}(\.[a-z]{2,6}){1,2}$/

3.9 验证类型

function testType(data){
    let str = Object.prototype.tostring.call(data)
    let reg = /\[object (\w+)\]/
    return str.exec(reg)[1].toLowerCase()
}