十.JavaScript正则表达式

139 阅读5分钟

正则表达式(regular expression)是一个描述字符模式的对象。

1. 正则表达式的定义

JavaScript中的正则表达式用RegExp对象表示,可以使用RegExp()构造函数来创建RegExp对象。不过RegExp对象更多的是通过一种特殊的直接量语法来创建。就像通过引号包裹字符的方式来定义字符串直接量一样,正则表达式直接量定义为包含在一对斜杠(/)之间的字符,例如:

var pattern=/s$/;
var pattern = new RegExp("s$"); //等价

运行这段代码创建一个新的RegExp对象,并将它赋值给变量pattern。这个特殊的RegExp对象用来匹配所有以字母"s"结尾的字符串。

同一段代码所表示的正则表达式直接量的每次运算都返回新对象。

正则表达式的模式规则是由一个字符序列组成的。包括所有字母和数字在内,大多数的字符都是按照直接量仅描述待匹配的字符的。正则表达式/java/可以匹配任何包含"java"子串的字符

1.1 正则表达式的直接量字符

正则表达式中的所有字母和数字都是按照字面含义进行匹配的。

JavaScript正则表达式语法也支持非字母的字符匹配,这些字符需要通过反斜线(\)作为前缀进行转义。比如,转义字符\n用以匹配换行符。

image.png

在正则表达式中,许多标点符号具有特殊含义,它们是:

^$.*+?=!:|\/()[]{}
  • 如果想在正则表达式中使用上边这些字符的直接量进行匹配,则必须使用前缀\
  • 要想在正则表达式中按照直接量匹配反斜线本身,则必须使用反斜线将其转义。比如,正则表达式“/\\/”用以匹配任何包含反斜线的字符串。

1.2 正则表达式的字符类

  • 将直接量字符单独放进方括号内就组成了字符类(character class)。一个字符类可以匹配它所包含的任意字符。因此,正则表达式/[abc]/就和字母"a"、"b"、"c"中的任意一个都匹配。
  • 可以通过“^”符号来定义否定字符类,它匹配所有不包含在方括号内的字符。定义否定字符类时,将一个“^”符号作为左方括号内的第一个字符。正则表达式/[^abc]/匹配的是"a"、"b"、"c"之外的所有字符。
  • 字符类可以使用连字符来表示字符范围。要匹配拉丁字母表中的小写字母,可以使用/[a-z]/,要匹配拉丁字母表中任何字母和数字,则使用/[a-zA-Z0-9]/。

image.png

注意,在方括号之内也可以写这些特殊转义字符。比如,由于\s匹配所有的空白字符,\d匹配的是所有数字,因此/[\s\d]/就匹配任意空白符或者数字。

1.3 正则表达式的重复字符

可以把两位数描述成/\d\d/,四位数描述成/\d\d\d\d/。正则表达式中某元素的“重复出现次数”。

我们在正则模式之后跟随用以指定字符重复的标记。例如,“+”用以匹配前一个模式的一个或多个副本。

image.png

/\d{2,4}/ //匹配2~4个数字

/\w{3}\d?/ //精确匹配三个单词和一个可选的数字

/\s+java\s+/  //匹配前后带有一个或多个空格的字符串"java"

