js正则表达式详解及个人经验总结(正反向预查,分组捕获,反向引用,子表达式...等等)

正则表达式

regular expression:RegExp

用来处理字符串的规则

  • 只能处理字符串
  • 它是一个规则:可以验证字符串是否符合某个规则(test),也可以把字符串中符合规则的内容捕获到(exec / match…)

编写正则表达式

创建方式有两种:

  • 字面量
  • 构造函数
//=>字面量创建方式(两个斜杠之间包起来的,都是用来描述规则的元字符)
let reg1 = /\d+/g;//=>构造函数模式创建  两个参数:元字符字符串,修饰符字符串
//=>构造函数因为传递的是字符串,\需要写两个才代表斜杠
let reg2 = new RegExp("\\d+","g");

字面量和构造函数创建的区别:
                      构造函数创建的正则表达式可以使用变量,比较灵活

	let type = "aaa";
    //这种情况只能使用构造函数方式(因为它传递的规则是字符串,只有这样才能进行字符串拼接)
    reg = new RegExp("^@" + type + "@$");
    console.log(reg.test("@aaa@")); //=>true
    console.log(reg.test("@aaaa@")); //=>false

总结:如果想把一个变量的值作为正则元字符的一部分,只能通过构造函数的方式创建正则

正则表达式由两部分组成:

  • 元字符
  • 修饰符

常用元字符:

1.量词元字符,设置出现的次数
           *     			零到多次
           +     			一到多次
           ?    			零次或者一次
           {
    
    n}   		出现n次
           {
    
    n,}  		出现n到多次
           {
    
    n,m} 		出现n到多次
           
2.特殊元字符,单个或者组合在一起代表特殊的含义
          \      	   转义字符
           .     	   除\n(换行符)以外的任意字符
 		 ^  	   	   以哪一个元字符作为开始	
		 		let reg=/^\d/    
				console.log(res.test("fDGng"))       //false
				console.log(res.test("2019SFfe"))   //true
				console.log(res.test("egswb2019"))   //false
					
		$      	   以哪一个元字符作为结束
				let reg=/\d$/    
				console.log(res.test("frdres"))       //false
				console.log(res.test("2019sesefe"))   //false
				console.log(res.test("wfasg2019"))   //true
				
		\n     	   组合元字符,表示换行,\把n转译成了换行符
		\d           0-9之间的数字
		\D0-9之间的数字(大写和小写的意思是相反的)
		\w      	    数字,字母,下划线中的任意一个字符
		\W          除所有字母,数组,下划线外的字符(大写和小写的意思是相反的)
		\s            一个空白字符(包含空格,制表符,换页符等)=[\r\n\t\v\f]
		\S           匹配非空格的字符(大写和小写的意思是相反的)
		\t            一个制表符(一个TAB键,四个空格)
		\b            匹配一个单词的边界
		\B            匹配一个非单词的边界
		\n            匹配一个换行符 
		\r             匹配一个回车符
		x|y           x或者y中的一个字符 
	   [xyz]        x或者y或者z中的一个字符
		[^xy]       除了x/y以外的任意字符
		[a-z]        指定a-z这个范围中的任意字符 [0-9a-zA-Z_]===\w
		[^a-z]      非a-z这个范围中的任意字符
		[\u4E00-\u9FA5]    汉字范围     
	    	()           正则中的分组符号
	       (?:)        只匹配不捕获,不让他捕获该子表达式(match,matchAll)
					let reg = /(?:b)(c)/
					let str = "abcabc"
					console.log(str.match(reg))  //["bc", "c"]
					console.log(...str.matchAll(reg))//["bc", "c"]

			(?=)   正向预查,表示匹配后面紧挨着的字符的字符串
			(?!)    反向预查,表示匹配后面不是该字符的字符串
					let str = "abcacb"
					let reg1 = /a(?=b)/g   //匹配a后面紧挨着是b的字符串
					let reg2 = /c(?!b)/g   //匹配c后面不是b的字符串
					console.log(str.match(reg1))  //["a"]
					console.log(str.match(reg2))  //["c"]
					
			(?<=pattern)xxx   反向预查,表示匹配紧跟pattern后面的xxx

^$和转义字符\详解:

		let reg=/\d+/      //^,$都不加,表示字符串包含符合规则的内容即可
		let reg=/^\d+$/    //^,$都加,表示字符串只能是和规则一致的内容
