JS正则表达式迷你书知识点整理

定义

  • Regular expressions are patterns used to match character combinations in strings.

知识点

字符匹配
  • 横向模糊匹配
1
2
3
4
var regex = /ab{2,5}c/g;
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log( string.match(regex) );
// => ["abbc", "abbbc", "abbbbc", "abbbbbc"]
  • 纵向模糊匹配
1
2
3
4
var regex = /a[123]b/g;
var string = "a0b a1b a2b a3b a4b";
console.log( string.match(regex) );
// => ["a1b", "a2b", "a3b"]
  • 分支结构
1
2
3
4
5
6
7
8
9
var regex = /good|nice/g;
var string = "good idea, nice try.";
console.log( string.match(regex) );
// => ["good", "nice"]

var regex = /good|goodbye/g;
var string = "goodbye";
console.log( string.match(regex) );
//分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了。
位置匹配

^ $

1
2
3
4
5
6
7
// 对于位置的理解,我们可以理解成空字符 ""。
^(脱字符)匹配开头,在多行匹配中匹配行开头。
$(美元符号)匹配结尾,在多行匹配中匹配行结尾。

var result = "hello".replace(/^|$/g, '#');
console.log(result);
// => "#hello#"

\b

1
2
3
4
5
// \b 是单词边界,具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置。
// 对于位置的理解,我们可以理解成空字符 ""。(再来一遍)
var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');
console.log(result);
// => "[#JS#] #Lesson_01#.#mp4#"

\B

1
2
3
4
5
6
// \B 就是 \b 的反面的意思,非单词边界。例如在字符串中所有位置中,扣掉 \b,剩下的都是 \B 的。
// 具体说来就是 \w 与 \w、 \W 与 \W、^ 与 \W,\W 与 $ 之间的位置。

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"

(?=)

1
2
3
4
5
6
// (?=p),其中 p 是一个子模式,即 p 前面的位置,或者说,该位置后面的字符要匹配 p。
// 比如 (?=l),表示 "l" 字符前面的位置,例如:

var result = "hello".replace(/(?=l)/g, '#');
console.log(result);
// => "he#l#lo"

(?!)

1
2
3
4
5
// (?!p) 就是 (?=p) 的反面意思

var result = "hello".replace(/(?!l)/g, '#');
console.log(result);
// => "#h#ell#o#"
括号作用
  • 分组与分组引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
"这是括号一个重要的作用,有了它,我们就可以进行数据提取,以及更强大的替换操作。"

//分组
var regex = /(ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) );
// => ["abab", "ab", "ababab"]

//我们知道 /a+/ 匹配连续出现的 "a",而要匹配连续出现的 "ab" 时,需要使用 /(ab)+/。

//提取
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log( string.match(regex) );
// => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]

//match 返回的一个数组,第一个元素是整体匹配结果,然后是各个分组(括号里)匹配的
//内容,然后是匹配下标,最后是输入的文本。另外,正则表达式是否有修饰符 g,match
//返回的数组格式是不一样的。

//match返回格式区别
var string = "2017.06.27";
var regex1 = /\b(\d+)\b/;
var regex2 = /\b(\d+)\b/g;
console.log( string.match(regex1) );
console.log( string.match(regex2) );
// => ["2017", "2017", index: 0, input: "2017.06.27"]
// => ["2017", "06", "27"

//没有 g,返回的是标准匹配格式,即,数组的第一个元素是整体匹配的内容,接下来是分组捕获的内容,然
//后是整体匹配的第一个下标,最后是输入的目标字符串。
//有 g,返回的是所有匹配的内容。


//替换
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result);

//反向引用(很重要的应用)
//这里\1,保证了前面使用的和现在使用的是同一个符号。
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false
  • 分支结构
1
2
3
4
5
6
7
8
9
var regex = /^I love (JavaScript|Regular Expression)$/;
console.log( regex.test("I love JavaScript") );
console.log( regex.test("I love Regular Expression") );
// => true
// => true

// 如果去掉正则中的括号,即:
// /^I love JavaScript|Regular Expression$/,
// 匹配字符串是 "I love JavaScript" 和 "Regular Expression",当然这不是我们想要的。
匹配原理
  • 回溯法,本质上就是深度优先搜索算法。其中退到之前的某一步这一过程,我们称为“回溯”。从上面的描述过程中 ,可以看出,路走不通时,就会发生“回溯”。即,尝试匹配失败时,接下来的一步通常就是回溯。使用场景可以参考我之前一篇的博客全排列
  • 贪婪量词回溯,最先开始全部匹配如果不成功,就一个个减少
  • 惰性量词回溯,最先开始匹配最少情况,如果不成功,再递加。
  • 分支结构回溯,第一个不合适,就换第二个,如此类推。

最后来个书的作者形象总结:

  • 贪婪量词“试”的策略是:买衣服砍价。价钱太高了,便宜点,不行,再便宜点。

  • 惰性量词“试”的策略是:卖东西加价。给少了,再多给点行不,还有点少啊,再给点。

  • 分支结构“试”的策略是:货比三家。这家不行,换一家吧,还不行,再换。

正则优先级

“这里稍微总结一下,竖杠的优先级最低,即最后运算。 只要知道这一点,就能读懂大部分正则。”

正则表达式的构建
  • 匹配预期的字符串

  • 不匹配非预期的字符串

  • 可读性和可维护性

  • 效率

正则表达式编程
  • 验证
1
2
3
4
var regex = /\d/;
var string = "abc123";
console.log( regex.test(string) );
// => true
  • 提取
1
2
3
4
5
var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;
var string = "2017-06-26";
console.log( string.match(regex) );
// =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"]

  • 分割
1
2
3
4
5
6
7
8
9
10
11
2017/06/26
2017.06.26
2017-06-26

var regex = /\D/;
console.log( "2017/06/26".split(regex) );
console.log( "2017.06.26".split(regex) );
console.log( "2017-06-26".split(regex) );
// => ["2017", "06", "26"]
// => ["2017", "06", "26"]
// => ["2017", "06", "26"]
  • 替换
1
2
3
4
var string = "2017-06-26";
var today = new Date( string.replace(/-/g, "/") );
console.log( today );
// => Mon Jun 26 2017 00:00:00 GMT+0800 (中国标准时间)

总结

“正则表达式是匹配模式,要么匹配字符,要么匹配位置”。

​ 另外我觉得作者对于知识的定义很有意思, “什么叫知识,能指导我们实践的东西才叫知识。“。学习确实不能浅尝辄止,一定要落地实施。

“说起正则表达式,我之所以会去详细地研究它,最初的动机是,当我分析前端常见的框架和库的源码时,发现一般被卡住的地方就是它。” 以上都是作者原话, 因为作者写这本书的动机是为了分析框架源码,个人远远没有到达这种阶段,而且日常开发中几乎没有使用过,所以这篇文章算是启蒙和参考书吧。