你为什么学不会正则?

135 阅读5分钟

16abe9df51b66af0_tplv-t2oaga2asx-zoom-crop-mark_1304_1304_1304_734

背景

正则表达式一直是困扰很多程序员的一门技术。曾经在我的眼里,正则表达式就是一堆杂乱无章的符号,他不认识我,我也不认识他。再加上正则几乎很少出现在面试的过程中,所以对于正则我们也是更加的陌生。在实际的开发过程中需要用到正则的时候打谷歌直接搜索拷贝,甚至找到的正则都卡不懂。有人也会说,不用正则,对于我们的开发也没什么问题呀,为什么还要学这些奇奇怪怪的字符呢?那么我们来看一个小小的案例:

验证密码,规则

  • 数字、字母、下划线
  • 6~16位

如果不用正则我们可能写出如下的方法:

const password = 'zhuanzhuan0624'

function checkPass(val) {
  if (val.length < 6 || val.length > 16) {
    alert('长度必须介于6-16位之间')
    return
  }
  const area = ['a’', 'b', 'c', 'd','_'] // 包含数字、字母、下划线,此处只列举的一部分
  for (let i = 0; i < val.length; i++) {
    const chat = val[i];
    if (!area.includes(chat)) {
      alert('您的输入存在非法字符')
      return
    }
  }
}

checkPass(password)

那如果我们使用了正则呢?

const password = 'zhuanzhuan0624'
const passwordReg = /^\w{6,16}$/
console.log(passwordReg.test(password));

这是开发中最常用的正则之一吧,除此之外还有用户名、解析url、千分位、有效数字判断等等,文末有大量常用正则

有人就不服了,正则就这吗,能帮到我们面试吗?行,那再来看一到面试题:

获取字符串中出现次数最多的字母和次数

解题的方式有很多,相信大家都已经很熟悉了,那么我们直接看看用正则怎么处理?

function checkStr(str) {
  // 排序
  const newStr = str.split('').sort((a,b) => a.localeCompare(b)).join('')
  // 分组引用
  const reg = /([a-zA-Z])\1+/g
  return newStr.match(reg).sort((a,b) => b.length -a.length)[0]
}
console.log(checkStr('aaabbdddddxxfrr')); // ddddd

除此之外还有很多,比如:

image-20220501144234026

就不再这里一一列举了。正则是一门艺术,我相信深入理解正则表达式并能融会贯通是很有意义的,对自己的提升也是很大的。当你学完正则再去看vue解析模版时候使用的正则表达式就很轻松了,也可以让我们更好的理解代码。当下次再遇到正则表达式的时候可以不借助搜索引擎,自己解决。

什么是正则

当我学习完正则以后,我自己对于正则的理解:

一种处理字符串的规则,验证字符串是否符合某种规则

那么规则怎么来呢?规则由特定的符号进行组合,组合的结果就是正则表达式。 在我心里就把他们当作符号就行了,每个符号代表不同的含义,我们只要记住常用符号的含义,然后就可以可以自由组合成不同的规则了。

那么正则都有哪些种类的符号呢?简单的进行一下分类:

  • 元字符:简单的说就是写在 / /之间的符号
    • 量词元字符
    • 特殊元字符
  • 修饰符:控制正则执行的行为

其实我们要记忆的东西也不是很多,接下来深入了解一下常见的符号(元字符和修饰符)。

元字符

量词元字符

设置出现的次数

  • * 零到多次
  • + 一到多次
  • ? 零次或者一次
  • {n} 出现n次
  • {n,} 出现n到多次
  • {n,m} 出现n到m次

特殊元字符

单个或者组合在一起代表特殊的含义

  • \ 转义字符(普通->特殊->普通)
  • . 除\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这个范围中的任意字符
  • [^a-z] 上一个的取反“非”
  • \b 匹配一个单词的边界
  • () 正则中的分组符号
  • (?:) 只匹配不捕获
  • (?=) 正向预查
  • (?!) 负向预查

以上的元字符是我们最常见的,也不多,共26个,一会写几个案例,不用背,就记下来了!

关注微信公众号【WEB大前端】,也可在文末进行扫码,回复【正则元字符】,可免费获取完整的元字符及其含义列表的PDF文件