-------------------------------------------------------------------------------------------
		let reg=/^2.3$/   
		console.log(reg.test("2f3"))   //true  
		console.log(reg.test("2.3"))  //true
		console.log(reg.test("23"))   //false
		console.log(reg.test("2fsef3"))  //false    
-------------------------------------------------------------------------------------------
		let reg=/^2\.3$/   //加了/(转译字符),只能让他代表小数点
		console.log(reg.test("2f3"))   //false 
		console.log(reg.test("2.3"))   //true 

中括号[ ]详解 :

  1. 中括号中出现的字符一般都代表本身的含义
  2. \d在中括号中还是0-9
  3. 中括号中不存在多位数
	 let reg=/^[@+]+$/   //表示@或者+符号,出现一到多次
      console.log(reg.test("@@@+++"))    //true
      
      let reg=/^[\d]$/   //\d在中括号中还是0-9
      console.log(reg.test("\\"))    //false
      console.log(reg.test("d"))    //false
      console.log(reg.test("9"))    //true

       let reg=/^[18]$/    //表示   1或者8	   console.log(reg.test("1"))    //true
	   console.log(reg.test("8"))    //true
	   console.log(reg.test("18"))   //false 
		
		let reg=/^[10-29]$/   //表示   1或者0-2或者9

() | 详解 :

		  let reg=/^18|29$/   //表示以1开头,以9结尾,中间8或者9
         /* 但是我们想要的是18或者19*/
          let reg=/^(18|29)$/     //表示只有18或者19符合,其他都不符合
          console.log(reg.test("18"))  

总结:直接x|y或存在很乱的优先级问题,一般我们写的时候都是伴随着先括号进行分组,因为小括号能改变处理的优先级

\b,\B详解:
\b是单词边界,具体就是\w与\W之间的位置,也包括\w与^之间的位置,和\w与$之间的位置。

\B就是\b的反面的意思,非单词边界。例如在字符串中所有位置中,扣掉\b,剩下的都是\B的。
具体说来就是\w与\w、\W与\W、^与\W,\W与$之间的位置。

	 let str = "[JS] lesson_01.mp4"
    console.log(str.replace(/\b/g, "#")) //[#JS#] #lesson_01#.#mp4#
    console.log(str.replace(/\B/g, "#")) //#[J#S]# l#e#s#s#o#n#_#0#1.m#p#4

常用修饰符:imgs

   i      :ignoreCase   忽略单词大小写匹配
 			  /A/.test(‘lalala’)   =>  false
			 /A/i.test(‘lalala’)   =>  true
  m    :multiline    可以进行多行匹配
  g      :global        全局匹配 
  gi    : 全局匹配+忽略大小写 
  s     :  "."默认的是匹配除换行符 \n 之外的任何单字符,加上s之后, "."中包含换行符

全局修饰符g的原理:基于lastIndex索引检索
lastIndex:是一个可读/写的整数,如果匹配模式中带有g修饰符,这个属性存储在整个字符串中下一次检索的开始位置,这个属性会被exec( ) 和 test( ) 方法用到。lastIndex这个属性可能会踩坑的。会导致有时候返回值出错有时候正确

子表达式 和 反向引用:() \1 \2:

		let str = "aaabbbcccddd"
		let reg1 = /(a)\1\1/g     //\1表示的是反向引用第1个子表达式 ,\2表示的是反向引用第2个子表达式
		let reg2 = /\1(a)/g    
		console.log(str.match(reg1))  //["aaa"]
		console.log(str.match(reg2))  //["a", "a", "a"]

注意:反向引用要写在子表达式的后面,否则无效

贪婪模式和非贪婪模式(惰性模式):

		let str = "kfs{
    
    {fjh}}lses{
    
    {42}}"
		let reg1 = /{
    
    {
    
    .*}}/g  //贪婪性
		let reg2 = /{
    
    {
    
    .*?}}/g  //非贪婪性,量词后面加?
		console.log(str.match(reg1)) //["{
    
    {fjh}}lses{
    
    {42}}"]
		console.log(str.match(reg2)) //["{
    
    {fjh}}","{
    
    {42}}"]

能使用正则的方法:

正则RegExp.prototype上的方法:

  1. exec

  2. test

