关键词
精确匹配 模糊匹配 位置匹配 重复出现 分组 捕获 反向引用 片段匹配 捕获的引用 邮箱验证 敏感词屏蔽 字符串修剪
心得
又叫规则表达式,它是用代码来准确表述一些我们语言上描述的规则,比如:用代码描述多个0,其中多个0就叫模式,规则
模式匹配是强大的,凡是你能用语言表述出来的规则,在正则表达式中都可以实现,而难点在于你是否深入正则表达式
谈正则肯定离不开字符串
正则表达式最难理解的地方在于等价这个概念,这个概念增加了理解难度,如果把等价都恢复成原始写法,自己书写正则就超级简单了,就像说话一样去写你的正则了
正则表达式除了匹配字符以外,还可以匹配位置
指定匹配的位置(实际上就是为了不断章取义)
复杂的规则往往就是指那些带了多个条件的正则表达式,比如位置条件,比如只匹配百分号前的数字,只匹配美元符号后的数字…这些
我们已经看到了,一个正则表达式中的许多元素才能够匹配字符串的一个字符.
例如: \s 匹配的只是一个空白符.还有一些正则表达式的元素匹配的是字符之间宽度为0的空间,而不是实际的字符例如: \b 匹配的是一个词语的边界,也就是处于一个/w字字符和一个\w非字字符之间的边界.像\b 这样的字符并不指定任何一个匹配了的字符串中的字符,它们指定的是匹配所发生的合法位置.有时我们称这些元素为正则表达式的锚.因为它们将模式定位在检索字符串中的一个特定位置.最常用的锚元素是 ^, 它使模式依赖于字符串的开头,而锚元素$则使模式定位在字符串的末尾.
例如:要匹配词 “javascript” ,我们可以使用正则表达式 /^ javascript $/. 如果我们想检索 “java” 这个词自身 (不像在 “javascript” 中那样作为前缀),那么我们可以使用模式 /\s java \s /, 它要求在词语java之前和之后都有空格.但是这样作有两个问题.第一: 如果 “java” 出现在一个字符的开头或者是结尾.该模式就不会与之匹配,除非在开头和结尾处有一个空格. 第二: 当这个模式找到一个与之匹配的字符时,它返回的匹配的字符串前端和后端都有空格,这并不是我们想要的.因此,我们使用词语的边界 \b 来代替真正的空格符 \s 进行匹配. 结果表达式是 /\b java \b/.
下面是正则表达式的锚字符:
字符 含义
^ 匹配的是字符的开头,在多行检索中,匹配的是一行的开头
$ 匹配的是字符的结尾,在多行检索中,匹配的是一行的结尾
\b 匹配的是一个词语的边界.简而言之就是位于字符\w 和 \w之间的位置(注意:[\b]匹配的是退格符)
\B 匹配的是非词语的边界的字符
正则表达式的特点
灵活性、逻辑性和功能性非常的强;
可以迅速地用极简单的方式达到字符串的复杂控制。
由于正则表达式主要应用对象是文本,因此它在各种文本编辑器场合都有应用,小到著名编辑器EditPlus,大到Microsoft Word、Visual Studio等大型编辑器,都可以使用正则表达式来处理文本内容。
规则总结
正则表达式中最小单位是字符
反斜杠(\ )总是对后面的字符起作用
大括号({})总是对前面的
字符表达式起修饰作用PS: 关于字符与表达式的区别,用表达式更准确,因为如果存在分组的情况时,大括号作用的目标就是一个单元而非一个字符
问号,加号,星号(? + *)就是大括号的特殊情况,也就是等价写法
中括号([])不是对前面和后面起作用的,而是占了一个字符的位置
小括号()的作用很强大
分组: 被小括号包裹的n个字符会被视为一个字符(专业术语:单元),这样可以方便? + *的整体匹配
在完整模式中定义子模式:(场景:一堆图片要重命名,文件名是截屏图片xxx2016,我们要把2016改成2017,首先我们不能直接找2016,因为这样的话会找到别的带有2016字样的文件,所以我们肯定是先匹配所有的”截屏图片xxx2016”这个模式,也就是完整模式,然后通过这个模式下再去找我们要的核心:2016,最后再进行替换操作,而2016就是子模式)
引用:用()分组以后,再后面用反斜杠+数字可以取得对前面小括号的引用,注意:引用的不是模式而是相同的文本
|:竖线不单独对一个字符起作用,而是划分选项(专业术语:指定分组项),要么是左边的表达式,要么是右边的表达式
()是为了提取字符串,[]定义范围,{}定义长度
常用的特殊元字符以及在正则表达式种的行为
正则表达式种不能用空格匹配空格,需要用\s匹配空格
Q:什么是贪婪模式?
所谓贪婪模式,就是满足匹配条件的情况下,会尽可能多地匹配所搜索的字符串。例如,对于字符串 ‘oooooo’ ,’o+?’ 将匹配单个’o’,而 ‘o+’ 将匹配所有的o。
这是为什么?
这就是 ?号的特殊用法,当它放在其他量词(*, +, ?, {n}, {n,}, {n,m}) 后面,会取消贪婪模式,即尽可能少地进行匹配
正则表达式的应用场景
配合字符串使用
检查字符串中是否包含指定的模式,常用于:替换操作的第一步 | 邮箱验证
【1】模式:简单理解就是检查字符串中是否包含指定的值,这里用模式这个词更准确,因为它可以表示用于泛指,模糊匹配,范围更广,比如是否包含多个0,这句话用代码就不能用指定的值来表示,因为1个0也是0,多个0也是0
RegExp.test(String)
返回布尔值true/false,在只想知道目标字符串与某个模式是否匹配,但不需要知道其文本内容的情况下,这个方法非常有用。
String.search(RegExp)
返回-1/位置
用指定的文本替换正则模式匹配到的文本,常用于:敏感词过滤,各种文本插值语法,高亮显示
String.replace(RegExp, String2)
这里返回的是一个新的字符串
其他的方法:
RegExp.exec(String)
:返回字符串符合模式匹配的值,如果没有,返回null,一般这个方法用得少,检查是否有值用test,替换值用replace,对指定的部分替换用捕获组的方式。
String.match(RegExp)
:如果具有选项g则找出所有匹配项,如果没有选项g,则会返回包含第一个匹配项的信息的数组,捕获组,整个匹配从0开始的索引,以及被解析的原始字符串。
如何用文字表达正则语法?
字符串;tel:086-0666-88810009999
原始正则:”^tel:[0-9]{1,3}-[0][0-9]{2,3}-[0-9]{8,11}$”
速记理解:匹配开始 “tel:普通文本”[0-9数字]{1至3位}”-普通文本”[0数字][0-9数字]{2至3位}”-普通文本”[0-9数字]{8至11位} 结束”
等价简写后正则写法:”^tel:\d{1,3}-[0]\d{2,3}-\d{8,11}$”
Q: 为什么 RegExp.$1 就指代了 /y+/?
描述:
为什么RegExp这个构造函数能知道我要的捕获组?我并没有以如下的方式获取捕获组,为什么就可以得到这个捕获组了呢?
|
|
如图:
高程上是这么说的:
5.4.3 RegExp构造函数属性
RegExp构造函数包含一些属性(这些属性在其他语言中被看成静态属性)。这些属性适用于作用域中的所有正则表达式,并且基于所执行的最近一次正则表达式操作而而变化。关于这些属性的另一个独特之处,就是可以通过两种方式访问它们。换句话说,这些属性分别有一个长属性名和一个短属性名
当然,除了这些属性以外,还有9个用于存储捕获组的构造函数属性,访问这些属性的语法是 RegExp.$1、 RegExp.$2、 RegExp.$3…… RegExp.$9,在调用exec()或test()方法时,这些属性会自动被填充。
从上面已经找到了我们想要的答案:
RegExp构造函数包含一些静态属性
静态属性的值基于所执行的最近一次正则表达式操作而而变化,比如exec()或test()
RegExp.$1访问到的就是最近一次正则表达式操作的捕获组内容
|
|
模式对象的实例属性
尤其注意实例属性和静态属性的区别
通过这些属性可以取得有关模式的各种信息
- 是否设置了 g 修饰符? => pattern.global
- 是否设置了 i 修饰符? => pattern.ignoreCase
- 是否设置了 m 修饰符? => pattern.multiline
- 获取正则表达式的字符串表示 => pattern.source
查找/设置开始搜索下一个匹配项的字符位置 => pattern.lastIndex
注意,这里从字面理解起来可能会有困惑,最后的索引? 指的是下次开始搜索匹配项的起始字符位置
注意,这个行为仅仅在具有修饰符的模式对象中有用,比如
g
,y
(es6新增),在没有修饰符的模式对象中会忽略这个变化,即获取/设置不具有修饰符的模式对象的lastIndex
属性,会被忽略.注意,只有调用
exec()
和test()
这些模式对象的方式 lastIndex 属性才会发生改变;调用字符串的方法,比如match
方法,lastIndex 属性不会发生改变.
ES6对正则表达式的修复和增强
修复1
原本( ES5 )的正则表达式无法对码位(code point)大于 0FFFF 的字符进行匹配,会匹配不上,因为对于码位大于 0FFFF 的字符需要用2个码位(或者说字符)去表示一个字符(指码位大于0FFFF 的字符),但是正则表达式却不知道这回事,它依然会认为这2个码位表示2个字符而不是码位大于 0FFFF 的字符,这是因为正则表达式默认操作模式是编码单元(code unit)操作模式.
用2个码位(编码单元)表示1个字符(或者说一个码位)的术语就叫代理对
那么 ES6 怎么修复这个问题呢?
ES6引入了修饰符u
, 含义为开启Unicode
模式,如此一来模式对象就不会视代理对为2个字符了.其实这里应该不少人和自己一样有个疑问:我怎么知道什么时候2个字符是映射一个单字符而不是2个单字符?这里在自己查阅资料后得到了理解,即代理对的定义:如果第一个值来自于高代理区(D800–DBFF),并且第二个值来自于低代理去( DC00-DFFF),那么这就是一个代理对,即2个字符映射一个辅助平面(supplementary plane)或者说码位大于 0*FFFF 的字符.
|
|
第二行代码说明了 JavaScript 正则表达式对象默认的操作模式-编码单元(也可以叫不认识代理对模式),\uD842\uDFB7
即代理对表示汉字”吉”,但是正则表达式并不认识代理对,仍把它当成2个字符,所以第二行结果是true(当成单字符匹配),而使用修饰符u
后,正则表达式就会开启 Unicode 模式,即拥有识别代理对的能力,目标字符串如果存在代理对就会有类似添加小括号的行为(便于理解),即(\uD842\uDFB7)
,显然此时模式\uD842
并不匹配(\uD842\uDFB7)
,所以第三行代码结果为false。第四行的结果为 true, 可以看出对于模式匹配字符串,字符串首先会默认转换为编码单元再进行模式匹配.
以上示例,我们学到了什么?
模式匹配的时候,字符串会转换成对应的编码单元
使用了
u
修饰符,则会开启 Unicode 模式,此时如果字符串中存在代理对,会被正确识别使用了
u
修饰符,并不影响模式本身,即/\^\uD842/u
和/\^\uD842/
仍然匹配\uD842
这个字符,只是如果这个字符在目标字符串中不表示自身,而是作为代理对的一部分,这两种模式就会存在是否正确识别的差异.
修复2 正则表达式的复制
增强1 粘连修饰符 y
粘连(sticky)修饰符,何为粘连修饰符?这就不得不提到 g 修饰符了,如果默认的不带 g 修饰符,每次匹配会重新从字符串开始的位置匹配,我称之为无状态匹配.但是 g 修饰符会从上一次匹配成功的下一个位置开始,即剩余字符串开始匹配,而 y 修饰符和 g 修饰符类似,也是如此,不同之处在于,g 修饰符只要剩余位置中存在匹配就可,而 y 修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的含义。
|
|
第一次匹配的结果都一样,因为都是从目标字符串的开头进行匹配,而第二次匹配的结果却各不相同,因为不带修饰符的模式匹配是无状态匹配,即每次都从目标字符串的开头重新匹配,而带有修饰符 g 和 y 的则会记住上次匹配完时的位置,下次匹配时候从剩余字符串的开头继续匹配,而 y 修饰符返回为 null 是因为 y 暗含了\^
语义,而 g 修饰符则没这个语义,只要在剩余字符串中能匹配到模式即可.
|
|
从上面这2个示例,我们学到了什么?
y
修饰符暗含了\^
的语义.- y修饰符的设计本意,就是让头部匹配的标志^在全局匹配中都有效。
模式对象的
lastIndex
属性可以影响模式匹配时从目标字符串的哪个位置开始匹配.
增强2 flags 属性
在 ES5中,我们无法直接获取一个模式对象的修饰符,那么在 ES5中我们是如何做的呢?
注意:模式对象的source
属性只能获得模式的文本而不能获得修饰符
|
|
为了使获取修饰符的过程更简单, ES6新增了一个flags
属性,它与source
属性都是只读的原型属性访问器,对其只定义了 getter 方法,访问这个属性会返回所有应用于当前模式对象的修饰符字符串.
增强3 sticky 属性
与y修饰符相匹配,ES6 的正则对象多了sticky属性,表示是否设置了y修饰符。
|
|
正则表达式的先行(xíng)断言(lookahead)和后行断言(lookbehind)
什么是先行断言呢?
如同^代表开头,$代表结尾,\b代表单词边界一样,先行断言和后行断言也有类似的作用,它们只匹配某些位置,在匹配过程中,不占用字符,所以被称为“零宽”。所谓位置,是指字符串中(每行)第一个字符的左边、最后一个字符的右边以及相邻字符的中间(假设文字方向是头左尾右)。
全称是零宽先行断言,因为本身不消耗字符
”先行断言“指的是,x只有在y前面才匹配,必须写成/x(?=y)/。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/。”先行否定断言“指的是,x只有不在y前面才匹配,必须写成/x(?!y)/。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/。
先行断言示例: 如果 re(待匹配的模式) 后面的字符是 gular, 则匹配这个 re
例如对”a regular expression”这个字符串,要想匹配regular中的re,但不能匹配expression中的re,可以用”re(?=gular)”,该表达式限定了re右边的位置,这个位置之后是gular,但并不消耗gular这些字符,将表达式改为”re(?=gular).”,将会匹配reg,元字符.匹配了g,括号这一砣匹配了e和g之间的位置。
先行否定断言的匹配: 如果re(待匹配的模式) 后面的字符不是regex和regular, 则匹配这个 re
即匹配时会限制模式后面的位置不能出现字符 g
否定和非否定的区别在于该位置之后的字符
(?=y) 又叫 正向先行断言, (?!y)又叫负向先行断言
如何理解先行和后行?
如何记忆先行形式
(?)是先行断言和后行断言的标识符,代表不占用位置,不消耗字符,?可以理解成询问
(?=pattern)询问是