最详细 so easy的正则表达式(二)
朋友们可以先阅读最详细 so easy的正则表达式(一)文章链接
参考文章链接:https://blog.csdn.net/weixin_46927507/article/details/114604206
反向引用
反向引用允许在正则表达式内部引用之前捕获分组匹配的文本,形式是 \num
, num
表示所引用分组的编号
例如,匹配诸如2022-11-01
这样的日期字符串,要求是需要根据前一个日期分隔符,来确定下一个日期分割符的样式,怎么做呢?
这时候反向引用的重要性就来了。
(\d{4})([-:])(\d{2})\2(\d{2})
啥意思呢?我们先看图,能看出什么些细节来?
我们看分析一下这个正则表达式(\d{4})([-:])(\d{2})\2(\d{2})
,其中 \2
,表示所引用分组的编号为2,表示引用第二个捕获组的内容,当第一个分割符为-
时,\2
的内容为 -
;当第一个分割符为:
时,\2
的内容为 :
;
再来看个栗子,重复字母。
例14
//重复字母
//英文单词里有很多重复出现的字母, "hello shoot ..." 如想要检查某个单词中是否重复出现的字母,需要使用反向引用
let reg = /([a-z])\1/;
console.log(reg.test('aa'));//true
console.log(reg.test('bb'));//true
console.log(reg.test('ab'));//false
// \1,表示所引用分组的编号为1
// 表示引用第一个捕获组的内容,当第一个字母是什么,\1 的内容就是什么,以便达到重复字母的效果,简单说就是“你吃什么,我就吃什么”
非捕获
上述提到,除了捕获分组,正则表达式还提供了非捕获分组(non-capturing group
),表示方式:(?:pattern)
,匹配 pattern
,但不捕获匹配结果(文本),也就是没有捕获分组编号,同样不可以使用反向引用,捕获分组和非捕获分组可以在同一个正则表达式中同时出现。
例15
// 有一需求,需要匹配 "iloveuiloveu" 这个字符串。
// 通过上述我们所学的内容,可以写成(iloveu){2},但是由于不需要捕获文本,只是限定量词的作用范围,所以还可以写成(?:iloveu){2}
let reg = /(iloveu){2}/;
console.log(reg.test("iloveuiloveu"));//true
console.log(RegExp.$1);//'iloveyou'
//--------------水平分割线------------------------
let reg1 = /(?:iloveu){2}/;
console.log(reg1.test("iloveuiloveu"));//true
console.log(RegExp.$1);//''
//--------------水平分割线------------------------
let reg2 = /(?:abc)\1/;
console.log(reg2.test("abcabc"));//false
let reg3 = /(abc)\1/;
console.log(reg2.test("abcabc"));//true
选择
表示方式 |
竖线,以竖线 |
分隔开的多个子表达式也叫选择分支或选择项,可以理解为程序的分支结构。
选择项的尝试匹配次序是从左到右,直到发现了匹配项就结束,就会忽略在于它右侧其他选择项,所有所有的子选择项都不匹配,则整个选择结构匹配失败。
注意;括号 ()
用来规定整个选择结构的范围,没有括号,则将整个表达式视为一个选择结构。
例16
// 对于时间匹配的分段处理
let month = /0?\d|1[0-2]/;//月(01-12)
let day = /0?\d|[12]\d|3[01]/;//日(01-31)
//思考 时 分 如何书写正则表达式? 你来试试看!
总结一下各种括号的用法
括号有很多,一般指的是 ()
,还有中括号 []
、花括号{}
,它们长的太像了,我们一起来区分一下,上代码!
// 中括号 [] 字符集合.匹配方括号中的任意字符.
var reg = /^[abc]$/;
// a 也可以 b 也可以 c 也可以 a || b || c 三选一
console.log(reg.test('a'));//true
console.log(reg.test('b'));//true
console.log(reg.test('abc'));//false
// 大括号 量词符. 里面表示重复次数
var reg1 = /^a{3}$/;
var reg2 = /^abc{3}$/;
// 它只是让c重复三次 abccc
console.log(reg2.test('abc'));//false
console.log(reg2.test('abcabcabc'));//false
console.log(reg2.test('abccc'));//true
// 小括号 表示优先级 括号里面是一个捕获组 整体看
var reg3 = /^(abc){3}$/;
// 它是让abc重复三次
console.log(reg3.test('abc'));//false
console.log(reg3.test('abcabcabc'));//true
console.log(reg3.test('abccc'));//false
console.log(RegExp.$1);//'abc'
断言
正则表达式中的大多数结构匹配的文本会出现在最终的匹配结果中(一般用group(0)
可以得到),但是也有些结构并不是真正匹配文本,而只负责判断在某个位置左/右侧的文本是否符合要求,这种结构被称为断言(assertion)。
常见的断言有3种:单词边界、行开头结尾、环视。
单词边界
在文本处理中经常可能进行单词替换,比如吧一段文本中的 row
都替换成 line
。一般想到的是调用字符串的替换方法,直接替换row
。
替换前:The `row` we are looking for is `row` 10.
替换后:The `line` we are looking for is `line` 10.
<!-- 意想不到的结果 -->
替换前:
...`tomorrow` I will wear in `brown` standing in `row` 10 next to the rowdy guy...
替换后:...`tomorline` I will wear in `blinen` standing in `line` 10 next to the `linedy` guy...
<!-- 不仅所有单词 row 都被替换成了 line,其他单词内部的 row 也被替换成了 line,这显然不是我们想要的结果 -->
解决这个问题,必须有办法确定单词 row
,而不是字符串 row
。为解决这类问题,正则表达式提供了专用的单词边界
(world boundary
),记为\b
。它匹配的是“单词边界”位置,而不是字符。也就是说,\b
能够匹配这样的位置:一边是单词字符``\w,另一边不是非单词字符
\W`。
例17
let reg = /\bcat\b/;
console.log(reg.test("Tom is a cat"));//true
console.log(reg.test("car cat"));//true
console.log(reg.test("car"));//false
console.log(reg.test("bcat"));//false
起始位置
常见的断言还有 ^
和 $
,分别匹配字符串的起始位置和结束位置。
上述已讲,不再重复说明,忘记了的小伙伴,可以往上翻阅。
环视
用于查找某些内容之前或者之后的东西,叫做环视(look around
)。
javascript
只支持正序环视,相当于只能往前看,不能往后看,正序环视又分为肯定正序环视和否定正序环视。
肯定正序环视的表示为 (?=n)
,表示前面必须是n
才匹配;(匹配任何其后紧接指定字符串 n
的字符串。)
否定正序环视的表示为 (?!n)
,表示前面必须不是n
才匹配;
例18
let reg = /a(?=b)/;
console.log(reg.exec('abc'));//['a']
let reg1 = /a(?!b)/;
console.log(reg1.exec('abc'));//null
console.log(reg1.exec('ac'));//['a']
优先级
name | 从上到下优先级逐渐降低 |
---|---|
\ | 转义符 |
() (?!) (?=) [] | 括号、字符组、环视 |
* + ? {n} {n,} {n,m} | 量词 |
^ $ | 起始结束位置 |
` | ` |
由于括号的用途之一就是为量词限定作用范围,所以优先级比量词高。
let reg = /ab{2}/;
console.log(reg.test('abb'));//true
console.log(reg.test('abab'));//false
let reg1 = /(ab){2}/;
console.log(reg1.test('abab'));//true
//优先级
let reg2 = /^ab|cd$/;
console.log(reg2.test('abc'));//true
console.log(reg2.exec('abc'));//'ab'
let reg3 = /^(ab|cd)$/;
console.log(reg3.test('abc'));//false
补充
RegExp 构造函数属性
RegExp
构造函数本身的属性被称为静态属性,会根据最后一次执行的正则表达式操作而变化。这些属性还有一个特点,就是可以通过两种不同的方式访问它们。即长属性名和短属性名(简写),简写方式大都不是有效的ECMAScript
标识符,需要使用中括号语法来访问。
全名 | 简写 | 说明 |
---|---|---|
input | $_ | 最后(最近一次)搜索的字符串(非标准特性) |
lastMatch | $& | 最后匹配的文本 |
lastParen | $+ | 最后匹配的捕获组(非标准特性) |
leftContext | $` | input 字符串中lastMatch 之前的文本 |
rightContext | $' | input 字符串中lastMatch 之后的文本 |
例19
let reg = /(.)isoo/g;
let txt = "I love Jisoo!";
if(reg.test(txt)){
console.log(RegExp.input);// I love Jisoo!
console.log(RegExp.lastMatch);// Jisoo
console.log(RegExp.lastParen);// J
console.log(RegExp.leftContext);// I love
console.log(RegExp.rightContext);//!
}
if(reg.test(txt)){
console.log(RegExp.$_);// I love Jisoo!
console.log(RegExp["$&"]);// Jisoo
console.log(RegExp["$+"]);// J
console.log(RegExp["$`"]);// I love
console.log(RegExp["$'"]);//!
}
RegExp
还有几个构造函数的属性,可以存储最多9
个捕获组的匹配项,这些属性可以通过RegExp.$1~RegExp.$9
进行访问,在调用exec()
或者是test()
时,这些属性会被自动填充,具体运用可以看看例10
实例方法
RegExp
对象继承了Object
对象的方法有toLocaleString()
, toString()
,valueOf()
三个方法。
toLocaleString()
, toString()
都返回正则表达式的字面量表示。
valueOf()
都返回正则表达式的本身。
例20
let reg = /(.)at/gi;
console.log(reg.toString());
console.log(reg.toLocaleString());
console.log(reg.valueOf());//返回本身 浏览器控制台输出有高亮
正则表达式篇完结啦!我认为在某些地方讲解的不是很到位,内容不够充分不够细致,主要问题在于内容的编排不够循序,下次能够改进,同时希望大家多多反馈,给予评价!