前言
- 以下内容均来自
老姚的JS正则表达式完整教程(略长). - 文本仅为学习摘录.
正则知识架构
- 字符匹配
- 两种模糊匹配
- 横向模糊匹配: 匹配一个长度不确定的分组或者字符
- 纵向模糊匹配: 匹配一个不确定内容的字符
- 字符组
- 字符组(字符类),用以实现纵向模糊匹配.
- 意义是匹配字符组中的字符之一.
- 案例:
[abc]表示匹配"a""b""c"其中之一. - 由于"-"在字符组中可以连接两个字符表示连续的字符序列,所以我们需要注意转义.如:
[-az]或[az-]或[a\-z].
- 量词
- 量词,用以实现横向模糊匹配.
- 意义是描述字符或者分组的重复出现次数.
- 量词的匹配模式分为:
贪婪模式和惰性模式:- 贪婪模式(默认): 在量词区间内,尽可能多匹配.
- 惰性模式: 在量词区间内,尽可能少匹配.
- 开启:在量词后面加
?. - 注意:这个
?是修饰量词的.
- 开启:在量词后面加
- 贪婪量词:
{m,n}. - 惰性量词:
{m,n}?.
- 分支结构
- 支持多个子模式任选其一,使用
|(管道符)分隔多个子模式. - 注意分支结构内部内容使用
()与外部分离,如:(p1|p2|p3). - 分支结构中的匹配优先级: 从前往后.
- 比如我用
/good|goodbye/,去匹配"goodbye"字符串时,结果是"good".
- 比如我用
- 支持多个子模式任选其一,使用
- 两种模糊匹配
- 位置匹配
- 什么是位置?
- 相邻字符之间的位置.
- 如何匹配位置?
- 使用字符锚.
- 位置的特性
- 对于位置的理解,我们可以理解空字符"",如:介于字符之间,介于开头结尾之间.
- 直观展示:
"hello" == "" + "h" + "" + "e" + "" + "l" + "" + "l" + "o" + ""
- 什么是位置?
- 括号的作用
- 分组与分支结构
- 用括号隔离分组内容和外部内容,如
/(ab)+/g. - 用括号隔离分支结构内部内容与外部内容,如
(p1|p2).
- 用括号隔离分组内容和外部内容,如
- 捕获分组
- 我们可以用
$n捕获分组.
- 我们可以用
- 反向引用
- 使用
\1反向引用分组. - 如果没有则翻译成转义字符.
- 使用
- 非捕获分组
- 不可被捕获,无法反向引用,表达方式
(?:p).
- 不可被捕获,无法反向引用,表达方式
- 分组与分支结构
- 回溯法原理
- 没有回溯的匹配
- 假设: 用
/ab{1,3}c/去匹配"abbbc"则不会发生回溯.
- 假设: 用
- 有回溯的匹配
- 假设: 用
/ab{1,3}c/去匹配"abbc"则不会发生回溯. - 本质上是由
贪婪模式的特性引起的. - 解决方案:
- 使用惰性模式(当然惰性模式中也会有溯回).
- 使用
[^"](假设匹配引号内部的内容),排除边界字符.
- 假设: 用
- 常见的回溯形式
- 我们知道:在
深度优先搜索算法中,退回到之前某一步的这个过程,我们将其称为回溯. - 即,
也就是尝试匹配失败时,接下来的一步通常就是回溯. 贪婪量词,惰性量词,分支结构这三个场景都会存在溯回.因为都属于暧昧的表达方式.
- 我们知道:在
- 没有回溯的匹配
- 拆分
- 结构和操作符
- 字符字面量.
- 字符组.
- 量词.
- 锚字符.
- 分组.
- 选择分支.
- 反向引用.
- 注意要点
- 匹配字符串整体问题,如:
/^abc|bcd$/(匹配不符合预期)=>/^(abc|bcd)$/. - 量词连缀问题,如:
/[abc]{3}+/(不合法)=>/([abc]{3})+/. - 元字符转义问题,如:
"^$.*+?|\\/[]{}=!:-,"对应的正则/\^\$\.\*\+\?\|\\\/\[\]\{\}\=\=\!\:\-\,/- 元字符一律可以转义.
- 字符组中必要转义,如
[],^,-, - 简要转义
/\[abc\]/=>/\[abc]/(两者等价),/\{3,5}/同理. {,n}是一个不合法的量词,会被当做普通字符串(换言之,不用转义).=,+,:,-,,等符号,只要不在特殊结构中,也不需要转义.- 括号前后都需要转移的,如
/\(123\)/. ^,$,.,*,+,?,|,\,/等字符,只要不在字符组内,都需要转义的.
- 匹配字符串整体问题,如:
- 结构和操作符
- 构建(方法论)
- 平衡法则.
- 匹配预期的字符串.
- 不匹配非预期的字符串.
- 可读性和可维护性.
- 效率.
- 构建正则前提.
- 能否使用正则.
- 是否有必要使用使用正则.如:
- 拆分字符串可以使用
split代劳. - 判断是否存在某字符使用
search,indexOf代劳.
- 拆分字符串可以使用
- 是否有必要构建一个复杂的正则
- 对于
选择分支,我们可以使用js条件语句代劳.以拆分正则.
- 对于
- 准确性.
- 匹配预期的字符串.
- 不匹配非预期的字符串.
- 效率.
- 优化方式:
- 使用具体型字符来代替通配符,来消除回溯.
- 使用非捕获型分组.
- 独立出确定字符.
- 提取分支公共部分.
- 减少分支数量,缩小它们的范围.
- 优化方式:
- 平衡法则.
- 编译
- 正则表达式的四种操作
- 分别有:
验证,切分,提取,替换. 验证: 换言之,就是需要返回一个boolean.search: 通过将返回的哨位值-1转换成0,再!!转成boolean.test: 通过返回的boolean判定(如果没有^和$限定,则存在即可).match: 返回值有两种可能一种是null,另一种是数组,可以直接!!转换成boolean.exec: 和match同理.
切分: 按照一定的规则切分字符串.split: 如,2017/06/26".split(/\D/).
提取: 使用分组捕获功能,提取部分匹配的数据.match: 通过返回的数据提取.- 数据结构:
[match,$1,$2,$3,index:_index,input:_input].
- 数据结构:
exec: 与match同理.test: 调用test之后,通过构造函数属性RegExp.$1,RegExp.$2...获取.search: 与test同理.replace: 通过Array.prototype.push抛出要提取的数据.
替换: 替换匹配字符.replace: 把日期的横杠换成斜杠(把横杠换成斜杠的原因就是,我们需要new一个日期).
- 分别有:
- 相关API注意要点
- 明确API的所属对象,如:
search,split,match,replace: 属于字符串(String#)实例方法.test,exec: 属于正则(RegExp#)实例方法.
- 注意参数问题,如:
- 字符串4个实例方法,即
search,split,match,replace都支持正则和字符串. search和match: 会把字符串直接转换为正则,也就是说,有必要的时候我们需要进行转义.split和replace: 则会作为字面量自动转义,也就是说,没有必要进行手动转义.
- 字符串4个实例方法,即
match和exec之间的不同:- 不带
g的情况下,match和exec都会返回完整的匹配信息. - 带
g的情况下:match: 会将所有完整正则匹配的内容返回,但是不会返回完整信息.exec: 每调用一次会返回一条完整信息,依次推移,我们通过RegExp.lastIndex获取到下一次开始的位置,如果是0,我们就停止.(while).
- 引申
test和exec一样,带g之后,如果调用多次,就会RegExp.lastIndex推移,而且test最后的返回值肯定是false.
- 不带
test整体匹配需要^和$.split:- 第二个参数决定返回数组的最大长度.
- 如果正则是加入分组
/(,)/,则结果包含分隔符["999",",","999",",","999"].
replace:- 当replace第二个参数是字符串的时候,如下字符有特殊的含义.
$1,$2,...,$99匹配第1~99个分组里捕获的文本$&匹配到的子串文本$\匹配到的子串的左边文本$'匹配到的子串的右边文本?美元符号
- 当replace的第二个参数是函数的时候,其回调函数传入这些值:
(match,$1,$2,$3,index,input).
- replace带
g调用多次的时候会出现index偏移的效果.
- 当replace第二个参数是字符串的时候,如下字符有特殊的含义.
- 不推荐使用构造函数,推荐使用字面量,省代码.且不执行的时候更为轻量级.
- 修饰符
- ES5中修饰符,共3个:
g(global):全局匹配.i(ignoreCase):忽略大小写.m(multiline):多行匹配,只影响^和$,两者将会变成行开头和行结尾.
- ES5中修饰符,共3个:
- 正则实例对象的只读属性.
regex.global: 是否为全局配.regex.ignoreCase: 是否忽略大小写.regex.multiline: 是否为多行匹配.regex.lastIndex: 下一个开始匹配的位置.regex.source: 获取构造函数构造出来的成品的字符串.如:"(^|\\s)high(\\s|$)".
- 构造函数的静态属性.
RegExp.input: 最近一次目标字符串,简写RegExp["$_"].RegExp.lastMatch: 最近一次匹配的文本,简写RegExp["$&"].RegExp.lastParen: 最近一次捕获的文本,简写RegExp["$+"].RegExp.leftContext: 目标字符串中lastMatch之前的文本,简写成RegExp["$"]``.RegExp.rightContext: 目标字符串中lastMatch之后的文本,简写成RegExp["$'"].
- 明确API的所属对象,如:
- 正则表达式的四种操作
归类
常见字符组简写形式
\d(digit)就是[0-9]:匹配数字字符.\D就是[0-9]:匹配除了数字以外的任意字符.\w(word)就是[0-9A-Za-z_]: 匹配单词字符,包括:数字,大小写字母,下划线.\W就是[^0-9A-Za-z_]:匹配非单词字符.\s(space character)就是[\t\v\n\r\f]匹配空白符,包括:空格,水平制表符,垂直制表符,换行符,回车符,换页符.\S就是[\t\v\n\r\f]匹配非空白符..就是[^\n\r\u2028\u2029],通配符,几乎匹配任意字符,除了:换行符,回车符,行分隔符,段分隔符.- 如果需要匹配任意字符,则需要:
[\d\D],[\w\W],[\s\S],[^]
常见量词简写形式
{m,}:匹配至少出现m次.{m}:等价于{m,m},表示出现m次.?:等价于{0,1},表示出现1次或没有出现.+:等价于{1,},表示至少出现1次.*:等价于{0,}表示可以出现人一次,或者没有出现.
字符锚点
在es5中,共有6个锚字符: ^ $ \b \B (?=p) (?!p)
^和$:^(脱字符),匹配开头.在多行模式中匹配行开头.$(美元符号),匹配结尾,多行模式中匹配行结尾.
\b和\B:\b指的是单词边界,也就是\w与\W(或^或$)之间.\B指的是非单词边界,也就是\w与\w之间,\W与\W之间,\W与^之间,\W与$之间.
(?=p)和(?!p):p指的是一个子模式.(?=p)的学名是正向先行断言(positive lookahead).- 匹配
子模式的前面位置.
- 匹配
(?!p)的学名是负向先行断言(negative lookahead).- 匹配非(
子模式的前面位置).
- 匹配非(
(?<=p)的学名是正向后继断言(positive lookbehind).- 匹配
子模式的后面位置.
- 匹配
(?<!p)的学名是负向后继断言(negative lookbehind).- 匹配非(
子模式的后面位置).
- 匹配非(
表达式结构
字面量: 匹配一个具体字符(注意特殊符号转义).字符组: 匹配一个字符的多种可能之一.如:[0-9],\d.量词: 匹配字符连续出现的次数.锚点: 一个位置,而不是字符.分组: 匹配一个整体,如(ab)+,非捕获(?:ab)+.分支: 匹配多个子表达式之一.反向引用: 引用出现过分组,如\2.
表达式操作符
- 转义符:
\. - 括号和方括号:
(),(?:),(?=),(?!),[]. - 量词限定符:
{m},{m,n},{m,},?,*,+. - 位置和序列:
^,$,\,\元字符,一般字符. - 管道符:
|
案例
回溯图
- 贪婪量词
var string = "12345";
var regex = /(\d{1,3})(\d{1,3})/;
console.log( string.match(regex) );
// => ["12345", "123", "45", index: 0, input: "12345"]
- 惰性量词
var string = "12345";
var regex = /(\d{1,3}?)(\d{1,3})/;
console.log( string.match(regex) );
// => ["1234", "1", "234", index: 0, input: "12345"]
RegExp: /^\d{1,3}?\d{1,3}$/
*__Begin!___Digit___Digit___End!__*
|_____| |_____|
1 to 3 times 1 to 3 times
- 分支结构
RegExp: /^(?:can|candy)$/
__can__
*__Begin!__| |__end!__*
|_candy_|
元字符转义测试
var string = "^$.*+?|\\/[]{}=!:-,";
var regex = /\^\$\.\*\+\?\|\\\/\[\]\{\}\=\=\!\:\-\,/
console.log(regex.test(string));
// => true
var string = "^$.*+?|\\/[]{}=!:-,"
var stirng2 = "\^\$\.\*\+\?\|\\\/\[\]\{\}\=\!\:\-\,"
console.log(string === string2)
// => true