字符串String.prototype上支持正则表达式处理的方法:

  1. replace

  2. match 只能整体捕获,不捕获括号中的内容

  3. split

  4. matchAll(新增) match的升级版,除了整体捕获,还可以捕获括号中内容

  5. search

exec:
exec() 不开启全局匹配模式g的情况:

	let str = "aaa2019ccc2020ccc021";
    let reg = /\d+/;
    /*
     * 基于exec实现正则的捕获
     *   1.捕获到的结果是null或者一个数组
     *     第一项:本次捕获到的内容
     *     其余项:对应小分组本次单独捕获的内容 
     *     index:当前捕获内容在字符串中的起始索引
     *     input:原始字符串
     *   2.每执行一次exec,只能捕获到一个符合正则规则的,但是默认情况下,我们执行一百遍,获取的结果永远都是第一个匹配到的,其余的捕获不到
     *     =>“正则捕获的懒惰性”:默认只捕获第一个
     */
    console.log(reg.exec(str)); //=>["2019", index: 7, input: "aaa2019ccc2020ccc021"]
    console.log(reg.exec(str)); //=>["2019", index: 7, input: "aaa2019ccc2020ccc021"]

exec() 开启全局匹配模式g的情况:

	let str = "aaa2019ccc2020ccc2021";
    let reg = /\d+/g;
    console.log(reg.exec(str)); //=>["2019", index: 7, input: "aaa2019ccc2020ccc021"]
    console.log(reg.exec(str)); //=>["2020", index: 10, input: "aaa2019ccc2020ccc021"]

exec()和test()API与lastIndex的关系:
exec: 当exec使用修饰符g进行匹配时,内部会基于lastIndex机制去匹配

let str = "aaa2019ccc2020ccc021";
/*
 * reg.lastIndex:当前正则下一次匹配的起始索引位置 
 *   懒惰性捕获的原因:默认情况下lastIndex的值不会被修改,每一次都是从字符串开始位置查找,所以找到的永远只是第一个 
 * */
let reg = /\d+/;
console.log(reg.lastIndex); //=>0 下面匹配捕获是从STR索引零的位置开始找
console.log(reg.exec(str)); //=> ["2019", index: 3, input: "aaa2019ccc2020ccc021"]
console.log(reg.lastIndex); //=>0 第一次匹配捕获完成,lastIndex没有改变,所以下一次exec依然是从字符串最开始找,找到的永远是第一个匹配到的
-------------------------------------------------------------------------------------------------------------------------------------
let str = "aaa2019ccc2020ccc2021";
//使用全局匹配模式g,内部会基于lastIndex机制去匹配
let reg = /\d+/g;
console.log(reg.exec(str)); //=>["2019"...]
console.log(reg.lastIndex); //=>7 设置全局匹配修饰符g后,第一次匹配完,lastIndex会自己修改
console.log(reg.exec(str)); //=>["2020"...]
console.log(reg.lastIndex); //=>14
console.log(reg.exec(str)); //=>["2021"...]
console.log(reg.lastIndex); //=>20
console.log(reg.exec(str)); //=>null 当全部捕获后,再次捕获的结果是null,但是lastIndex又回归了初始值零,再次捕获又从第一个开始了...
console.log(reg.lastIndex); //=>0
console.log(reg.exec(str)); //=>["2019"...]

test() 开启g同理:基于lastIndex查找

	let reg = /\d/
    console.log(reg.test("3"))  //true
    console.log(reg.lastIndex)  //0
    console.log(reg.test("3"))  //true
    console.log(reg.lastIndex)  //0
    console.log(reg.test("3"))  //true
    console.log(reg.lastIndex)  //0
    -----------------------------------------------------------
    let reg = /\d/g
    console.log(reg.test("3"))  //true
    console.log(reg.lastIndex)  //1
    console.log(reg.test("3"))  //false
    console.log(reg.lastIndex)  //0
    console.log(reg.test("3"))  //true
    console.log(reg.lastIndex)  //1

test使用修饰符g消除lastIndex机制的解决方法:

  1. 不使用g去test()

  2. 每次创建一个新的正则去test

	console.log(/\d/g.test("3"))  //true
    console.log(/\d/g.test("3"))  //true
    console.log(/\d/g.test("3"))  //true
  1. lastIndex的值可读可写,手动重写为0
let reg = /\d/g
    console.log(reg.test("3"))  //true
    reg.lastIndex = 0
    console.log(reg.test("3"))  //true
    reg.lastIndex = 0
    console.log(reg.test("3"))  //true
    reg.lastIndex = 0

