JS — 正则表达式(RegExp)

用来处理字符串的规则

  • 只能处理字符串
  • 它是一个规则:可以验证字符串是否符合某个规则(test),也可以把字符串中符合规则的内容捕获到(exec/match…)
let str = 'good'
let reg = /\d+/
reg.test(str)  //false 不包含数字

str = '2021-09-10'
reg.exec(str)  //['2021', index: 0, inputs: '原始字符串']
复制代码

编写正则表达式

创建方式有两种

// 1. 字面量创建方式(两个斜杠之间的,都是用来描述规则的元字符)
let reg1 = /\d+/

// 2. 构造函数模式创建,有两个参数:元字符字符串,修饰符字符串
let reg2 = new RegExp('\\d+')
//当需要把变量加入到正则表达式中时,不能使用字面量的方式创建,只能用构造函数去创建
复制代码

正则表达式由两部分组成

  • 元字符
  • 修饰符
常用的元字符
// 1.量词元字符:设置出现的次数
*      零到多次
+      一到多次
?      零次或者一次
{n}    出现n次
{n, }  出现n到多次
{n, m} 出现n到m次

// 2.特殊元字符:单个或者组合在一起代表特殊的含义
\   转义字符(普通->特殊->普通)
.   除\n(换行符)以外的任意字符
^   以哪一个元字符作为开始
$   以哪一个元字符作为结束
\n  换行符
\d  0~9之间的一个数字
\D  非0~9之间的一个数字
\w  数字、字母、下划线中的任意一个字符
\W  非数字、字母、下划线中的任意一个字符
\s  一个空白字符(包含空格、制表符、换页符等)
\t  一个制表符(一个TAB键:四个空格)
\b  匹配一个单词的边界
x|y x或者y中的一个字符
[xyz] x、y、z中的一个字符
[^xy] 除了xy以外的任意一个字符
[a-z] 指定a-z这个范围中的任意一个字符 [0-9a-zA-Z_] === \w
[^a-z] 除了a-z这个范围中的任意一个字符
()     正则中的分组符号
(?:)   只匹配不捕获
(?=)   正向预查
(?!)   负向预查

// 3.普通元字符:代表本身的含义
/js/ 此正则匹配的就是 "js"
复制代码
常用修饰符:i m g
i: ignoreCase   忽略单词大小写匹配
m: multiline    可以进行多行匹配
g: global       全局匹配

/A/.test('lalalala')    //false
/A/i.test('lalalala')   //true
复制代码

元字符详细解析

^ $

let reg = /^\d/  //必须以数字开头
console.log(reg.test('js'))  //false
console.log(reg.test('2021js')) //true
console.log(reg.test('js2021')) //false

reg = /\d$/  //必须以数字结束
console.log(reg.test('js'))  //false
console.log(reg.test('2021js')) //false
console.log(reg.test('js2021')) //true

//^ $两个都不加:字符串中包含符合规则的内容即可
reg = /\d+/:字符串中包含1到多个数字即可

//^ $两个都加:字符串只能是和规则一致的内容
reg = /^\d+$/

//验证手机号(11为,以1开头)
reg = /^1\d{10}$/
复制代码

\

let reg = /^2.3$/   // .不是小数点,是\n外的任意字符
reg.test('2.3')  //true
reg.test('2@3')  //true
reg.test('23')   //false

reg = /^2\.3$/    //只能代表2.3了

//匹配"\d"
let str = "\\d"   //在字符串中\也有特殊含义
reg = /^\d$/  //不能匹配str
reg = /^\\d$/
reg.test("\\d")  //正确匹配str
复制代码

x|y

let reg = /^18|29$/
reg.test('18')
reg.test('29')
reg.test('129')
reg.test('189')
reg.test('1829')
reg.test('829')
reg.test('182')
//以上结果全为true

//------直接x|y会存在很乱的优先级问题,可以用小括号处理 => 小括号:分组
reg = /^(18|29)$/  //只能是18或者29
复制代码

[]

