目录

  • 第一章 正则表达式字符匹配攻略
  • 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. 什么是位置呢?

位置是相邻字符之间的位置。比如,下图中箭头所指的地方:

ansible copy 正则匹配 正则表达式匹配目录_ansible copy 正则匹配

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 lookaheadnegative lookahead
中文翻译分别是正向先行断言负向先行断言

ES6中,还支持positive lookbehindnegative 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章 正则表达式回溯法原理

ansible copy 正则匹配 正则表达式匹配目录_bc_02

目标字符串是:"acd"ef,匹配过程是:

ansible copy 正则匹配 正则表达式匹配目录_前端_03

图中省略了尝试匹配双引号失败的过程。可以看出.*是非常影响效率的。

⭐为了减少一些不必要的回溯,可以把正则修改为/"[^"]*"/。(ps:第六章有详细优化方案)

第5章 正则表达式的拆分

操作符的优先级:

  1. 转义符 \
  2. 括号和方括号 (...)(?:...)(?=...)(?!...)[...]
  3. 量词限定符 {m}{m,n}{m,}?*+
  4. 位置和序列 ^$\元字符、 一般字符
  5. 管道符(竖杠)|

上面操作符的优先级从上至下,由高到低

所谓元字符,就是正则中有特殊含义的字符。

所有结构里,用到的元字符总结如下:

^ $ . * + ? | \ / ( ) [ ] { } = ! : - ,

当匹配上面的字符本身时,可以一律转义:

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