match()方法:
                字符串中的MATCH方法,可以在执行一次的情况下,捕获到所有匹配的数据(前提:正则也得设置G才可以

  • 不加g:大正则和小分组都能拿到,但是只能捕获一次且每次捕获都需要从头开始
  • 加了g后:全局可捕获多次,但只能把大正则捕获的信息拿到,小分组就拿不到了
    console.log("aaa2019bbb2020ccc2021".match(/\d+/)); //["2019", index: 3, input: "aaa2019bbb2020ccc2021"]
    console.log("aaa2019bbb2020ccc2021".match(/(\d)+/)); //["2019", "9", index: 3, input: "aaa2019bbb2020ccc2021"]
    console.log("aaa2019bbb2020ccc2021".match(/\d+/g)); //["2019", "2020", "2021"]
    console.log("aaa2019bbb2020ccc2021".match(/(\d)+/g)); //["2019", "2020", "2021"]

总结:match方法可以看做是exec的升级版,exec开启g后需要基于lastIndex调用多次才能匹配上所有满足的字符串,而match方法开启g后,一次调用就能全部匹配出来

使用exec封装一个match方法:

 ~ function () {
    
    
        function execAll(str = "") {
    
    
            //str:要匹配的字符串
            //验证当前正则是否设置了G,不设置则不能在进行循环捕获了,否则会导致死循环
            if (!this.global) return this.exec(str);
            //ARY存储最后所有捕获的信息  RES存储每一次捕获的内容(数组)
            let ary = [],
                res = this.exec(str);
            while (res) {
    
    
                //把每一次捕获的内容RES[0]存放到数组中
                ary.push(res[0]);
                //只要捕获的内容不为NULL,则继续捕获下去
                res = this.exec(str);
            }
            return ary.length === 0 ? null : ary;
        }
        RegExp.prototype.execAll = execAll;
    }();

    let str = "aaa2019bbb2020ccc2021"

    console.log(str.match(/\d+/g)); //["2019", "2020", "2021"]
    console.log(/\d+/g.execAll(str)); //["2019", "2020", "2021"]

matchAll:
                可以看成是match的升级版,并且RegExp必须是设置了全局模式g,否则会抛出异常TypeError,返回值是一个可迭代对象,可通过...展开,也可以通过for of 遍历
matchAll比match的优势在于:

  1. 多了可以匹配括号中的内容,而match不行,只会整体捕获
  2. 整体匹配比match多很多信息,比如索引
	 let reg = /\d(\d)/g
    let str = "my 34dg grd42 name is tom  "
    console.log(str.match(reg)) //["34", "42"]
    console.log(...str.matchAll(reg)) 
    //["34", "4", index: 3, input: "my 34dg grd42 name is tom  "]
    //["42", "2", index: 11, input: "my 34dg grd42 name is tom  "]

split:

let str = "11aa122aa233aa4";
console.log(str.split("aa"))  //["11", "122", "233", "4"]
console.log(str.split(/[a-z]+/))  //["11", "122", "233", "4"]
console.log(str.split(/([a-z])+/))  //["11", "a", "122", "a", "233", "a", "4"]

search:

let str = "11aa122aa233aa4";
console.log(str.search("aa"))   //2
console.log(str.search(/[a-z]+/))  //2

replace: 我认为最强大的方法,第二个参数传入一个函数,可以处理很多问题

	let str = '2018aaaa@2019aaaa@2020'
    //需求:把'aaaa'都替换成空格
    // 1.不用正则执行一次只能替换一个,执行多次后才能全部替换
    console.log(str.replace('aaaa', ' '))  //2018 @2019aaaa@2020
    //2.使用正则
    console.log(str.replace(/aaaa/, ' '))  //2018 @2019aaaa@2020
    console.log(str.replace(/aaaa/g, ' ')) //2018 @2019 @2020

replace的反向引用:

		let str = "aabb   ddff  sge"
		let reg1 = /(\w)\1(\w)\2/g  
		//   $& :表示匹配的全部字符串1
		//   $1 : 表示匹配到的第一个括号的引用
		//   $2 : 表示匹配到的第二个括号的引用
		let newStr = str.replace(reg1,"$2$2$1$1")
		let newStr2 = str.replace(reg1,"$&")
		console.log(newStr) //bbaa   ffdd  sgeclike
		console.log(newStr2) //aabb   ddff  sge


	   let str2 = str.replace(reg1,($,$1,$2)=>{
    
    
		//   $:整体匹配到的内容
		//   $1:第一个括号匹配到的内容
		//   $2:第二个括号匹配到的内容
		console.log($,$1,$2) //aabb a b     //ddff d f
				//  需要return 
			})
		console.log(str2)  //undefined   undefined  sge

正则的分组捕获:

let str = "111222334";
let reg = /^(\d{3})(\d{3})(\d{2})(\d{1})$/;
console.log(reg.exec(str));  //["111222334", "111", "222", "33", "4", index: 0, input: "111222334"]
console.log(str.match(reg)); //["111222334", "111", "222", "33", "4", index: 0, input: "111222334"]
//=>第一项:大正则匹配的结果
//=>其余项:每一个小分组单独匹配捕获的结果
//=>如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于?:来处理

正则的应用:个人经验

时间字符串的格式化处理

	function formatTime(template = '{0}年{1}月{2}日  {3}时{4}分{5}秒') {
    
    
        //1.首先获取时间字符串中的年月日,小时分钟秒等信息
        let timeAry = this.match(/\d+/g) // ["2019", "8", "13", "16", "51", "3"]
        template = template.replace(/\{(\d+)\}/g, (content, $1) => {
    
    
            //content:当前本次大正则匹配的信息
            //$1:本次小分组单独匹配的信息
            //$1的值作为索引,到time-ary中找到对应的时间(如果没有则用“00”补)
            let time = timeAry[$1] || "00"
            time.length < 2 ? time = '0' + time : null
            return time
        })
        return template
    }
    String.prototype.formatTime = formatTime
    let time = "2019-8-13 16:51:3"
    console.log(time.formatTime()) //2019年08月13日  16时51分03秒

地址中查询字符串和HASH的提取

    function queryURL() {
    
    
        let obj = {
    
    }
        this.replace(/([^?#&=]+)=([^?#&=]+)/g, (...arg) => {
    
    
            let [, $1, $2] = arg
            obj[$1] = $2
        })
        this.replace(/#([^?#&=]+)/g, (...arg) => {
    
    
            let [, $1] = arg
            obj['HASH'] = $1
        })
        return obj
    }

    String.prototype.queryURL = queryURL
    let urlr = 'www.baidu.com/?lx=1&from=weixin&name=zhangsan#video'
    //{lx: "1", from: "weixin", name: "zhangsan", HASH: "video"}

正则之千分符

    function millimeter() {
    
    
        res = /\d{1,3}(?=(\d{3})+$)/g 
        // return this.replace(res, (content) => {
    
    
        //     return content + ","
        // })

        //简写
        return this.replace(res,"$&,")
    }
    String.prototype.millimeter = millimeter
    let num = '156845842412343245223' //156,845,842,412,343,245,223
    console.log(num.millimeter())

小驼峰写法

	//the-first-time  变成小驼峰写法
	let reg4 = /-(\w)/g
	console.log("the-first-time".replace(reg4, function ($, $1) {
    
    
		return $1.toUpperCase()
	}))  //theFirstTime

字符串去重

let reg5 = /(\w)\1*/g
console.log("aaaaaabbbbbbcccccc".replace(reg5, function ($, $1) {
    
    
		return $1
}))    //abc

敏感词过滤

let str = /你妈|恢复/g
console.log(..."你妈看能恢复护文妈妈 ,附件你妈".replace(str, "**"))
//* * 看 能 * * 护 文 妈 妈   , 附 件 * *

反转

let reg3 = /(\w)\1(\w)\2/
console.log("aabb".replace(reg3, "$2$2$1$1"))  //bbaa

解析html模板提取信息

 let str = `
			<ul>
                <li>
                    <a>肖生克的救赎</a>
                    <p>上映日期: 1994-09-10</p>
                </li>
                <li>
                    <a>阿甘正传</a>
                    <p>上映日期: 1994-07-06</p>
                </li>
			</ul>
		`

	let reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>.*?<\/li>/sg
	//matchAll相当于exec的循环到底
	let result = str.matchAll(reg)
	console.log(...result)

猜你喜欢

转载自blog.csdn.net/fesfsefgs/article/details/114464480