/[^(]*/  //匹配一个或多个非左括号的字符

在使用“*”和“?”时要注意,由于这些字符可能匹配0个字符,因此它们允许什么都不匹配。例如,正则表达式/a*/实际上与字符串"bbbb"匹配,因为这个字符串含有0个a。

1.3.1 非贪婪的重复

上表列出的匹配重复字符是尽可能多地匹配,而且允许后续的正则表达式继续匹配。因此,我们称之为“贪婪的”匹配。

我们同样可以使用正则表达式进行非贪婪匹配。只须在待匹配的字符后跟随一个问号即可:“??”、“+?”、“*?”或“{1,5}?”。

表达式/a+/可以匹配一个或多个连续的字母a。当使用"aaa"作为匹配字符串时,正则表达式会匹配它的三个字符。/a+?/也可以匹配一个或多个连续字母a,但它是尽可能少地匹配。我们同样将"aaa"作为匹配字符串,只能匹配第一个a。

1.4 指定选择项、子表达式分组、引用前边的子表达式

1.4.1 指定选择项

字符“|”用于分隔供选择的字符。例如,/ab|cd|ef/可以匹配字符串"ab",也可以匹配字符串"cd",还可以匹配字符串"ef"。

注意,选择项的尝试匹配次序是从左到右,直到发现了匹配项。如果左边的选择项匹配,就忽略右边的匹配项,即使它产生更好的匹配。因此,当正则表达式/a|ab/匹配字符串"ab"时,它只能匹配第一个字符。

1.4.2 正则表达式中的圆括号作用

  • 把单独的项组合成子表达式,以便可以像处理一个独立的单元那样用“|”、“*”、“+”或者“?”等来对单元内的项进行处理。例如,/(ab|cd)+|ef/可以匹配字符串"ef",也可以匹配字符串"ab"或"cd"的一次或多次重复。
  • 在完整的模式中定义子模式。当一个正则表达式成功地和目标字符串相匹配时,可以从目标串中抽出和圆括号中的子模式相匹配的部分
  • 允许在同一正则表达式的后部引用前面的子表达式。这是通过在字符“\”后加一位或多位数字来实现的。这个数字指定了带圆括号的子表达式在正则表达式中的位置。例如,\1引用的是第一个带圆括号的子表达式。注意,因为子表达式可以嵌套另一个子表达式,所以它的位置是参与计数的左括号的位置。

对正则表达式中前一个子表达式的引用,并不是指对子表达式模式的引用,而指的是与那个模式相匹配的文本的引用。这样,引用可以用于实施一条约束,即一个字符串各个单独部分包含的是完全相同的字符

/(['"])[^'"]*\1/
// 左侧的引号必须和右侧的引号相匹配。
// \1匹配的是第一个带圆括号的子表达式所匹配的模式

在正则表达式中不用创建带数字编码的引用,也可以对子表达式进行分组。它不是以“(”和“)”进行分组,而是以“(?:”和“)”来进行分组

image.png

1.5 指定匹配位置

正则表达式的锚元素不匹配某个可见的字符,它们指定匹配发生的合法位置。为它们将模式定位在搜索字符串的特定位置上。

最常用的锚元素是^,它用来匹配字符串的开始,锚元素$用以匹配字符串的结束。例如,要匹配单词"JavaScript",可以使用正则表达式/^JavaScript$/

任意正则表达式都可以作为锚点条件。

如果在符号“(?=”和“)”之间加入一个表达式,它就是一个先行断言,用以说明圆括号内的表达式必须正确匹配。/[Jj]ava([Ss]cript)?(?=\:)/这个正则表达式只在其后有冒号时才匹配,可以匹配"JavaScript:The Definitive Guide"中的"JavaScript",但是不能匹配"Java in a Nutshell"中的"Java",因为它后面没有冒号。

带有“(?!”的断言是负向先行断言,用以指定接下来的字符都不必匹配。 /Java(?!Script)([A-Z]\w*)/Java后面不能跟随"Script",可以匹配"JavaBeans",但不能匹配"Javanese";它可以匹配"JavaScript",但不能匹配"JavaScripter"。

image.png

1.6 修饰符

正则表达式的修饰符,用以说明高级匹配模式的规则。

修饰符是放在“/”符号之外的,也就是说,它们不是出现在两条斜线之间,而是第二条斜线之后。

image.png

  • 修饰符"i"用以说明模式匹配是不区分大小写的。
  • 修饰符"g"说明模式匹配应该是全局的,也就是说,应该找出被检索字符串中所有的匹配。
  • 修饰符"m"用以在多行模式中执行匹配,在这种模式下,如果待检索的字符串包含多行,那么^和$锚字符除了匹配整个字符串的开始和结尾之外,还能匹配每行的开始和结尾。比如正则表达式/java$/im可以匹配"java"也可以匹配"Java\nis fun"。

修饰符可以任意组合 例如 /java/gi

2 与正则相关的String方法

2.1 search()

它的参数是一个正则表达式,返回第一个与之匹配的子串的起始位置,如果找不到匹配的子串,它将返回-1。

"JavaScript".search(/script/i); // 4

如果search()的参数不是正则表达式,则首先会通过RegExp构造函数将它转换成正则表达式,search()方法不支持全局检索,因为它忽略正则表达式参数中的修饰符g。

2.2 replace()

replace()方法用以执行检索与替换操作。

  • 其中第一个参数是一个正则表达式,第二个参数是要进行替换的字符串。这个方法会对调用它的字符串进行检索,使用指定的模式来匹配。
  • 如果正则表达式中设置了修饰符g,那么源字符串中所有与模式匹配的子串都将替换成第二个参数指定的字符串;
  • 如果不带修饰符g,则只替换所匹配的第一个子串。
  • 如果replace()的第一个参数是字符串而不是正则表达式,则replace()将直接搜索这个字符串,而不是像search()一样首先通过RegExp()将它转换为正则表达式。
//将所有不区分大小写的javascript都替换成大小写正确的JavaScript
text.replace(/javascript/gi, "JavaScript");

如果在替换字符串中出现了$加数字,那么replace()将用与指定的子表达式相匹配的文本来替换这两个字符。

replace()方法的第二个参数可以是函数,该函数能够动态地计算替换字符串。

2.3 match()

最常用的String正则表达式方法。它的唯一参数就是一个正则表达式(或通过RegExp()构造函数将其转换为正则表达式),返回的是一个由匹配结果组成的数组。

"1 plus 2 equals 3".match(/\d+/g); //返回["1","2","3"]
  • 如果该正则表达式设置了修饰符g,则该方法返回的数组包含字符串中的所有匹配结果。
  • 如果这个正则表达式没有设置修饰符g,match()就不会进行全局检索,它只检索第一个匹配。但即使match()执行的不是全局检索,它也返回一个数组。在这种情况下,数组的第一个元素就是匹配的字符串,余下的元素则是正则表达式中用圆括号括起来的子表达式。
var url = /(\w+):\/\/([\w.]+)\/(\S*)/;
var text = "Visit my blog at http://www.example.com/~david";
var result = text.match(url);
if (result != null) {
  var fullurl = result[0]; //包含"http://www.example.com/~david"
  var protocol = result[1]; //包含"http"
  var host = result[2]; //包含"www.example.com"
  var path = result[3]; //包含"~david"
}

image.png

2.4 split()

split()方法的参数也可以是一个正则表达式

例如,可以指定分隔符,允许两边可以留有任意多的空白符:

"1,2,3, 4,5 ".split(/\s*,\s*/); //返回["1","2","3","4","5"]

image.png

3 RegExp对象

正则表达式是通过RegExp对象来表示的。

  • RegExp()构造函数带有两个字符串参数,其中第二个参数是可选的,RegExp()用以创建新的RegExp对象。
  • 第一个参数包含正则表达式的主体部分,也就是正则表达式直接量中两条斜线之间的文本。
  • 如果提供第二个参数,它就指定正则表达式的修饰符。不过只能传入修饰符g、i、m或者它们的组合。

需要注意的是,不论是字符串直接量还是正则表达式,都使用“\”字符作为转义字符的前缀,因此当给RegExp()传入一个字符串表述的正则表达式时,必须将“\”替换成“\\”。

//全局匹配字符串中的5个数字,注意这里使用了"\\",而不是"\"
var zipcode = new RegExp("\\d{5}", "g");

3.1 RegExp对象属性

image.png

  • 属性source是一个只读的字符串,包含正则表达式的文本。
  • 属性global是一个只读的布尔值,用以说明这个正则表达式是否带有修饰符g。
  • 属性ignoreCase也是一个只读的布尔值,用以说明正则表达式是否带有修饰符i。
  • 属性multiline是一个只读的布尔值,用以说明正则表达式是否带有修饰符m。

3.2 RegExp对象方法

RegExp方法的参数是一个字符串,而String方法的参数是一个RegExp对象。

3.2.1 exec()

exec()方法在一个字符串中执行匹配检索。

  • 如果它没有找到任何匹配,它就返回null,
  • 但如果它找到了一个匹配,它将返回一个数组,就像match()方法为非全局检索返回的数组一样。这个数组的第一个元素包含的是与正则表达式相匹配的字符串,余下的元素是与圆括号内的子表达式相匹配的子串。
  • 属性index包含了发生匹配的字符位置,
  • 属性input引用的是正在检索的字符串。

当match()的参数是一个全局正则表达式时,它返回由匹配结果组成的数组。相比之下,exec()总是返回一个匹配结果,并提供关于本次匹配的完整信息。

我们可以在用正则表达式匹配字符串的过程中反复调用exec()

var pattern = /Java/g;
var text = "JavaScript is more fun than Java!";
var result;
while ((result = pattern.exec(text)) != null) {
  console.log(result);
  console.log(
    "Matched'" +
      result[0] +
      "'" +
      "at position" +
      result.index +
      ";next search begins at" +
      pattern.lastIndex
  );
}

image.png

3.2.2 test()

test()方法的参数是一个字符串,用test()对某个字符串进行检测,如果包含正则表达式的一个匹配结果,则返回true:

var pattern = /java/i;
pattern.test("JavaScript"); //返回true

调用test()和调用exec()等价,当exec()的返回结果不是null时,test()返回true。