[正则表达式系列] 字符匹配

[正则表达式系列] 字符匹配

正则表达式是用于匹配字符串中字符组合的模式,在计算机科学的各个方面都有它的身影,然而学习它却是一件枯燥而困难的事情。得怀于老姚的 《JavaScript 正则表达式迷你书》,内容凝练且有深度,故这次下决心要攻破之。本系列为《JavaScript 正则表达式迷你书》的读书笔记,旨在今后能够游刃有余的使用正则表达式。

模糊匹配

正则表达式是匹配模式, 要么匹配字符, 要么匹配位置. 模糊匹配有两种形式, 分别是 横向模糊匹配纵向模糊匹配.

横向模糊匹配

使用量词 {m,n} 来匹配某个字符出现的个数. 如下面的例子中, 匹配的字符串必须满足: 第一个字符是 a, 最后一个字符是 c, 中间的 b 可以出现 1~4 次.

横向匹配.jpg

const str = "abc abbc abbbc abbbbc abbbbbc";

str.match(/ab{1,4}c/g); // [ 'abc', 'abbc', 'abbbc', 'abbbbc' ]

TIPS 关于 String 的 match 方法可参照 JavaScript API 全解析.

纵向模糊匹配

使用字符组 [xyz] 来匹配某个位置应该出现的字符.

纵向匹配.jpg

const str = "a1c a2c a3c a4c";

str.match(/a[123]c/g); // [ 'a1c', 'a2c', 'a3c' ]

字符组

字符组用于纵向模糊匹配, 它匹配某个位置可以是哪些字符.

范围表示法

如果字符组匹配的字符表示一个范围, 可使用 范围表示法.

const regexp = /[123456abcdefGHIJKLM]/;

// 上面的正则表达式可简化成如下形式
const simplifyRegexp = /[1-6a-fG-M]/;

通过上面的例子可以看出 - 在正则表达式中有特定的含义, 如果恰好要匹配 - 这个字符, 需要用到 转义, 或把 - 字符放到字符组的开头或结尾.

// 下面是纵向匹配 abcde
const regexp = /[a-e]/;

// 若想匹配 - 这个字符, 需要转义
const regexp = /[a\-e]/;

// 或者放到开头或结尾
const regexp = /[-ae]/;
const regexp = /[ae-]/;

排除字符组

即匹配除字符组以外的字符, 如 /A[^a-c]B/, 第二个字符是除 a, b, c 以外的任何字符.

const str = "AaB AbB AcB AdB";

str.match(/A[^a-c]B/g); // [ 'AdB' ]

内置字符组

正则表达式内置了一些字符组的简写形式, 可参照如下表格.

字符组含义
\d等价于 /[0-9]/, 表示匹配一个数字字符. d 是 digit 的缩写.
\D等价于 /[^0-9]/, 表示匹配一个非数字字符.
\w等价于 /[0-9a-zA-Z_]/, 表示匹配一个数字, 大小写字母或下划线. w 是 word 的缩写.
\W等价于 /[^0-9a-zA-Z_]/, 表示匹配一个非单词字符.
\s等价于 /[ \t\v\n\r\f]/, 表示匹配一个空白符. s 是 space 的缩写.
\S等价于 /[^0-9a-zA-Z_]/, 表示匹配一个非空白符.
.等价于 /[^\n\r\u2028\u2029]/, 表示通配符.

TIPS 匹配任意字符可使用 /[\d\D]//[\w\W]/、/[\s\S]/ 和 /[^]/ 的任意一个.

量词

字符组用于横向模糊匹配, 它匹配一个字符出现的次数.

内置量词

量词含义
{m,n}至少出现 m 次, 至多出现 n 次.
{m,}至少出现 m 次.
{m}等价于 {m,m} 表示必须出现 m 次.
?等价于 {0,1} 表示要么出现要么不出现.
+等价于 {1,+} 表示至少出现一次.
*等价于 {0,} 表示出现任意次.

贪婪匹配和惰性匹配

贪婪与惰性模式影响的是被量词修饰的子表达式的匹配行为, 贪婪模式在整个表达式匹配成功的前提下, 尽可能多的匹配; 而惰性模式在整个表达式匹配成功的前提下, 尽可能少的匹配.

