《正则表达式必知必会》读书笔记

最后更新:
阅读次数:

随着 JavaScript 语言的不断发展,本文也会记录 JavaScript 对正则表达式的新的特性,不再仅仅是一篇读书笔记。

元字符(metacharacter)

元字符大致有两种:一种是用来匹配文本的(比如 .),另一种是用来定义正则表达式的语法的(比如 [ ])。

匹配一个字符或字符集合

  • .:匹配除换行符(\n)以外的任何单个字符。
  • \:对特殊字符进行转义。
  • [ ]:定义字符集合。
  • - :一个特殊的元字符,常用在字符集合里定义一个字符区间,在字符集合外,只是一个普通字符。
  • ^:对字符集合进行取非匹配。

重复匹配

  • +:匹配一个或多个字符或字符集合({1,}
  • *:匹配零个或多个字符或字符集合({0,}
  • ?:匹配零个或一个字符或字符集合({0,1}
  • { }:定义匹配的重复次数
    • {3}:匹配重复 3 次
    • {3,}:匹配至少重复 3 次
    • {2,4}:匹配最少重复 2 次,最多重复 4 次

特殊的元字符

同一个元字符的大写形式与它的小写形式在功能上往往刚好相反。

  • \d:匹配任何一个数字字符([0-9])
  • \D:匹配任何一个非数字字符([^0-9])
  • \w:[a-zA-Z0-9_]
    • 注意 \w 包括下划线 (_) 字符
  • \W:[^a-za-z0-9_]
  • \s:匹配任何一个空白字符
  • \S:匹配任何一个非空白字符

空格、制表符(Tab 键,\t)、换行符(\n)、回车符(\r)、换页符(\f)和垂直制表符(\v)称为空白字符

  • \s:匹配一个空白字符,它是一个空白字符集合。
// \s 等价于下面的字符集合
\s == [\t\n\v\f\r\u0020\u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]

如果你单单需要匹配空格,请使用空格的 Unicode 码(\u0020进行匹配,不要直接使用空格字符进行匹配。

\r\n 是 Windows 系统使用的文本行结束标签,而 Linux/Unix 只使用 \n 来结束一个文本行,还有 Mac 系统里,每行结尾是 \r参看:回车和换行

防止过度匹配

* + {n,} 都是所谓的贪婪型元字符,它们在匹配时的行为模式是多多益善而不是适可而止的。为了解决这种状况,只需在这几个元字符后面加上一个?后缀即可将其变为懒惰型元字符*? +? {n,}?)。

var str = "This is <span>my</span> home <span>page</span>!";
var test1 = str.match(/<span>.*<\/span>/g);
var test2 = str.match(/<span>.*?<\/span>/g);
console.log(test1); // ["<span>my</span> home <span>page</span>"]
console.log(test2); // ["<span>my</span>", "<span>page</span>"]

位置匹配

单词边界

  • \b:匹配一个单词的开始或结尾(这里的单词是一个由 \w 匹配的字符中的一个或多个字符组成的字符串)
    \b:只匹配一个位置,不匹配任何字符
    \b 匹配这样的一个位置,这个位置位于一个能够用来构成单词的字符(字母、数字和下划线,即 \w 匹配的字符)和一个不能用来构成单词的字符(即 \W 匹配的字符)之间。

difference between \w and \b regular expression meta characters

  • \B:匹配一个非单词边界的位置

字符串边界

  • ^:匹配整个字符串的开始位置
  • $:匹配整个字符串的结束位置

子表达式(subexpression)

子表达式用来对表达式进行分组和归类。

  • ( ):定义一个子表达式

  • (x):匹配并记住 x,括号被称为捕获括号

var str = "__abc_abcabc";
var test1 = str.match(/abc{2,}/g);
var test2 = str.match(/(abc){2,}/g);
console.log(test1); // null
console.log(test2); // ["abcabc"]
  • (?:x):匹配但不记住 x,括号为非捕获括号(看一下下面的例子就会秒懂了)

使用字符串的 match() 方法,在 不给匹配的正则表达式加 g 标志 时,match 方法除过会返回正常匹配的字符串外,还会返回捕获括号中匹配到的子串(有几个捕获括号,返回几个子串)。

let str = `http://stackoverflow.com/questions`;

// 捕获括号测试
str.match(/(stackoverflow)(\.com)/);
// ["stackoverflow.com", "stackoverflow", ".com", index: 7, input: "http://stackoverflow.com/questions"]

// 非捕获括号测试
str.match(/(?:stackoverflow)(\.com)/);
// ["stackoverflow.com", ".com", index: 7, input: "http://stackoverflow.com/questions"]

回溯引用:前后一致匹配(backreference)

回溯引用是子表达式的另一种重要的用途。

  • \1:对应着第一个子表达式
  • \2:对应着第二个子表达式
  • ……以此类推
var str = "begin <h1>Right title</h1> ... <h1>Wrong title</h2> end";

str.match(/<h[1-6]>.*?<\/h[1-6]>/g);
// ["<h1>Right title</h1>", "<h1>Wrong title</h2>"]

str.match(/<h([1-6])>.*?<\/h\1>/g);
// ["<h1>Right title</h1>"]

前后查找(lookaround)

JavaScript 在 ES2018 中引入了向后查找,此前仅支持向前查找

  • 向前查找(lookahead),分两种模式
    • x(?=y):匹配 x 当且仅当 x 后面跟着 y
    • x(?!y):匹配 x 当且仅当 x 后面没有跟着 y
var str = "http://www.baidu.com";

str.match(/\w{4}(:)/g);
// ["http:"]

str.match(/\w{4}(?=:)/g);
// ["http"]

str.match(/\w{4}(?!:)/g);
// ["baid"]

var str = "$100 almost equals to 700RMB";

str.match(/\d+(?=(RMB))/g);
// ["700"]
  • 向后查找(lookbehind),也分两种模式
    • (?<=y)x:匹配 x 当且仅当 x 跟在 y 的后面
    • (?<!y)x:匹配 x 当且仅当 x 没有跟在 y 的后面
var str = "$100 almost equals to 700RMB";

str.match(/(?<=\$)\d+/g);
// ["100"]

str.match(/(?<!\$)\d+/g);
// ["00", "700"]

Unicode 属性类

ES2018 引入了一种新的类的写法 \p{...}\P{...},允许正则表达式匹配符合 Unicode 某种属性的所有字符。

// 匹配希腊字母
/\p{Script=Greek}/u.test("π"); // true
/\p{Script=Greek}/u.test("β"); // true

// 匹配汉字
/\p{Unified_Ideograph}/u.test("霾"); // true
/\p{Unified_Ideograph}/u.test("w"); // false
/\p{Unified_Ideograph}/u.test("だ"); // false
/\p{Unified_Ideograph}/u.test("한"); // false

具名组匹配

ES2018 引入了 具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec("1999-12-31");
const { year, month, day } = matchObj.groups;

console.log(year, month, day); // 1999 12 31

其他语法

  • x|y:匹配 x 或 y,x、y 可以是一个字符或子表达式
  • [\b]:匹配一个退格(不要和 \b 搞混了)
  • \uhhhh:匹配一个与相应的 Unicode 编码对应的字符
// 空格的 unicode 编码为 \u0020

"hello world".match(/\w\u0020\w/g);
// ["o w"]

在 JavaScript 中使用正则表达式

RegExp 对象

// 定义一个正则表达式
var reg1 = /pattern/flags;
var reg2 = new RegExp(pattern [, flags]);
// pattern 为正则表达式
// flags:
// g 全局匹配
// i 不区分大小写匹配
// m 多行匹配

如果使用 m 标志,^和$匹配的开始或结束输入字符串中的每一行,而不是整个字符串的开始或结束。

  • RegExp.prototype.exec(str):为指定的一段字符串执行搜索匹配操作,返回一个数组或者 null
  • RegExp.prototype.test(str):用来查看正则表达式与指定的字符串是否匹配,返回 true 或 false

String 对象

  • String.prototype.match(regexp):为指定的一段字符串执行搜索匹配操作,返回一个数组或者 null

在 match 函数中的正则表达式参数不包含 g 标志时,调用 match 函数返回的结果中会包含正则表达式中声明的捕获组,否则捕获组就不会被返回。

let url = "http://abcde.com#top";

url.match(/.+:.*?(#.*)/);
// ["http://abcde.com#top", "#top", index: 0, input: "http://abcde.com#top", groups: undefined]

url.match(/.+:.*?(#.*)/g);
// ["http://abcde.com#top"]

当 replace 函数的第二个参数为字符串时,我们可以使用 $1$2$3… 这些特殊变量来表示前面正则表达式对应位数的捕获括号中匹配到的子字符串。

// 交换字符串中的两个单词
var re = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(re, "$2, $1");
console.log(newstr); // Smith, John

当 replace 函数的第二个参数为函数时,这个函数的第一个参数和倒数第二个参数之间的参数也可以用来表示捕获括号中匹配的子串。

let str = "20171005";

str.replace(/(\d{4})(\d{2})(\d{2})/, function(match, p1, p2, p3, offset, str) {
return `${p1}-${p2}-${p3}`;
});
// "2017-10-05"

这个函数的第一个参数常为字符串,很少是正则表达式,但是遇到奇葩问题时,它还真的得用正则表达式

// 奇葩分割
let str = "abc@123#as%666";

str.split(/[@#%]/);
// ["abc", "123", "as", "666"]

以上。

参考资料