这篇文章继续分享《高程四》第5章的内容。从5.2.1节开始。
5.2.1 RegExp 实例属性
每个 RegExp 实例都有下列属性,提供有关模式的各方面信息。
global :布尔值,表示是否设置了 g 标记。
ignoreCase :布尔值,表示是否设置了 i 标记。
unicode :布尔值,表示是否设置了 u 标记。
sticky :布尔值,表示是否设置了 y 标记。
lastIndex :整数,表示在源字符串中下一次搜索的开始位置,始终从0开始。
multiline :布尔值,表示是否设置了 m 标记。
dotAll :布尔值,表示是否设置了 s 标记。
source :正则表达式的字面量字符串(不是传给构造函数的模式字符串),没有开头和结尾的斜杠。
flags :正则表达式的标记字符串。始终以字面量而非传入构造函数的字符串模式形式返回(没有前后斜杠)。
通过这些属性可以全面了解正则表达式的信息,不过实际开发中用得并不多,因为模式声明中包含这些信息。下面是一个例子:
let pattern1 = /\[bc\]at/i;
console.log(pattern1.global); // false
console.log(pattern1.ignoreCase); // true
console.log(pattern1.multiline); // false
console.log(pattern1.lastIndex); // 0
console.log(pattern1.source); // "\[bc\]at"
console.log(pattern1.flags); // "i"let pattern2 = new RegExp("\\[bc\\]at", "i");
console.log(pattern2.global); // false
console.log(pattern2.ignoreCase); // true
console.log(pattern2.multiline); // false
console.log(pattern2.lastIndex); // 0
console.log(pattern2.source); // "\[bc\]at"
console.log(pattern2.flags); // "i"
注意,虽然第一个模式是通过字面量创建的,第二个模式是通过RegExp 构造函数创建的,但两个模式的 source 和 flags 属性是相同的。 source 和 flags 属性返回的是规范化之后可以在字面量中使用的形式。
5.2.2 RegExp 实例方法
RegExp 实例的主要方法是 exec() ,主要用于配合捕获组使用。这个方法只接收一个参数,即要应用模式的字符串。如果找到了匹配项,则返回包含第一个匹配信息的数组;如果没找到匹配项,则返回 null 。返回的数组虽然是 Array 的实例,但包含两个额外的属性: index 和input 。 index 是字符串中匹配模式的起始位置, input 是要查找的字符串。这个数组的第一个元素是匹配整个模式的字符串,其他元素是与表达式中的捕获组匹配的字符串。如果模式中没有捕获组,则数组只包含一个元素。来看下面的例子:
let text = "mom and dad and baby";
let pattern = /mom( and dad( and baby)?)?/gi;let matches = pattern.exec(text);console.log(matches.index); // 0
console.log(matches.input); // "mom and dad and baby"
console.log(matches[0]); // "mom and dad andbaby"
console.log(matches[1]); // " and dad and baby"
console.log(matches[2]); // " and baby"
在这个例子中,模式包含两个捕获组:最内部的匹配项 " and baby" ,以及外部的匹配项 " and dad" 或 " and dad andbaby" 。调用 exec() 后找到了一个匹配项。因为整个字符串匹配模式,所以 matchs 数组的 index 属性就是0。数组的第一个元素是匹配的整个字符串,第二个元素是匹配第一个捕获组的字符串,第三个元素是匹配第二个捕获组的字符串。
如果模式设置了全局标记,则每次调用 exec() 方法会返回一个匹配的信息。(第一次调用返回第一次匹配的地方,第二次调用放回第二次匹配的地方,lastIndex也会增加)如果没有设置全局标记,则无论对同一个字符串调用多少次exec() ,也只会返回第一个匹配的信息。
let text = "cat, bat, sat, fat";
let pattern = /.at/;
let matches = pattern.exec(text);
console.log(matches.index); // 0
console.log(matches[0]); // cat
console.log(pattern.lastIndex); // 0
matches = pattern.exec(text);
console.log(matches.index); // 0
console.log(matches[0]); // cat
console.log(pattern.lastIndex); // 0
上面例子中的模式没有设置全局标记,因此调用 exec() 只返回第一个匹配项( "cat" )。 lastIndex 在非全局模式下始终不变。如果在这个模式上设置了 g 标记,则每次调用 exec() 都会在字符串中向前搜索下一个匹配项,如下面的例子所示:
let text = "cat, bat, sat, fat";
let pattern = /.at/g;
let matches = pattern.exec(text);
console.log(matches.index); // 0
console.log(matches[0]); // cat
console.log(pattern.lastIndex); // 3matches = pattern.exec(text);
console.log(matches.index); // 5
console.log(matches[0]); // bat
console.log(pattern.lastIndex); // 8matches = pattern.exec(text);
console.log(matches.index); // 10
console.log(matches[0]); // sat
console.log(pattern.lastIndex); // 13
这次模式设置了全局标记,因此每次调用 exec() 都会返回字符串中的下一个匹配项,直到搜索到字符串末尾。注意模式的 lastIndex 属性每次都会变化。在全局匹配模式下,每次调用 exec() 都会更新lastIndex 值,以反映上次匹配的最后一个字符的索引。
如果模式设置了粘附标记 y ,则每次调用 exec() 就只会在lastIndex 的位置上寻找匹配项。粘附标记覆盖全局标记。
let text = "cat, bat, sat, fat";
let pattern = /.at/y;
let matches = pattern.exec(text);
console.log(matches.index); // 0
console.log(matches[0]); // cat
console.log(pattern.lastIndex); // 3// 以索引3对应的字符开头找不到匹配项,因此exec()返回null
// exec()没找到匹配项,于是将lastIndex设置为0
matches = pattern.exec(text);
console.log(matches); // null
console.log(pattern.lastIndex); // 0
// 向前设置lastIndex可以让粘附的模式通过exec()找到下一个匹配项:
pattern.lastIndex = 5;
matches = pattern.exec(text);
console.log(matches.index); // 5
console.log(matches[0]); // bat
console.log(pattern.lastIndex); // 8
正则表达式的另一个方法是 test() ,接收一个字符串参数。如果输入的文本与模式匹配,则参数返回 true ,否则返回 false 。这个方法适用于只想测试模式是否匹配,而不需要实际匹配内容的情况。test() 经常用在 if 语句中:
let text = "000-00-0000";
let pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)) {console.log("The pattern was matched.");
}
在这个例子中,正则表达式用于测试特定的数值序列。如果输入的文本与模式匹配,则显示匹配成功的消息。这个用法常用于验证用户输入,此时我们只在乎输入是否有效,不关心为什么无效。
无论正则表达式是怎么创建的,继承的方法 toLocaleString() 和toString() 都返回正则表达式的字面量表示。比如:
let pattern = new RegExp("\\[bc\\]at", "gi");
console.log(pattern.toString()); // /\[bc\]at/gi
console.log(pattern.toLocaleString()); // /\[bc\]at/gi
这里的模式是通过 RegExp 构造函数创建的,但toLocaleString() 和 toString() 返回的都是其字面量的形式。
注意 正则表达式的 valueOf() 方法返回正则表达式本身。
5.2.3 RegExp 构造函数属性
RegExp 构造函数本身也有几个属性。(在其他语言中,这种属性被称为静态属性。)这些属性适用于作用域中的所有正则表达式,而且会根据最后执行的正则表达式操作而变化。这些属性还有一个特点,就是可以通过两种不同的方式访问它们。换句话说,每个属性都有一个全名和一个简写。下表列出了 RegExp 构造函数的属性。
全名 简写 说明 input $_ 最后搜索的字符串 lastMatch $& 最后匹配的文本 lastParen $+ 最后匹配的捕获组 leftContext $` input 字符串中出现在lastMatch 前面的文本
rightContext $' input 字符串中出现在lastMatch 后面的文本
通过这些属性可以提取出与 exec() 和 test() 执行的操作相关的信息。来看下面的例子:
let text = "this has been a short summer";
let pattern = /(.)hort/g;
if (pattern.test(text)) {console.log(RegExp.input); // this has been a short summerconsole.log(RegExp.leftContext); // this has been aconsole.log(RegExp.rightContext); // summerconsole.log(RegExp.lastMatch); // shortconsole.log(RegExp.lastParen); // s
}
以上代码创建了一个模式,用于搜索任何后跟 "hort" 的字符,并把第一个字符放在了捕获组中。不同属性包含的内容如下。
input 属性中包含原始的字符串。
leftConext 属性包含原始字符串中 "short" 之前的内容,rightContext 属性包含 "short" 之后的内容。
lastMatch 属性包含匹配整个正则表达式的上一个字符串,即 "short" 。
lastParen 属性包含捕获组的上一次匹配,即 "s" 。
这些属性名也可以替换成简写形式,只不过要使用中括号语法来访问,如下面的例子所示,因为大多数简写形式都不是合法的ECMAScript标识符:
let text = "this has been a short summer";
let pattern = /(.)hort/g;
/*
* 注意:Opera不支持简写属性名
* IE不支持多行匹配
*/
if (pattern.test(text)) {console.log(RegExp.$_); // this has been a short summerconsole.log(RegExp["$`"]); // this has been aconsole.log(RegExp["$'"]); // summerconsole.log(RegExp["$&"]); // shortconsole.log(RegExp["$+"]); // sconsole.log(RegExp["$*"]); // false
}
RegExp 还有其他几个构造函数属性,可以存储最多9个捕获组的匹配项。这些属性通过 RegExp.$1~RegExp.$9 来访问,分别包含第1~9个捕获组的匹配项。在调用 exec() 或 test() 时,这些属性就会被填充,然后就可以像下面这样使用它们:
let text = "this has been a short summer";
let pattern = /(..)or(.)/g;
if (pattern.test(text)) {console.log(RegExp.$1); // shconsole.log(RegExp.$2); // t
}
在这个例子中,模式包含两个捕获组。调用 test() 搜索字符串之后,因为找到了匹配项所以返回 true ,而且可以打印出通过 RegExp 构造函数的 $1 和 $2 属性取得的两个捕获组匹配的内容。
注意 RegExp 构造函数的所有属性都没有任何Web标准出处,因此不要在生产环境中使用它们。