// 1. 中括号中出现的字符一般都代表字符本身的含义
let reg = /^[@+]+$/   //@或者+ 出现一到多次

reg = /^[\d]$/   // \d在[]中代表0-9的一个数字
reg = /^[\\d]$/  //  \ 或者 d

// 2. 中括号中不存在多位数
reg = /^[18]$/   //只能匹配1或者8,不能匹配18
reg = /^[10-29]$/   // 1或者0-2或者9
复制代码

常用正则表达式

  1. 验证是否为有效数字
/*
 * 规则分析
 * 1. 可能出现 + - 号,也可能不出现
 * 2. 一位0-9都可以, 多位时第一位不能是0
 * 3. 小数部分可能有可能没有,一旦有小数点,小数点后面一定有数字
 */
 let reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/
复制代码
  1. 验证密码
// 数字、字母、下划线  6——16位
let reg = /^\w{6, 16}$/
let flag = reg.test('密码字符串')
复制代码
  1. 验证真实姓名
/* 1. 只能是中文汉字 /^\u4E00-\u9FA5$/
 * 2. 名字长度 2-10位
 * 3. 可能有译名  ·汉字   (爱新觉罗·康熙)
 */
 let reg = /^[\u4E00-\u9FA5]{2, 10}(·[\u4E00-\u9FA5]{2, 10}){0, 2}$/
复制代码
  1. 验证邮箱
let reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/
// => \w+((-\w+)|(\.\w+))*
// 1. 开头是数字字母下划线(1到多位)
// 2. 还可以是 -数字字母下划线 或者 .数字字母下划线,整体0到多次
// => 邮箱的名字由"数字、字母、下划线、-、."几部分组成,但是-/.不能连续出现也不能作为开始

// => @[A-Za-z0-9]+
// 1. @后面紧跟着:数字、字母(1到多位)

// => ((\.|-)[A-Za-z0-9]+)*
// 1. 对@后面名字的补充 多域名  .com.cn
//企业域名 @aaa-bbb-ccc-ddd.com

// => \.[A-Za-z0-9]+
// 匹配最后的域名  .com(cn, org, edu, net, vip...)
复制代码
  1. 身份证号码
/*
 * 1. 一共18位
 * 2. 最后一位可能是X
 * 身份证前6位: 省市县 511623
 * 中间8位: 年月日
 * 最后4位:
 *     最后一位  => X 或者 数字
 *     倒数第二位 => 偶数:女  奇数:男
 */
 // => 小括号分组的第二个作用:分组捕获,不仅可以把大正则匹配的信息捕获到,还可以单独捕获到每个小分组的内容
 let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/
 
 reg.exec("511623199006141110")
 //["511623199006141110", "511623", "1990", "06", "10", "1", "0", ...] 捕获结果是数组,数组第一位是捕获到的完整的正则匹配到的信息,后面的是捕获到的小括号中匹配到的信息
复制代码

正则的捕获

实现正则捕获的方法
  • 正则RegExp.prototype上的方法
    • exec
    • test
  • 字符串String.prototype上支持正则表达式处理的方法
    • replace
    • match
    • splite

基于exec实现正则的捕获

  1. 捕获到的结果是null或数组
    • 第一项:本次捕获到的内容
    • 其余项: 对应小分组本次单独捕获的内容
    • index:当前捕获内容在字符串中的起始索引
    • input: 原始字符串
  2. 每执行一次exec,只能捕获到一个符合正则规则的,但是默认情况下,我们执行100遍,获取的结果永远是第一个匹配到的,其余的捕获不到,这就是正则捕获的懒惰性:默认只捕获第一个。
let str = 'abcde123fgh456igk789lmn'
let reg = /\d+/
//reg.lastIndex: 当前正则下一次匹配的起始索引位置,这个值不会被修改,所以匹配到的永远是第一个
console.log(reg.lastIndex)  //0 下面的匹配捕获是从str索引0的位置开始
//正则捕获的前提:当前正则要与字符串匹配。不匹配,捕获结果为null
console.log(reg.exec(str))  //["123", index: 5, input: "abcde123fgh456igk789lmn", groups: undefined]

