JavaScript 正则表达式

3 minute read

JavaScript 中的正则表达式类型为 RegExp,RegExp 对象可通过两种方式创建:

  • 使用字面量直接创建
    var re = /ta/g; // 全局匹配字符串 ta
    
  • 使用构造函数创建
    var re = new RegExp('ta', 'i'); // 匹配一次字符串 ta,不区分大小写
    

    正则表达式书写语法为

    var re = /pattern/flag

pattern 为正则表达式,包含字符类、限定符、分组、向前查找、反向引用。

flag 表示匹配行为:

  • g:全局匹配,字符串中匹配第一个子字符串后继续向下匹配至结束
  • i:不区分大小写
  • m:匹配多行,遇到行尾,继续匹配下一行

字符类

字符类分为普通字符、非打印字符和特殊字符

普通字符

  描述
\w 任意一个字母或数字或下划线,A-Za-z0-9_ 中任意一个
\W 查找非单词的字符,等价于[^A-Za-z0-9_ ]
\d 匹配一个数字字符,等价于[0-9]
\D 匹配一个非数字字符,等价于[^0-9]

非打印字符

  描述
\cx 匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。
\f 匹配一个换页符。等价于 \x0c 和 \cL。
\n 匹配一个换行符。等价于 \x0a 和 \cJ。
\r 匹配一个回车符。等价于 \x0d 和 \cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\t 匹配一个制表符。等价于 \x09 和 \cI。
\v 匹配一个垂直制表符。等价于 \x0b 和 \cK。

特殊字符

  描述
