关于前端正则的一些复习 | 8月更文挑战

205 阅读6分钟

正则 RegExp

前言

难得到了周末,终于可以有时间再写一下文章了. 今天的话,我们就复习一下正则表达式吧, 正则表达式在我们日常的工作中可以说是用到的非常多了. 比如写一个表单校验的时候,基本上都少不了它. 好了,废话不多说,下面我们就开始了!

写法

  • 字面量 /\d+/
  • 构造函数 new RegExp("\d+") 首先先从它的写法开始说起, 正则是有两种写法的, 一种是字面量写法, 一种是构造函数的写法. 在一般情况下我们基本上都是用的字面量写法,原因无它,更简洁! 如果是用构造函数的写法的话, 还需要new一下,不仅如此,我们填写的参数由于是一个字符串的规则,比如我们写一个匹配数字的规则\d,那么\还得进行转义. 可以说是非常麻烦了. 但是这种构造函数的形式是不是就一点用处都没有呢? 其实也不是, 如果我们碰到一些动态规则的时候, 那么我们就必须得用构造函数的形式去写了. 由于构造函数方式写的是一个字符串, 所以我们可以在规则里边写入变量. 达到规则的灵活性. 我们可以举个例子, 假如我们需要写一个通用的校验, 方法里边传入一个变量,校验输入框输入的值必须是数字,且长度位变量传进来的数值. 那么我们就可以这么写.