reg = /\d+/g  //设置全局匹配修饰符后,每一次匹配完,lastIndex会自动修改,多次捕获后,当全部捕获后,再次捕获结果为null,lastIndex回归为初始值0,再次捕获又从第一个开始

if(reg.test(str)){ //这里匹配后,已经修改了lastIndex的值
    console.log(reg.exec(str)) //456 这里捕获的是第二个结果
}

//字符串中的match方法,可以在执行一次的情况下,捕获到所有匹配的数据(前提:正则设置了g)
console.log(str.macth(reg))  //["123", "456", "789"]
复制代码

正则的分组捕获

let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/
 
 reg.exec("511623199006141110")
 //["511623199006141110", "511623", "1990", "06", "10", "1", ...] 捕获结果是数组,数组第一位是捕获到的完整的正则匹配到的信息,后面的是捕获到的小括号中匹配到的信息
 //最后一项,加了()只是为了改变|的优先级,但是并不需要捕获,可以基于?:来处理,代表只匹配不捕获
复制代码
//既要匹配到{数字},也想单独匹配带数字 {0} 0...
let str = "{0}年{1}月{2}日"
//不设置g只匹配一次,exec和match获取的结果一致(既有大正则匹配的信息,也有小分组匹配的信息)
let reg = /\{(\d+)\}/     // ["{0}", "0", index: 0, input: "{0}年{1}月{2}日", groups: undefined]

let reg = /\{(\d+)\}/g
console.log(str.match(reg))  // ["{0}", "{1}", "{2}"]  全局匹配的情况下,match只能把大正则匹配的内容捕获到,小分组匹配的信息无法获取

let aryBig = [],
  arySmall = [],
  res = reg.exec(str)  //用exec一次一次获取
while (res) {  // 循环到res = null为止
  let [big, small] = res
  aryBig.push(big)
  arySmall.push(small)
  res = reg.exec(str) //没有捕获完就一直捕获
}
console.log(aryBig) //["{0}", "{1}", "{2}"]
console.log(arySmall) //["0", "1", "2"]
复制代码

分组的第三个作用:分组引用

分组引用就是通过”\数字”让其代表和对应分组出现一模一样的内容

let str = "book" //要求一共4个字母,中间两个字母一样
let reg = /^[A-Za-z]([A-Za-z])\1[A-Za-z]$/
console.log(reg.test(str))
复制代码

正则捕获的贪婪性

默认情况下,正则捕获时,是按照当前正则所匹配的最长结果来获取的

在量词元字符后面设置 ?, 代表取消捕获时的贪婪性(按照正则匹配的最短结果来获取)

let str = 'aaa1111@2222bbb'
let reg = /\d+/g
console.log(str.match(reg))  //["1111", "2222"]

reg = /\d+?/g    //["1", "1", "1", "1", "2", "2", "2", "2"]
复制代码
  • ?在正则中的五大作用:

    • 左边是普通元字符,本身代表量词元字符出现0或者1次
    • 左边是量词元字符,代表取消捕获时候的贪婪性
    • (?:) 只匹配不捕获
    • (?=) 正向预查
    • (?!) 负向预查
  • ()在正则中的三大作用

    • 改变优先级
    • 分组捕获
    • 分组引用 \数字

其他正则捕获的方法

  1. test也能捕获(本意是匹配)
let str = "{0}年{1}月{2}日"
let reg = /{(\d+)}/g
console.log(reg.test(str)) //true
console.log(typeof RegExp.$1) //"0"

console.log(reg.test(str)) //true
console.log(RegExp.$1) //"1"

console.log(reg.test(str)) //true
console.log(RegExp.$1) //"2"

console.log(reg.test(str)) //false
console.log(RegExp.$1) //"2"  存储的是上次捕获的结果

// RegExp.$1 ~ RegExp.$9 : 获取当前本次正则匹配后,第一个到第九个分组的信息
复制代码

