js正则表达式-进阶篇

218 阅读5分钟

学习 进阶篇

本片是进阶篇,话不多说开始

贪婪匹配、惰性匹配

贪婪匹配和惰性匹配一般都要配合量词使用,上节已经讲过

*0次或多次
+1次或多次
0次或1次
{n}重复n次
{n,}重复n次已上
{n,m}重复n次到m次

先说一下概念,

贪婪匹配

在匹配成功的前提下尽可能多的去匹配。默认都是使用贪婪匹配,也就是尽可能多的匹配内容

惰性匹配

在匹配成功的前提下尽可能少的去匹配。如果想使用惰性匹配的话就在量词后面加上 ?
下面举一些例子
先看默认的贪婪匹配

let str = '1aaaaaa1bbbbbb1'

/\d.*\d/.exec(str)
//["1aaaaaa1bbbbbb1", index: 0, input: "1aaaaaa1bbbbbb1", groups: undefined]
/\d.+\d/.exec(str)
//["1aaaaaa1bbbbbb1", index: 0, input: "1aaaaaa1bbbbbb1", groups: undefined]
/\d.{5,}\d/.exec(str)
//["1aaaaaa1bbbbbb1", index: 0, input: "1aaaaaa1bbbbbb1", groups: undefined]

是吧 清一色的按最多的去匹配。
再看下惰性匹配,只需要在量词后面加上 ?

let str = '1aaaaaa1bbbbbb1'

/\d.*?\d/.exec(str)
//["1aaaaaa1", index: 0, input: "1aaaaaa1bbbbbb1", groups: undefined]
/\d.+?\d/.exec(str)
//["1aaaaaa1", index: 0, input: "1aaaaaa1bbbbbb1", groups: undefined]
/\d.{5,}?\d/.exec(str)
//["1aaaaaa1", index: 0, input: "1aaaaaa1bbbbbb1", groups: undefined]

把结果跟上面的贪婪匹配对比一下就能看出来,惰性匹配都是按照最少的去匹配结果

分组、捕获

分组

在正则表达式中用小括号()来分组,一对括号括起来的表达式就是一个分组。一个正则表达式中可以有多个分组
比如

/\d(\w)\d/.exec('1a1')
//["1a1", "a", index: 0, input: "1a1", groups: undefined]

(\w)就是一个分组

捕获

分组会把匹配到的值保存起来,比如上面返回结果的第二项 "a" 就是分组匹配到的内容,也就是捕获到的内容。
分析下返回结果
第一项:匹配项 "1a1",对应着\d(\w)\d
第二项:捕获分组 "a",对应着(\w)
index项:匹配的其实位置
input项:原字符串

捕获型分组

加了括号就叫做捕获型分组,如果有多个就会同时捕获多个。

/(\d)(\w)(\d)/.exec('1a1')
//["1a1", "1", "a", "1", index: 0, input: "1a1", groups: undefined]

可以看到一共捕获3个分组,分别是1、2、3项

非捕获型分组

加了括号但不捕获就叫非捕获型分组,当需要加括号但又不想被捕获是就需要用到非捕获型分组了。比如

/\d(a|b)\d/.exec('1a1')
//["1a1", "a", index: 0, input: "1a1", groups: undefined]

这里的括号只是想使用分隔符表示或的意思,但却捕获到了"a"
所以这里就要使用到非捕获型分组了。重点:非捕获型分组需要用到 ?:
把**?:**放在分组(也就是小括号)的最前面就可以了,举个例子:

/\d(?:a|b)\d/.exec('1a1')
//["1a1", index: 0, input: "1a1", groups: undefined]

是吧 加了括号却没有捕获项~

引用、反向引用

引用和反向引用其实跟捕获型分组相关的东西,只有使用了捕获型分组才会用到引用和反向引用

引用

在使用捕获型分组的时候,捕获的对象会被保存在js的内置对象RegExp上面,我们先看下
image.png
可以看到捕获到的分组被保存在了RegExp对象的11-9属性上面。而所谓的引用就是我们在字符串的一些操作上可以引用被保存的这些值。看代码

'1a1 2b2 3c3'.replace(/\d(\w)\d/g,'$1')
//"a b c"

可以看到我们可以引用保存在$1属性上的值去替换正则表达式的匹配项。再举一个例子

//把所有的标签替换成div标签
let html = '<p>111</p><a>222</a><b>333</b>'
html.replace(/<(\/?)\w+>/g,'<$1div>')
//"<div>111</div><div>222</div><div>333</div>"

反向引用

反向引用其实就是把正则表达式中的分组当成一个变量,然后在正则表达式中再使用。反向引用必须在使用捕获型分组的正则表达式中才能使用
使用方式:**转义符(\) + 数字(代表引用第几个分组)。**举个例子

/(\d)(\w)\1\2/.exec('1a1a')
//["1a1a", "1", "a", index: 0, input: "1a1a", groups: undefined]

上面\1就代表引用的分组(\d),\2引用的分组(\w)。数字需要转义所以加上\,第几个分组就用数字几来表示。是不是感觉也挺简单的,一般在匹配头尾相同的字符串时会用到反向引用。
举荔子
如果想把<p>111</p><a>222</a><b>333</b>中的<p>111</p>、<a>222</a>、<b>333</b>分别匹配出来,怎么处理首尾标签保持相同呢?答案就是反向引用

'<p>111</p><a>222</a><b>333</b>'.match(/<(\w+)>.*?<\/\1>/g)
//["<p>111</p>", "<a>222</a>", "<b>333</b>"]

正向前瞻、反向前瞻

前瞻的功能比较类似于过滤作用,当需要匹配后面跟着某些内容或者后面不能跟着某些内容的字符时就需要用到前瞻了

正向前瞻

意思就是我现在要匹配一个字符串,这个字符串后面必须要跟着某些内容。
用法:?=
把?=放在需要跟着的某些内容的前面,并且用小括号()括起来
比如说字符串'hello world',我只想匹配到hello但后面必须跟着world
看下面的荔枝

'hello world'.match(/hello(?= world)/)
//["hello", index: 0, input: "hello world", groups: undefined]

'hello world'.match(/hello(?= xiaoming)/)
//null

这就叫正向前瞻

反向前瞻

理解了正向前瞻,反向前瞻也就好理解了。意思就是我现在要匹配一个字符串,这个字符串后面必须不能跟着某些内容。
用法跟正向前瞻一样,把符号改成?!
举个荔枝

'hello world'.match(/hello(?! world)/)
//null

'hello world'.match(/hello(?! xiaoming)/)
//["hello", index: 0, input: "hello world", groups: undefined]

是不是很容易就懂了
看来什么正向前瞻、反向前瞻也挺容易理解的嘛

前瞻其他注意点

前瞻虽然也有括号,看似一个分组却不会被当做分组,所以前瞻是非捕获型的
如果前瞻里面使用了分组,依然会捕获出来。比如

'hello world'.match(/hello(?= (world))/)
//["hello", "world", index: 0, input: "hello world", groups: undefined]

"world"被捕获了出来
如果不想捕获的话依然还是使用?:来使其变成非捕获分组

over