修饰符

  • i 忽略单词大小写匹配
  • g 全局匹配
  • m 可以进行多行匹配

修饰符相比于元字符就少很多啦,接下来先练习一下元字符!

元字符练习

^ $

^: 以哪一个元字符作为开始 $: 以哪一个元字符作为结束,

// 匹配以数字开头
const reg = /^\d/
console.log(reg.test("zhuanzhuan")) // false
console.log(reg.test("2022,I love zhuanzhuan more")) // true
console.log(reg.test("I love zhuanzhuan more, 2022")) // false

// 匹配以数字结尾
const reg = /\d$/
console.log(reg.test("zhuanzhuan")) // false
console.log(reg.test("2022,I love zhuanzhuan more")) // false
console.log(reg.test("I love zhuanzhuan more, 2022")) // true

// ^$ 两个都不加:字符串中包含符合规则的内容即可
const reg = /\d/
console.log(reg.test("zhuanzhuan")) // false
console.log(reg.test("2022,I love zhuanzhuan more")) // true
console.log(reg.test("I love zhuanzhuan more, 2022")) // true

// ^$ 两个都加:字符串只能是和规则一致的内容
const reg = /^\d$/
console.log(reg.test("zhuanzhuan")) // false
console.log(reg.test("2022,I love zhuanzhuan more")) // false
console.log(reg.test("I love zhuanzhuan more, 2022")) // false
console.log(reg.test("2022")) // false, 思考? 匹配 2022 的正则表达式应该怎么写呢?
console.log(reg.test("2")) // true

// 简单的案例:验证手机号码(11位,第一个数字是1即可, 此处只做简单的案例学习使用)
const reg = /^1\d{10}$/
console.log(reg.test("18372635819")) // true 
console.log(reg.test("48372635819")) // false 

\

转义字符(普通->特殊->普通)

const str = '\\d'
const reg1 = /^\d$/
console.log(reg1.test(str)); // false 此处的 \d 表示 0-9的数字
const reg2 = /^\\d$/ // 把特殊符号转换为普通符号
console.log(reg2.test(str)); // true 

const str2 = '\d'
console.log(reg2.test(str2)); // false why?  字符串中的\ 也需要转译, 这里可以先记住,进阶内容中会有相关说明

x|y

x或者y中的一个字符

const reg = /^18|37$/
console.log(reg.test("18")) // true
console.log(reg.test("37")) // true
console.log(reg.test("137")) // true
console.log(reg.test("189")) // true
console.log(reg.test("1837")) // true
console.log(reg.test("837")) // true
console.log(reg.test("182")) // true

// 直接x|y会存在很乱的优先级问题,一般我们写的时候都伴随着小括号 进行分组,因为小括号改变处理的优先级 小括号:分组
const reg2 = /^(18|37)$/
console.log(reg2.test("18")) // true
console.log(reg2.test("37")) // true
console.log(reg2.test("137")) // fa1se
console.log(reg2.test("189")) // false 
// 只能是18或者37中的一个了

[]

1.中括号中出现的字符一般都代表本身的含义

const reg = /^[@+]$/ // 此处的 + 就是+本身,并不代表 一到多次
console.log(reg.test("@")) // true
console.log(reg.test("+")) // true
console.log(reg.test("@@")) // false
console.log(reg.test("@+")) // false

const reg2 = /^[\d]$/ // \d在中括号中还是0-9
console.log(reg2.test("d")) // false
console.log(reg2.test("\\")) // false
console.log(reg2.test("9")) // true

2、中括号中不存在多位数

const reg = /^[18]$/ 
console.log(reg.test("1")) // true
console.log(reg.test("8")) // true
console.log(reg.test("18")) // false

const reg2 = /^[10-49]$/ // 1或者0-4或者9
console.log(reg2.test("1")) // true
console.log(reg2.test("9")) // true
console.log(reg2.test("0")) // true
console.log(reg2.test("2")) // true
console.log(reg2.test("10")) // false
console.log(reg2.test("3")) // true
console.log(reg2.test("20")) // false

认真理解这几个元字符对我们下面的案例很重要!

小试牛刀