// 检验输入的是否是数字,且数字长度为count
function validateNum (count) {
   return function (rule, value, callback) {
        var reg = new RegExp(`^\\d\{${count}\}$`) // 
        const flag = reg.test(value)
        if (flag) {
            callback()
        } else {
            callback(new Error('校验错误!'))
        }
   }
}
// 调用校验
rules: { 
    pwd: [ { validator: validateNum(8), trigger: 'change' },
    phone: [ { validator: validateNum(11), trigger: 'change' }
]

以上就写好了一个通用的校验长度为count,并且输入的为数字的一个校验规则了. 到这里大家应该了解了使用构造函数的场景了吧! 那么下面我们就从正则的元字符开始一点点的讲解正则的用法吧.

元字符

量词元字符

+ 一个或多个

* 0个或多个

? 0个或1个

{m} m个

{m,} 至少m个

{m, n} m到n个

特殊元字符

\d 0-9的数字

\w 数字、字母、下划线

\W 除了(数字、字母、下划线)的字符 [其它的字符也一样,大写表示小写相反的意思]

\s 空白符

\t 制表符

\b 单词边界

\n 换行符

a|b a或者b

[a-b] a-b范围内的元素

^ 从某一个字符开始

$ 从某一个字符结束

() 分组

修饰符

i 忽略大小写

m 多行匹配

g 全局匹配

重点解析

^、$

^表示以什么开头,$表示以什么结尾

var reg = /^[1|0]+$/ // 表示从开头到结尾都必须是0或者1
console.log(reg.test(1)) // true
console.log(reg.test(0)) // true
console.log(reg.test(10)) // true
console.log(reg.test(120)) // false

var reg = /[1|0]+/ // 表示字符中包含1或者0
console.log(reg.test(1)) // true
console.log(reg.test(0)) // true
console.log(reg.test(10)) // true
console.log(reg.test(120)) // true

如果在正则中写了^开头和$结尾的话, 则证明匹配的是全部的规则. 就是说匹配的字符得所有字符都满足规则, 如果没有写的话, 则匹配的规则则是包含的意思, 也就是说只要匹配的字符里边有满足条件的. 则为true.

|

|表示的意思, a|b就是匹配a或者b中任意一个字符, 虽然看起来很简单. 但是结合我们前面介绍的^、$一起来看一个例子吧

var reg = /^12|34$/

1. console.log(reg.test(123))
2. console.log(reg.test(124))
3. console.log(reg.test(134))
4. console.log(reg.test(234))
5. console.log(reg.test(12))
6. console.log(reg.test(34))
7. console.log(reg.test(1234))

这些最后返回的值各是什么呢? 大家可以自己先思考一下. 为了让大家有一个小小的思考过程. 我把具体答案以及解析放到后边()的地方讲解.

[]

[]里边可以用-描述一个范围的值. 大部分的特殊字符在[]里边描述的其实就是它本身的意思,并不是转义的意思. 比如.*+(等,当然也有一些还是其转义的意思, 比如\d. 下边我们写一个例子. 大家同样先思考一下

var reg =  /^[(a-z)(1-10)+(A-Z)]+$/
console.log(reg.test('a10Z'))
console.log(reg.test('(1)'))
console.log(reg.test('(abc)'))
console.log(reg.test('(a2z)'))

()

小括号是可以用来分组捕获的, 并且也可以用来去规划一下规则的优先级的. 我们先做分组捕获吧. 比如我想在用户的身份证信息里边提取出来他的出生年月和所在的省市区的话,我们可以这么写

let regTest = /^(\d{6})(\d{4})(\d{2})(\d{2})[\dX]{4}$/
regTest.exec('320622199612055119') // ["320622199612055119", "320622", "1996", "12", "05", index: 0, input: "320622199612055119", groups: undefined]

得到的结果是一个数组: 第一位是匹配的字符串, 第二位以后则是按顺序分组捕获的信息. 我们拿到的前六位数字就是省市区的编码,我们可以根据编码找到用户所处的省市区,然后再拿到用户的出生年月日. 好了,说完分组捕获的作用,另外一个作用就是可以提高规则的优先级. 还记得我们之前的|例子嘛? 我们还没有公布答案呢. 其实|里边console出来的所有的都是true. 因为上边写的规则var reg = /^12|34$/, 因为这个规则的意思是:以开头是12,或者结尾是34的字符串. 但是其实我们有可能想要的规则并不是这样, 而是以1开头以4结尾,中间是2或者3的字符串. 这样的话,其实我们就要var reg = /^1(2|3)4$/这样用括号包起来,这样才是我们想要的意思.

exec与match

我们先直接看一个例子:

let text = '我稀罕你1314,哈哈哈520'

我希望捕获到上边的1314和520两个数字. 那么我们写上代码

let reg = /\d+/
reg.exec(text) // ["1314", index: 4, input: "我稀罕你1314,哈哈哈520", groups: undefined]
reg.exec(text) // ["1314", index: 4, input: "我稀罕你1314,哈哈哈520", groups: undefined]

执行exec的是否,我们发现我们只捕获到了第一个匹配的,并且无论再执行多少次,都是一样的. 然后我们这时候加上全局/g试试看

let reg = /\d+/g
reg.exec(text) // ["1314", index: 4, input: "我稀罕你1314,哈哈哈520", groups: undefined]
reg.exec(text) // ["520", index: 12, input: "我稀罕你1314,哈哈哈520", groups: undefined]
reg.exec(text) // null
reg.exec(text) // ["1314", index: 4, input: "我稀罕你1314,哈哈哈520", groups: undefined]

这时候我们发现,执行第一次也只能拿到第一个匹配的,但是如果再执行一次的话,则拿到了第二个匹配的. 看到以上的规律,我们可以实现一个方法,一次性拿到我们想要的所有的匹配的数据

let reg = /\d+/g
RegExp.prototype.execAll = function (val) {
       let result = []
       let loop
       do {
           loop = this.exec(val)
           if (loop) {
               result.push(loop[0])
           }
       } while(loop) 
       return result
}
reg.execAll(text) // ["1314", "520"]

这样的话,就达到了我们的需求了. 但是还需要在原型链上添加一个我们自己的方法. 这样做感觉还是比较复杂. 就没有现成的方法可以给我们用吗? 答案是有的. 但是不在构造函数的方法上, 而是在字符串的原型链的方法里.

let reg = /\d+/g
"我稀罕你1314,哈哈哈520".match(reg) // ["1314", "520"]

正则的懒惰模式和贪婪模式

讲这个的话, 我们还是回到上边那个例子

let text = '我稀罕你1314,哈哈哈520'
let reg = /\d+/g
reg.execAll(text) // ["1314", "520"]

大家有没有想过一个问题, 为什么匹配的是1314, 520? 我写的规则是: \d+\, 它的意思是: 一个或者一个以上, 那么按照这个规则匹配的话, 为什么给我的结果不能是: [1,3,1,4,5,2,0] 呢? 这里的话, 就是因为正则表达式匹配的贪婪性, 它匹配的时候,会按照匹配规则的最大长度去匹配. 所以1314, 它就会匹配到1314, 那么如果我们不想要匹配到1314,就想要一个一个的匹配怎么办呢? 这就要把正则匹配改成懒惰模式,如下:

let text = '我稀罕你1314,哈哈哈520'
let reg = /\d+?/g
text.match(reg) // ["1", "3", "1", "4", "5", "2", "0"]

对的, 如果我们在量词+后边加上一个?的话, 就能取消正则的贪婪默认,改成懒惰模式.

写在最后

好了, 今天就到这里咯. 希望能给大家带来一些帮助, 顺便也给自己复习一下. 在最后给大家推荐一个网站 它可以让我们写的正则可视化,这样可以更好的帮助我们理解. 那么下次见了~