2.replace 字符串中实现替换的方法(一般都是伴随正则一起使用的)

let str = 'abc000abc111' //把abc替换成xyz
//不用正则,执行一次只能替换一个
//str = str.replace('abc', 'xyz').replace('abc', 'xyz')
//console.log(str)  //xyz000xyz111

str = str.replace(/abc/g, 'xyz')
//console.log(str)  //xyz000xyz111

复制代码
let str = 'abc000abc111' //把abc替换成abcxyz
str = str.replace('abc', 'abcxyz').replace('abc', 'abcxyz')  //每一次捕获都是从字符串第一个位置开始找,类似于正则的懒惰性
console.log(str) // abcxyzxyz000abc111  

str = str.replace(/abc/g, 'abcxyz')
console.log(str) // abcxyz000abcxyz111
复制代码
let time = '2021-09-11' //变成"2021年09月11日"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/   //注意 {1,2} 逗号后面不能加空格,否则会匹配失败
console.log(reg.test(time))  //true
time = time.replace(reg, "$1年$2月$3日")
console.log(time)  //2021年09月11日
复制代码
通过上面的三小段代码,我们可看出replace方法有下面这三种特性:
1. 首先拿reg和time进行匹配,能匹配几次,就把传递进来的函数执行几次(而且匹配一次就执行一次)
2. 不仅执行了方法,replace还给方法传递了实参(和exec捕获的内容一致的信息,大正则匹配的内容,小分组匹配的信息)
3. 在函数中我们返回的是啥,就把当前大正则匹配的内容替换成啥。
复制代码
let time = '2021-09-11' //变成"2021年09月11日"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/
time = time.replace(reg, (...arg) => {
  let [, $1, $2, $3] = arg
  return $1 + '年' + $2 + '月' + $3 + '日'
})
console.log(time)  //2021年09月11日
复制代码

单词首字母大写

//单词首字母大写
let str = 'good good study, day day up!'
let reg = /\b([a-zA-z])[a-zA-z]*\b/g
str = str.replace(reg, (x, y) => {
  return y.toUpperCase() + x.slice(1)
})
console.log(str) //Good Good Study, Day Day Up!
复制代码

验证一个字符串中哪个字母出现的次数最多,有多少次

let str = 'jintianshixingqiliu'
let arr = str.split('')
arr = [...new Set(arr)]

let newArr = []
arr.forEach(item => {
  let tmp = {
    code: item
  }
  let i = 0
  str = str.replace(new RegExp(item + "{1}", 'g'), () => i++)
  tmp['num'] = i
  newArr.push(tmp)
})
newArr.sort((a, b) => b.num - a.num)
console.log(newArr[0]) //{code: "i", num: 6}
复制代码
let str = 'jintianshixingqiliu'
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[0].slice(0, 1), ary[0].length) //i, 6

let max = ary[0].length,
  res = [ary[0].slice(0, 1)]  // //可能有相同的最大数的字母
for (let i = 1; i < ary.length; i++) {
  let item = ary[i]
  if (item.length < max) {
    break
  }
  res.push(item.slice(0, 1))
}
console.log(res)
复制代码
let str = 'jintianshixingqiliu',
  res = [],
  max = 0, 
  flag = false  //是否已经找到
str = str.split('').sort((a, b) => a.localeCompare(b)).join('')
for (let i = str.length; i > 0; i--) {
  let reg = new RegExp("([a-zA-Z])\\1{" + (i - 1) + "}", "g")
  str.replace(reg, (content, $1) => {
    res.push($1)
    max = i
    flag = true
  })
  if (flag) break
}
console.log(res, max) //["i"] 6
复制代码

千分符

(function () {
 function millimeter() {
   return this.replace(/\d{1,3}(?=(\d{3})+$)/g, (content) => {
     return content + ','
   })
 }
 ['millimeter'].forEach(item => {
   String.prototype[item] = eval(item)
 })
})();
let num = '2645565264562'
console.log(num.millimeter()) //2,645,565,264,562
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享