正则就是规则,那么先搞清楚需要匹配什么字符串,规则是什么?

验证是否为有效数字

/**
 * 规则分析
 * 1.可能出现 + - 号,也可能不出现, 对应正则: [+-]?
 * 2.一位0-9都可以,多位首位不能是0 ,对应正则:  (\d|([1-9]\d+))
 * 3.小数部分可能有可能没有,一旦有后面必须有小数点+数字,对应正则: (\.\d+)?
 */

const effectiveNum = [0, '+2022', '-2022', '0.2' ,'-2.2' , '3.1415926']

// 将上述的规则进行合并,就可以得到完整的正则表达式
const reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/
effectiveNum.forEach( num => console.log(reg.test(num)) )

验证密码

/**
 * 规则分析
 * 1、数字、字母、下划线, 对应正则: \w
 * 2、6~16位, 对应正则: {6,16}
 */

// 将上述的规则进行合并,就可以得到完整的正则表达式
const reg = /^\w{6,16}$/
const pwd1 = 'zhuanzhuan0624'
const pwd2 = 'z0624'
console.log(reg.test(pwd1)) // true
console.log(reg.test(pwd2)) // false

验证用户名

/**
 * 规则
 * 1、汉字, 对应正则: /^[\u4e00-\u9fa5]$/
 * 2、名字长度 2~10位, 对应正则: {2,10}
 * 3、可能有译名.汉字 /(·[\u4e00-\u9fa5]{2,10}){0,2}/
 */

// 将上述的规则进行合并,就可以得到完整的正则表达式
const nameReg =/^[\u4e00-\u9fa5]{2,10}(·[\u4e00-\u9fa5]{2,10}){0,2}$/ 
const name1 = '弗拉基米尔·弗拉基米罗维奇·普京'
const name2 = '杨大仙'
console.log(nameReg.test(name1)) // true
console.log(nameReg.test(name2)) // true

邮箱

const reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
const email = '1806328384@qq.com'
console.log(reg.test(email))

=> \w+((-\w+)|(.\w+))*

1.开头是数字字母下划线(1到多位)

2.还可以是-数字字母下划线或者. 数字字母下划线,整体零到多次

=> 邮箱的名字由“数字、字母、下划线、-、. ”几部分组成,但是-/.不能连续出现也不能作为开始

=> @[A-Za-z0-9]+

  1. @后面紧跟着:数字、字母(1-多位)

=> ((.|-)[A-Za-z0-9]+)*

1.对@后面名字的补充

多域名 .com.cn

企业邮箱 admin@jx-technology.com

=> .[A-Za-z0-9]+

  1. 这个匹配的是最后的域名 (.com/.cn/.org/.edu/.net... )

身份证

规则:

1、一共18位 2、最后一位可能是X 3、身份证前六位:省市县 4、中间八位:年月日 5、最后四位:

  • 最后一位=> X或者数字
  • 倒数第二位=>偶数女奇数男
  • 其余的是经过算法算出来的
// 小括号分组的第二个作用:分组捕获,不仅可以把大正则匹配的信息捕获到,还可以单独捕获到每个小分组的内容。 下一篇文档会讲到,此时可以先略过
const reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/;
console.log(reg.exec("42068419960526751X"))

// [
//   '42068419960526751X',
//   '420684',
//   '1996',
//   '05',
//   '26',
//   '1',
//   'X',
//   index: 0,
//   input: '42068419960526751X',
//   groups: undefined
// ]

idea:目前已经拿到身份证的相关信息,如果我们获取到前六位的所有的数据信息,是不是就可以做一个身份证查询的接口喽!

总结

这里我们已经接触到了捕获,也是到了这篇文章的尾声,相信读到这里,对于正则已经不在恐惧了吧,算是已经入了个门。细细品尝,其实正则也很好玩,下一篇会介绍正则的捕获、贪婪、懒惰及相关的原理,下节更精彩!最后留一个 “做人不能太正则!” ,谜底下篇揭晓。

本文属于个人理解,如有误,请斧正!

大量的正则表达式: any86.github.io/any-rule/

——本文首发于个人公众号,转载请注明出处———

WEB大前端

最后,欢迎大家关注我的公众号,一起学习交流。