JS 正则篇

138 阅读6分钟

基础

1. 零宽断言

写法名称含义
exp1(?=exp2)正向前瞻查找exp2前面的exp1
(?<=exp2)exp1负向前瞻查找exp2后面的exp1
exp1(?!exp2)正向后瞻查找后面不是exp2的exp1
(?<!exp2)exp1负向后瞻查找前面不是exp2的exp1

2.replace()方法与正则约定的特殊标记符$

写法含义
$i(i:1-99),表示从左到右正则子表达式所匹配的文本
$&表示与正则表达式匹配的全文本
$``:切换技能键。表示匹配字符串左边的文本
?表示$转移

3. 分组语法:

写法含义
捕获
(exp)匹配exp,并捕获到自动命名到组里
(?exp)匹配exp,并捕获文本到名称为name到组里
(?:exp)匹配exp,不捕获匹配的文本
位置指定
exp1(?=exp2)查找exp2前面的exp1
(?<=exp2)exp1查找exp2后面的exp1
exp1(?!exp2)查找后面不是exp2的exp1
(?<!exp2)exp1查找前面不是exp2的exp1
注释
(?#comment)这种类型的组不对正则表达式对处理产生任何影响,只是为了提供让人阅读注释

4. 具有特殊意义的元字符

  \d -> 匹配一个0-9的数字,相当于[0-9],和它相反的是\D ->匹配一个除了0-9的任意字符
   \w -> 匹配一个0-9、a-z、A-Z、_的数字或字符,相当于[0-9a-zA-Z_]
   \s -> 匹配一个空白字符(空格、制表符...)
   \b -> 匹配一个单词的边界
   \t -> 匹配一个制表符
   \n -> 匹配一个换行
   . -> 匹配一个除了\n以外的任意字符
   ^ -> 以某一个元字符开头
   $ -> 以某一个元字符结尾
   \ -> 转义字符
   x|y -> x或者y的一个
   [xyz] -> x、y、z中的任意一个
   [^xyz] -> 除了xyz中的任意一个字符
   [a-z] -> 匹配a-z中的任意一个字符
   [^a-z] -> 匹配除了a-z中的任意一个字符
   () -> 正则中的分组
   ()\1 -> 正则中的分组内容重复匹配一次,也就是()内的内容是重复一次显示的如:book oo就是重复匹配

5. 代表出现次数的"量词元字符"

    ->+ : 出现一到多次
    ->* : 出现零到多次
    ->? : 出现零到一次
    ->{n} : 出现n次
    ->{n,} : 出现n到多次
    ->{n,m} : 出现n-m次

6.?在正则中的五大作用:

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

7.()在正则中的三大作用

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

8. 正则捕获

  • exec的依次执行结果,由lastIndex决定,match可以一次性匹配到所有的值,但是无法匹配分组的值,replace方法可以匹配到所有的值以及分组的值

  • 匹配值:捕获到的结果是null或数组

    • 第一项:本次捕获到的内容
    • 其余项: 对应小分组本次单独捕获的内容
    • index:当前捕获内容在字符串中的起始索引
    • input: 原始字符串
  • 懒惰性:如果正则为全局匹配,每次执行exec后如果有匹配结果,返回结果,并且该次匹配后lastIndex会修改成当前匹配内容最后一个字符的在原字符串中索引,如果没有匹配到结果,返回null,lastIndex索引会变成0,如下题目:

// 1.
var str = "duffy2016peixun2017"; 
var reg = /\d+/; 
console.log(reg.lastIndex)
console.log(reg.exec(str));
console.log(reg.lastIndex)
console.log(reg.exec(str));
console.log(reg.lastIndex)
    
console.log(str.match(reg))

// 2.

var str = "duffy2016peixun2017"; 
var reg = /\d+/g; 
console.log(reg.lastIndex)
console.log(reg.exec(str));
console.log(reg.lastIndex)
console.log(reg.exec(str));
console.log(reg.lastIndex)
console.log(reg.exec(str));
console.log(reg.lastIndex)
    
console.log(str.match(reg))
    
// 3. 
const myRe = /ab*/g;
const str = 'abbcdefabh';
let myArray;
while ((myArray = myRe.exec(str)) !== null) {
let msg = `Found ${myArray[0]}. `;
msg += `Next match starts at ${myRe.lastIndex}`;
console.log(msg);
}

    
// 4.题目考查
    
let reg=/\d+/g;  
let a;  
while(a=reg.exec('sfsf2016sfs2017')){  
    console.log(a)  
}
    
// 5.
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"]
    
// 6.
let str = '3.1415'
let reg = /\d+(?!\.)/
reg.exec(str)  // [1415]   (1)
    

// 7. let str = 'abcd' let reg1 = /[a-c]+/ let reg2 = /[^d]$/ reg1.test(str) // true (1) reg2.test(str) // false (2)

  • 分组捕获

//既要匹配到{数字},也想单独匹配带数字 {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"]
  • 分组引用:()\1
// 1.
let str = "book" //要求一共4个字母,中间两个字母一样
let reg = /^[A-Za-z]([A-Za-z])\1[A-Za-z]$/
console.log(reg.test(str))
    
// 2.
let str = 'xuxi is xuxi is'
let reg = /(xuxi) (is) \1 \2/g
reg.test(str)  // true    (1)
str.replace(reg, '$1 $2')  // xuxi is  (2)
  • 贪婪性: 默认情况下正则采用贪婪算法,按照当前正则匹配到的最长结果来获取,通过在量词元字符后设置?可以取消贪婪算法;
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"]
    
  • 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 : 获取当前本次正则匹配后,第一个到第九个分组的信息

9. 字符串支持正则的方法

  • search
let str = "hello world!";
console.log(str.search(/hello/) != -1);
// 只有一个参数, 并且是一个正则表达式对象, 如果传入一个非正则表达式对象,
// 则会使用 new RegExp(obj)隐式地将其转换为正则表达式对象
// 如果匹配成功, 则返回正则表达式在字符串中首次匹配项的索引, 否则, 返回-1
复制代码
  • match
let str = "hello world!";
console.log(!!str.match(/hello/g));
// 如果传入一个非正则表达式对象, 则会隐式地使用new RegExp(obj)将其转换为一个RegExp
// 返回值(数组), 如果匹配到数组第一项是匹配的完整字符串, 之后项是用圆括号捕获的结果, 如果没有匹配到, 返回null
// 如果正则表达式包含g标志, 则该方法返回一个Array, 它包含所有匹配的子字符串而不是匹配对象
  • replace

 // 1.
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
  
// 2.
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日

  • split

10. 词边界和非单词边界匹配

// 1.
let str = 'xuxi sa'
let reg1 = /\bsa\b/g
reg1.exec(str) // sa
    
let reg2 = /\bsa/g
reg2.exec(str) // sa

let reg3 = /sa\b/g
reg3.exec(str) // sa

let reg4 = /sa\B/g
reg4.exec(str) // null

let reg5 = /\Bsa/g
reg5.exec(str) // null

题目

  • 字符串替换:
// 原字符串:
`<div id="app"><span class="app" src="app" name="app">class="app" </span><p attr="apps"></p></div>`

// 1.如何将上述字符串中id="app" 和 class="app"替换成id="root" 和 class="root"
`'<div id="root"><span class="root" src="app" name="app">class="app" </span><p attr="apps"></p></div>'`
// 代码:
'<div id="app"><span class="app" src="app" name="app">class="app" </span><p attr="apps"></p></div>'.replace(/(?<=(id|class)=")app(")(?!<|(\s+<))/g, 'root$2')

// 2.将上述字符串中每个dom节点,最后一个属性值为app的替换成root,如上述字符串替换后的结果为:
`<div id="root"><span class="app" src="app" name="root">class="app" </span><p attr="apps"></p></div>`
// 代码:
`<div id="app"><span class="app" src="app" name="app">class="app" </span><p attr="apps"></p></div>'.replace(/\bapp(")(?=>|(\s+>))/g, 'root$1')`

// 3.将上述字符串中每个dom节点,第一个属性值为app的替换成root,如上述字符串替换后的结果为:
'<div id="root"><span class="root" src="app" name="app">class="app" </span><p attr="apps"></p></div>'
// 代码:
`<div id="app"><span class="app" src="app" name="app">class="app" </span><p attr="apps"></p></div>'.replace(/(?<=<\w+\s+\w+=")app(?=")/g, 'root')`

// 4.将上述字符串中每个dom节点,每一个属性值为app的替换成root, 除name属性外,如上述字符串替换后的结果为:
'<div id="root"><span class="root" src="root" name="app">class="app" </span><p attr="apps"></p></div>'
// 代码:
`<div id="app"><span class="app" src="app" name="app">class="app" </span><p attr="apps"></p></div>'.replace(/(\b(?<!name)=")app(")(?!<|(\s+<))/g, '$1root$2')`

  • 如何给一串数字用千分制表示?比如9999999999变成9,999,999,999
'99999999999.02'.replace(/\d{1,3}(?=(\d{3})+(?:\.\d+)?$)/g, '$&,')
输出:"99,999,999,999.02"

'99999999999.33333333'.replace(/\d{1,3}(?=(\d{3})+(?:\.\d+)?$)/g, '$&,')
输出:"99,999,999,999.33,333,333"

  • 验证一个字符串中哪个字母出现的次数最多,有多少次
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 trimTag(tagName, htmlStr) {
  let reg = new RegExp(`<${tagName}(\\s.*)*>(\\n|.)*<\\/${tagName}>`, "g")
  return htmlStr.replace(reg, '')
}
  • 短横线命名转驼峰命名
// 短横线转驼峰命名, flag = 0为小驼峰, 1为大驼峰
function toCamelCase(str, flag = 0) {
  if(flag) {
    return str[0].toUpperCase() + str.slice(1).replace(/-(\w)/g, ($0, $1) => $1.toUpperCase())
  }else {
    return str.replace(/-(\w)/g, ($0, $1) => $1.toUpperCase())
  }
}
  • 去除url参数字符串中值为空的字段
// 去除url参数字符串中值为空的字段
const trimParmas = (parmaStr:string = '') => {
  return parmaStr.replace(/((\w*?)=&|(&\w*?=)$)/g, '')
}
  • 实现搜索联想功能
function searchLink(keyword) {
  // 模拟后端返回数据
  let list = ['abc', 'ab', 'a', 'bcd', 'edf', 'abd'];
  let reg = new RegExp(keyword, 'i');
  return list.filter(item => reg.test(item))
}