$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。
( 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。
* 匹配前面的子表达式零次或多次。
+ 匹配前面的子表达式一次或多次。
. 匹配除换行符 \n 之外的任何单字符。
[ 标记一个中括号表达式的开始。
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘\’ 匹配 “",而 ‘(’ 则匹配 “(“。
^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。
{ 标记限定符表达式的开始。
| 指明两项之间的一个选择
以上 ( [ { \ ^ $ ) ? * + .]} ,匹配字符串中的特殊字符需要使用反斜杠转义。

限定符

用来表示正则表达式中子项出现次数

  描述
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 、 “does” 中的 “does” 、 “doxy” 中的 “do” 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*‘。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。

定位符

定位正则表达式开始匹配生效的位置,通常为字符串头、尾、空格。

  描述
^ 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \n 或 \r 之后的位置匹配。
$ 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \n 或 \r 之前的位置匹配。
\b 匹配一个字边界,即字与空格间的位置、字与标点符号。
\B 非字边界匹配。

分组

分组在正则中用()表示,分组内可用 | 分割选项,表示两项选择其中一项。使用分组可表达子表达式的重复情况。 使用分组有一个副作用,就是分组作为子匹配项,分组相关的匹配结果会被缓存,针对分组的子匹配项是否会被缓存,分组分为两种形式:捕获组和非捕获组。

捕获组

捕获组通过从左到右计算其开括号来编号 。例如,在表达式 (A)(B(C)) 中,存在四个这样的组:

索引 分组
 (A)(B(C))
 (A)
 (B(C))
 (C)

组 0 始终代表整个表达式。以这样的顺序命名捕获组,每个子分组都有一个索引,匹配完成后,返回一个匹配数组,组索引位置对应着这个子匹配项的结果。捕获的子匹配可以通过反向引用在表达式中使用,也可以在匹配完成后,从匹配集合中检索。

非捕获组

与捕获组相反,分组的相关的匹配结果不会被缓存。

通过在()起始添加标记字符,此组标记为非捕获组。JavaScript 中有三种可标记组为非捕获组:?: 、?=、 ?!

其中 ?: 是非捕获元之一, ?= 和 ?! 除表明分组非捕获外,还有更多含义,前者为正向预查,圆括号内为匹配锚定位置,从括号内模式匹配成功的位置,往后开始匹配,后者为负向预查,从不匹配该分组模式的位置来向后匹配。由于 ?= 和 ?! 只用来锚定位置,非捕获组不计入整个字符串的匹配结果,因此这种非捕获组又称为零宽度断言。

零宽度断言可理解为,为匹配增加了条件判断,找到符合条件的某个位置,向后或向前查找字符串是否匹配某种模式。

零宽度断言 JavaScript 支持有局限,只支持向前查找 ?=(零宽度正先行断言)和 ?!(零宽度负先行断言),不支持向后查找 ?<=(零宽度正后发断言)和 ?<!(零宽度负后发断言)。

反向引用

反向引用是指在后面的表达式中我们可以使用组的编号来引用前面的表达式所捕获到的文本序列。需注意,引用的是前面捕获组中的文本而不是正则,也就是说一旦前面捕获组匹配,则反向引用指向的内容已经固定,反向引用处匹配的文本和前面捕获组中的文本相同。

var str = "longenaabcd";
console.log(str.match(/([ab])\1/)[0]);//aa

如 / ([ab])\1 / ,其中使用了分组, 捕获组中子表达式[ab];可以匹配a,也可以匹配b,但是如果匹配成功的话,那么它的反向引用也就确定了,如果捕获分组匹配到的是a,那么它的反向引用就只能匹配a,如果捕获分组匹配到的是b,那么它的反向引用就只能匹配到b;\1的含义是 捕获分组匹配到是什么,那么它必须与捕获分组到是相同的字符;也就是说 只能匹配到aa或者bb才能匹配成功。

正则的应用

RegExp 对象的方法有两个 exec 和 test,用于匹配和测试字符串中包含模式的情况。

  • exec

专门为组捕获设计的方法,接收一字符串,匹配成功时,返回包含匹配信息的数组,数组索引位置对应分组的匹配情况,另外数组还有两个属性 index 和 input 分别代表匹配成功起始位置和输入字符串。当匹配失败时,返回 null。 无论模式设置为全局匹配还是匹配一次,exec 每次执行只完成一次匹配,区别是设置为全局匹配时,下此运行从上一次匹配位置开始继续向下搜索匹配,而如果采用默认设置匹配一次,则每次运行都重新从头开始匹配。

var re = /[cfb](at)/gi;
re.exec('cat, bat, sat, fat');
// output  ["cat", "at", index: 0, input: "cat, bat, sat, fat"]
re.exec('cat, bat, sat, fat');
// output ["bat", "at", index: 5, input: "cat, bat, sat, fat"]
var re1 = /[cfb](at)/i;
re1.exec('cat, bat, sat, fat');
// output  ["cat", "at", index: 0, input: "cat, bat, sat, fat"]
re1.exec('cat, bat, sat, fat');
// output  ["cat", "at", index: 0, input: "cat, bat, sat, fat"]
  • test 如果只测试字符串是否包含该模式,而不在意匹配该模式的内容,此时使用 test 方法,改方法匹配成功时返回 true,失败返回 false
var re = /.at/i;
re.test('cat'); // true
re.test('bag'); //false

还有多达 9 个用于存储捕获组的构造函数属性。 RegExp.$1、 RegExp.$2… RegExp.$9, 分别用于 存储第一到九个匹配的捕获 组。 在 调用 exec() 或 test() 方法时, 这些属性会被自动填充。 使用示例:

var re = /\b(.at)\seat\s(\w*)/g;
re.exec('cat eat fish, sat eat food');
console.log(RegExp.$1); // cat
console.log(RegExp.$2); // fish
re.exec('cat eat fish, sat eat food');
console.log(RegExp.$1); // sat
console.log(RegExp.$2); // food

在字符串对象方法中

字符串对象方法中应用到 RegExp 对象实例 的方法有三个 match、search 和 replace,此时 RegExp 对象作为参数传人字符串对象的方法,用于字符串的匹配、搜索、替换等。

  • match 以传入的模式参数匹配字符串对象,返回一个匹配组
  • search 用于查找,符号匹配模式的子字符串在字符串中的位置
  • replace 用于替换掉字符串中符号匹配模式的子字符串
var re = /.at/;
var reg = /.at/g;
var str = 'I say cat eat fish, sat eat food';
console.log(str.match(re));  // ["cat", index: 6, input: "I say cat eat fish, sat eat food"]
console.log(str.match(reg)); // ["cat", "eat", "sat", "eat"]
console.log(str.search(re)); // 6
console.log(str.search(reg)); // 6
console.log(str.replace(re, 'oo')); // I say oo eat fish, sat eat food
console.log(str.replace(reg, 'oo')); // I say oo oo fish, oo oo food

总结

从书上、网上整理这么多,总结一下使用。

  • 用 \d、\w(即[a-zA-Z0-9_]) 等用于匹配数字、标识符,\s 匹配空白字符,. 匹配任意一个字符
  • ^ $匹配开始和结束,\b 表示字边界
  • 三种括号:[] 括起来的区间范围,^字符在中括号中打头时表示区间取非,{} 用来表示次数,() 用来分组
  • +号跟在字符后面表示至少出现一次,? 表示最多出现一次,* 可出现任意次
  • (?=) (?!)用在匹配模式的后面,锚定位置向前查找,(?:)打头分组匹配结果不缓存
  • match 为字符串方法,exec为表达式对象方法,当模式为一次匹配时,此时调用两者返回的结果一致;当模式为全局匹配时,match 返回所有匹配结果,而 exec 一次调用仍只返回一次结果,但是继续调用会在上一次匹配处继续向下搜索,用 exec 或 test 匹配的后,结果会临时存到构造函数 RegExp 对象上, RegExp.$1等获得分组匹配结果。