目录
- 第一章 正则表达式字符匹配攻略
- 1 两种模糊匹配
- 1.1 横向模糊匹配
- 横向模糊匹配
- 量词
- 简写形式
- 全局匹配
- 1.2 纵向模糊匹配
- 纵向模糊匹配
- 字符组
- 范围表示法
- 连字符
- 需要匹配连字符`-`怎么办?
- 排除字符组
- 脱字符
- 字符组列举
- 贪婪匹配和惰性匹配
- 贪婪匹配
- 惰性匹配
- `.*`和`.*?`的不同:
- 第二章 正则表达式位置匹配攻略
- 1. 什么是位置呢?
- 2. 如何匹配位置呢?
- 2.1 ^和$
- 2.2 \b和\B
- 2.3 (?=p)和(?!p)
- 锚字符指定正则中相对应位置上有什么
- 第4章 正则表达式回溯法原理
- 第5章 正则表达式的拆分
- 1 字符组中的元字符
- 2 匹配“[abc]”和“{3,5}”
- 3 其余情况
- 第6章 正则表达式的构建
- 1 使用具体型字符组来代替通配符,来消除回溯
- 2 使用非捕获型分组
- 3 独立出确定字符
- 4 提取分支公共部分
- 5 减少分支的数量,缩小它们的范围
- 第七章 正则表达式编程
- 1. 正则表达式的四种操作
- 1.1 验证
- 使用search
- 使用test
- 使用match
- 使用exec
正则表达式是匹配模式,要么匹配字符,要么匹配位置。
第一章 正则表达式字符匹配攻略
1 两种模糊匹配
1.1 横向模糊匹配
横向模糊匹配
横向模糊匹配: /ad{2,10}c/
(横向模糊指的是,一个正则可匹配的字符串的长度不是固定的,可以是多种情况的。)
(表示匹配这样一个字符串:第一个字符是“a”,接下来是2到5个字符“b”,最后是字符“c”)
量词
量词: {m,n}
(表示此字母连续出现最少m次,最多n次)
(量词在正则表达式中使用时可以限制前方字符的长度)
简写形式
简写形式:
-
{m,}
表示至少出现m次。 -
{m}
等价于{m,m}
,表示出现m次。 -
?
等价于{0,1}
,表示出现或者不出现。记忆方式:问号的意思表示,有吗? -
+
等价于{1,}
,表示出现至少一次。记忆方式:加号是追加的意思,得先有一个,然后才考虑追加。 -
*
等价于{0,}
,表示出现任意次,有可能不出现。记忆方式:看看天上的星星,可能一颗没有,可能零散有几颗,可能数也数不过来。
ex.
var regex = /ab{2,5}c/g;
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log( string.match(regex) );
// => ["abbc", "abbbc", "abbbbc", "abbbbbc"]
全局匹配
全局匹配:/g
(在目标字符串中按顺序找到满足匹配模式的所有子串,强调的是“所有”,而不只是“第一个”。)
1.2 纵向模糊匹配
纵向模糊匹配
纵向模糊匹配:/a[123]c/
(纵向模糊指的是,一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种可能。)
(表示该字符是可以字符“1”、“2”、“3”中的任何一个。)
字符组
字符组:[abv123%]
(字符组在正则表达式中使用时可以代替一个字符)
范围表示法
范围表示法: [1-6a-fG-M]
连字符
连字符:-
(来省略和简写)
需要匹配连字符-
怎么办?
要匹配“a”、“-”、“z”这三者中任意一个字符
[-az]
或[az-]
或[a\-z]
。连字符要么放在开头,要么放在结尾,要么反斜杠写在连字符前转义。
排除字符组
排除字符组:[^abc]
([^abc]
,表示是一个除"a"、“b”、"c"之外的任意一个字符)
脱字符
脱字符:^
字符组列举
-
\d
就是[0-9]
。表示是一位数字。记忆方式:其英文是digit(数字)。 -
\D
就是[^0-9]
。表示除数字外的任意字符。 -
\w
就是[0-9a-zA-Z_]
。表示数字、大小写字母和下划线。记忆方式:w是word的简写,也称单词字符。 -
\W
是[^0-9a-zA-Z_]
。非单词字符。 -
\s
是[ \t\v\n\r\f]
。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符。记忆方式:s是space character的首字母。 -
\S
是[^ \t\v\n\r\f]
。 非空白符。 -
.
就是[^\n\r\u2028\u2029]
。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外。记忆方式:想想省略号…中的每个点,都可以理解成占位符,表示任何类似的东西。
匹配任意字符:
[\d\D]
、[\w\W]
、[\s\S]
和[^]
*通配符并不能匹配任意字符,但无特殊情况要求下可以使用它匹配任意字符
贪婪匹配和惰性匹配
贪婪匹配
贪婪匹配
(正则 /\d{2,5}/
,表示数字连续出现2到5次。会匹配2位、3位、4位、5位连续数字。)
(但是其是贪婪的,它会尽可能多的匹配。)
默认情况下,使用贪婪匹配。
惰性匹配
惰性匹配,就是尽可能少的匹配:
⭐( /\d{2,5}?/
表示,虽然2到5次都行,当2个就够的时候,就不在往下尝试了,只会匹配2位。)
通过在量词后面加个问号 ?
就能实现惰性匹配,因此所有惰性匹配情形如下:
{m,n}?
{m,}?
??
+?
*?
多选分支
具体形式如下:(p1|p2|p3)
,其中p1、p2和p3是子模式,用 |
(管道符)分隔,表示其中任何之一。
用管道符链接的多个子模式就是多选分支,她是惰性匹配。
例如要匹配"good"和"nice"可以使用 /good|nice/
。测试如下:
var regex = /good|nice/g;
var string = "good idea, nice try.";
console.log( string.match(regex) );
// => ["good", "nice"]
但有个事实我们应该注意,比如我用/good|goodbye/,去匹配"goodbye"字符串时,结果是"good":
var regex = /good|goodbye/g;
var string = "goodbye";
console.log( string.match(regex) );
// => ["good"]
而把正则改成/goodbye|good/,结果是:
var regex = /goodbye|good/g;
var string = "goodbye";
console.log( string.match(regex) );
// => ["goodbye"]
也就是说,分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了。
⭐(/goodbye|good/
表示匹配goodbye或good都可以,但匹配到goodbye后就不再判断是否有good了)
ex:匹配时间
以24小时制为例。
要求匹配:
23:59
02:07
分析:
共4位数字,第一位数字可以为[0-2]。
当第1位为2时,第2位可以为[0-3],其他情况时,第2位为[0-9]。
第3位数字为[0-5],第4位为[0-9]
正则如下:
var regex = /^([01][0-9]|[2][0-3]):[0-5][0-9]$/;
console.log( regex.test("23:59") );
console.log( regex.test("02:07") );
// => true
// => true
如果也要求匹配7:9,也就是说时分前面的0可以省略。
此时正则变成:
var regex = /^(0?[0-9]|1[0-9]|[2][0-3]):(0?[0-9]|[1-5][0-9])$/;
console.log( regex.test("23:59") );
console.log( regex.test("02:07") );
console.log( regex.test("7:9") );
// => true
// => true
// => true
⭐(?
可以直接使用表示前方的数字可以省略)
\
字符需要转义
[^\\:*<>|"?\r\n/]
:
表示合法字符
要求匹配:
F:\study\javascript\regex\regular expression.pdf
F:\study\javascript\regex
F:\study\javascript
F:\
var regex = /^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+)?$/;
console.log( regex.test("F:\\study\\javascript\\regex\\regular expression.pdf") );
console.log( regex.test("F:\\study\\javascript\\regex\\") );
console.log( regex.test("F:\\study\\javascript") );
console.log( regex.test("F:\\") );
// => true
// => true
// => true
// => true
其中,JS中字符串表示\
时,也要转义。
.*
和.*?
的不同:
比如说匹配输入串
A: 101000000000100
使用 1.*1
将会匹配到1010000000001, 匹配方法: 先匹配至输入串A的最后, 然后向前匹配, 直到可以匹配到1, 称之为贪婪匹配。
(⭐贪婪的重点是从字符的最后向前匹配)
使用 1.*?1
将会匹配到101, 匹配方法: 匹配下一个1之前的所有字符, 称之为非贪婪匹配。
(⭐非贪婪先找到第一个匹配到的结尾字符,获取此字符之前的值)
第二章 正则表达式位置匹配攻略
1. 什么是位置呢?
位置是相邻字符之间的位置。比如,下图中箭头所指的地方:
2. 如何匹配位置呢?
在ES5中,共有6个锚字符:
^ $ \b \B (?=p) (?!p)
2.1 ^和$
^
(脱字符)匹配开头,在多行匹配中匹配行开头。
$
(美元符号)匹配结尾,在多行匹配中匹配行结尾。
比如我们把字符串的开头和结尾用"#"替换(位置可以替换成字符!):
var result = "hello".replace(/^|$/g, '#');
console.log(result);
// => "#hello#"
多行匹配模式时,二者是行的概念,这个需要我们的注意:
var result = "I\nlove\njavascript".replace(/^|$/gm, '#');
console.log(result);
/*
#I#
#love#
#javascript#
*/
也就是说,每行都有开头和结尾被匹配。
2.2 \b和\B
\b
是单词边界,具体就是\w
和\W
之间的位置,也包括\w
和^
之间的位置,也包括\w
和$
之间的位置。
比如一个文件名是"[JS] Lesson_01.mp4"中的\b
,如下:
var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');
console.log(result);
// => "[#JS#] #Lesson_01#.#mp4#"
开头没被匹配,结尾被匹配的原因:
开头是^
与/W
之间的位置,结尾是/w
与$
之间的位置,必须是/w
与开头或结尾之间产生\b
知道了\b
的概念后,那么\B
也就相对好理解了。
\B
就是\b
的反面的意思,非单词边界。
具体说来就是\w
与\w
、\W
与\W
、^
与\W
,\W
与$
之间的位置。
把所有\B
替换成"#":
var result = "[JS] Lesson_01.mp4".replace(/\B/g, '#');
console.log(result);
// => "#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4"
2.3 (?=p)和(?!p)
(?=p)
,其中p是一个子模式,即p前面的位置。
比如(?=l)
,表示’l’字符前面的位置,例如:
var result = "hello".replace(/(?=l)/g, '#');
console.log(result);
// => "he#l#lo"
而(?!p)
就是(?=p)
的反面意思,比如:
var result = "hello".replace(/(?!l)/g, '#');
console.log(result);
// => "#h#ell#o#"
二者的学名分别是positive lookahead和negative lookahead。
中文翻译分别是正向先行断言和负向先行断言。
ES6中,还支持positive lookbehind和negative lookbehind。
具体是(?<=p)
和(?<!p)
,即p的后面位置和除了p之外元素的后面位置。
锚字符指定正则中相对应位置上有什么
不匹配任何东西的正则:/.^/
因为此正则要求只有一个字符,但该字符后面是开头。
Ex:(?=.*[0-9])
任意字符串后有一数字(?=.*[a-z])
任意字符串后有一小写字母(?=.*[A-Z])
任意字符串后有一大写字母
不匹配任何东西的正则 /.^/
开头位置 /(?=^)/
非开头位置 /(?!^)/
密码长度6-12位,由数字、小写字符和大写字母组成,但必须至少包括2种字符。
包含数字大小写字母的6-12位密码:
var reg = /^[0-9A-Za-z]{6,12}$/;
“至少包含两种字符”的意思就是说,不能全部都是数字,也不能全部都是小写字母,也不能全部都是大写字母:
var reg = /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/;
console.log( reg.test("1234567") ); // false 全是数字
console.log( reg.test("abcdef") ); // false 全是小写字母
console.log( reg.test("ABCDEFGH") ); // false 全是大写字母
console.log( reg.test("ab23C") ); // false 不足6位
console.log( reg.test("ABCDEF234") ); // true 大写字母和数字
console.log( reg.test("abcdEF234") ); // true 三者都有
同时包含两种类型字符,同时包含数字、小写字母和大写字母:
var reg = /((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A-Z]))^[0-9A-Za-z]{6,12}$/;
console.log( reg.test("1234567") ); // false 全是数字
console.log( reg.test("abcdef") ); // false 全是小写字母
console.log( reg.test("ABCDEFGH") ); // false 全是大写字母
console.log( reg.test("ab23C") ); // false 不足6位
console.log( reg.test("ABCDEF234") ); // true 大写字母和数字
console.log( reg.test("abcdEF234") ); // true 三者都有
第4章 正则表达式回溯法原理
目标字符串是:"acd"ef,匹配过程是:
图中省略了尝试匹配双引号失败的过程。可以看出.*是非常影响效率的。
⭐为了减少一些不必要的回溯,可以把正则修改为/"[^"]*"/
。(ps:第六章有详细优化方案)
第5章 正则表达式的拆分
操作符的优先级:
- 转义符
\
- 括号和方括号
(...)
、(?:...)
、(?=...)
、(?!...)
、[...]
- 量词限定符
{m}
、{m,n}
、{m,}
、?
、*
、+
- 位置和序列
^
、$
、\
元字符、 一般字符 - 管道符(竖杠)
|
上面操作符的优先级从上至下,由高到低
所谓元字符,就是正则中有特殊含义的字符。
所有结构里,用到的元字符总结如下:
^ $ . * + ? | \ / ( ) [ ] { } = ! : - ,
当匹配上面的字符本身时,可以一律转义:
var string = "^$.*+?|\\/[]{}=!:-,";
var regex = /\^\$\.\*\+\?\|\\\/\[\]\{\}\=\!\:\-\,/;
console.log( regex.test(string) );
// => true
但不是什么时候都需要转义,看情况是否需要转译。
1 字符组中的元字符
跟字符组相关的元字符有[]
、^
、-
。因此在会引起歧义的地方进行转义。例如开头的^
必须转义,不然会把整个字符组,看成反义字符组。
var string = "^$.*+?|\\/[]{}=!:-,";
var regex = /[\^$.*+?|\\/\[\]{}=!:\-,]/g;
console.log( string.match(regex) );
// => ["^", "$", ".", "*", "+", "?", "|", "\", "/", "[", "]", "{", "}", "=", "!", ":", "-", ","]
字符组内需要转义的字符:^ - ( ) [ ] \
2 匹配“[abc]”和“{3,5}”
我们知道[abc]
,是个字符组。如果要匹配字符串"[abc]"时,该怎么办?
可以写成/\[abc\]/
,也可以写成/\[abc]/
,测试如下:
var string = "[abc]";
var regex = /\[abc]/g;
console.log( string.match(regex)[0] );
// => "[abc]"
只需要在第一个方括号转义即可,因为后面的方括号不构成字符组,正则不会引发歧义,自然不需要转义。
同理,要匹配字符串"{3,5}",只需要把正则写成/\{3,5}/
即可。
另外,我们知道量词有简写形式{m,}
,却没有{,n}
的情况。虽然后者不构成量词的形式,但此时并不会报错。
匹配字符串直接写作 /{,n}/g
3 其余情况
比如= ! : - ,
等符号,只要不在特殊结构中,也不需要转义。
但是,括号需要前后都转义,如/\(123\)/
。
至于剩下的^ $ . * + ? | \ /
等字符,只要不在字符组内,都需要转义的。
第6章 正则表达式的构建
1 使用具体型字符组来代替通配符,来消除回溯
例如,匹配双引用号之间的字符。如,匹配字符串123"abc"456中的"abc"。
如果正则用的是:/".*"/
,,会在第3阶段产生4次回溯(粉色表示.*匹配的内容):
如果正则用的是:/".*?"/
,会产生2次回溯(粉色表示.*?匹配的内容):
要使用具体化的字符组,来代替通配符.
,以便消除不必要的字符,此时使用正则/"[^"]*"/
,即可。
2 使用非捕获型分组
因为括号的作用之一是,可以捕获分组和分支里的数据。那么就需要内存来保存它们。
当我们不需要使用分组引用和反向引用时,此时可以使用非捕获分组。例如:
/^[+-]?(\d+\.\d+|\d+|\.\d+)$/
可以修改成:
/^[+-]?(?:\d+\.\d+|\d+|\.\d+)$/
3 独立出确定字符
例如/a+/
,可以修改成/aa*/
。
因为后者能比前者多确定了字符a。这样会在第四步中,加快判断是否匹配失败,进而加快移位的速度。
4 提取分支公共部分
比如/^abc|^def/
,修改成/^(?:abc|def)/
。
又比如/this|that/
,修改成/th(?:is|at)/
。
这样做,可以减少匹配过程中可消除的重复。
5 减少分支的数量,缩小它们的范围
/red|read/
,可以修改成/rea?d/
。此时分支和量词产生的回溯的成本是不一样的。但这样优化后,可读性会降低的
第七章 正则表达式编程
1. 正则表达式的四种操作
正则表达式是匹配模式,不管如何使用正则表达式,万变不离其宗,都需要先“匹配”。
有了匹配这一基本操作后,才有其他的操作:验证、切分、提取、替换。
进行任何相关操作,也需要宿主引擎相关API的配合使用。当然,在JS中,相关API也不多。
1.1 验证
在说验证之前,先要说清楚匹配是什么概念。
所谓匹配,就是看目标字符串里是否有满足匹配的子串。因此,“匹配”的本质就是“查找”。
有没有匹配,是不是匹配上,判断是否的操作,即称为“验证”。
判断一个字符串中是否有数字。
使用search
var regex = /\d/;
var string = "abc123";
console.log( !!~string.search(regex) );
// => true
使用test
var regex = /\d/;
var string = "abc123";
console.log( regex.test(string) );
// => true
使用match
var regex = /\d/;
var string = "abc123";
console.log( !!string.match(regex) );
// => true
使用exec
var regex = /\d/;
var string = "abc123";
console.log( !!regex.exec(string) );
// => true