下面的示例是经典的爬虫正则. 第一个是贪婪匹配, 在匹配到第一个 </div> 时已经可以使整个表达式匹配成功, 但由于采用的是贪婪模式, 所以仍然要向右尝试匹配, 查看是否还有更长的可以成功匹配的子串,在匹配到第二个 </div> 后, 向右再没有可以成功匹配的子串, 此时匹配结束. 对于惰性匹配, 它会从左到右找出字符串中每个可以配对的 <div></div> 进行匹配, 找到一对就算一对.

const str = "<main><div>test1</div>bb<div>test2</div><main/>";

// 贪婪匹配
str.match(/<div>.*<\/div>/g); // [ '<div>test1</div>bb<div>test2</div>' ]

// 惰性匹配
str.match(/<div>.*?<\/div>/g); // [ '<div>test1</div>', '<div>test2</div>' ]

下面这个例子, 正则匹配 2-5 位的数字. 对于贪婪匹配, 它会尽量匹配的字符, 也就是子串有 5 位就能匹配到 5 位, 有 3 位就能匹配到 3 位; 而对于惰性匹配, 它会尽量的匹配字符, 也就是尽量以 2 位去匹配.

const str = "123 1234 12345 123456";

// 贪婪匹配
str.match(/\d{2,5}/g); // [ '123', '1234', '12345', '12345' ]

// 惰性匹配
str.match(/\d{2,5}?/g); // [ '12', '12', '34', '12', '34', '12', '34', '56' ]

因此, 对于上述的量词, 通过后面加一个 ? 可实现其惰性形式.

惰性量词贪婪量词
{m,n}?{m,n}
{m,}?{m,}
???
+?+
*?*

多选分支

通过 | 来分割子模式, 表示可匹配其中任意一个子模式. 注意多选分支遵从惰性模式, 下面的例子中, 匹配到 good 后就不再考虑后面的 goodbye 了.

const str = "goodbye, seishun.";

str.match(/good|goodbye/g); // good

训练营

匹配 Hex 色值

下面拆解十六进制颜色:

  • 开头是 #

  • 后面可以是 3 位, 也可以是 6 位

  • 每位字符可以是 0-9, a-f 或 A-F

// 注意多选分支是惰性匹配
// 所以要把匹配长度多的放到前面来
// 因此 [0-9a-fA-F]{6} 要先于 [0-9a-fA-F]{3}
const hexRegExp = /\#[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/g;

"#ffbbad #Fc01DF #FFF #ffE skr 鸡你太美".match(hexRegExp); // [ '#ffbbad', '#Fc01DF', 'FFF', 'ffE' ]

匹配 24 小时时间

const timeRegExp = /^(0?[\d]|1[\d]|2[0-3]):(0?[\d]|[1-5][\d])$/;

timeRegExp.test("23:59"); // true
timeRegExp.test("02:07"); // true
timeRegExp.test("2:7"); // true
timeRegExp.test("42:37"); // false

匹配日期

const dateRegExp = /^([\d]{4})-(0?[1-9]|1[0-2])-(0?[1-9]|[12][\d]|3[01])$/;

dateRegExp.test("2019-03-08"); // true
dateRegExp.test("2019-3-2"); // true
dateRegExp.test("2019-3-92"); // false

匹配系统路径

const pathRegExp = /^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+)?$/;

pathRegExp.test("F:\\study\\javascript\\regex\\regular expression.pdf"); // true
pathRegExp.test("F:\\study\\javascript\\regex\\"); // true
pathRegExp.test("F:\\study\\javascript"); // true
pathRegExp.test("F:\\"); // true

匹配 html 元素的 id 属性

const idRegExp = /id=".*?"/;
const idRegExp1 = /id="[^"]*"/; // 这个效率高

idRegExp1.test('<div id="container" class="main"></div>'); // true
回到顶部的几种玩法

PREVIOUS POST

回到顶部的几种玩法

深入理解并手写遵循 Promise/A+ 规范的 Promise

NEXT POST

深入理解并手写遵循 Promise/A+ 规范的 Promise