RegExp对象提供正则表达式的功能。

有关 JS 正则的更多内容,详见:

  1. JS 正则匹配规则
  2. JS 正则练习
  3. ES6 正则的扩展

由于 RegExp 的相关内容较多,会分为若干文档来介绍,在本文档中,我们仅介绍 RegExp 对象的一些实例方法以及正则在字符串实例方法中的一些基本用法。

重点:

  1. 验证字符串 str 是否匹配 reg:reg.test(str)
  2. 获取字符串 str 匹配 reg 的结果:reg.exec(str)**str.match(reg)**
  3. 获取字符串 str 中满足 reg 匹配规则的第一个成员索引:str.search(reg)
  4. lastIndex决定了下一次匹配的开始位置
  5. 替换字符串 str1 中满足 reg 匹配规则的所有成员为 str2:str1.replace(reg, str2)

概述

正则表达式(regular expression)是一种表达文本模式(即字符串结构)的方法,有点像字符串的模板,常常用来按照“给定模式”匹配文本。比如,正则表达式给出一个 Email 地址的模式,然后用它来确定一个字符串是否为 Email 地址。JavaScript 的正则表达式体系是参照 Perl 5 建立的

新建正则表达式有两种方法。一种是使用字面量,以斜杠表示开始和结束。

  1. var regex = /xyz/;

另一种是使用RegExp构造函数。

  1. var regex = new RegExp('xyz');

上面两种写法是等价的,都新建了一个内容为xyz的正则表达式对象。它们的主要区别是:

  • 写法1:第一种方法在引擎编译代码时,就会新建正则表达式
  • 写法2:第二种方法在运行时新建正则表达式

写法 1 效率较高,且比较便利和直观,所以实际应用中,基本上都采用字面量定义正则表达式。

RegExp构造函数还可以接受第二个参数,表示修饰符

  1. var regex = new RegExp('xyz', 'i');
  2. // 等价于
  3. var regex = /xyz/i;

上面代码中,正则表达式/xyz/有一个修饰符i

实例属性

正则对象的实例属性分成两类。

一类是修饰符相关,用于了解设置了什么修饰符。

  • RegExp.prototype.ignoreCase:返回一个布尔值,表示是否设置了i修饰符。
  • RegExp.prototype.global:返回一个布尔值,表示是否设置了g修饰符。
  • RegExp.prototype.multiline:返回一个布尔值,表示是否设置了m修饰符。
  • RegExp.prototype.flags:返回一个字符串,包含了已经设置的所有修饰符,按字母排序。

上面四个属性都是只读的。

  1. var r = /abc/igm;
  2. r.ignoreCase // true
  3. r.global // true
  4. r.multiline // true
  5. r.flags // 'gim'

另一类是与修饰符无关的属性,主要是下面两个。

  • RegExp.prototype.lastIndex:返回一个整数,表示下一次开始搜索的位置。该属性可读写,但是只在进行连续搜索时有意义,详细介绍请看后文。
  • RegExp.prototype.source:返回正则表达式的字符串形式(不包括反斜杠),该属性只读。
  1. var r = /abc/igm;
  2. r.lastIndex // 0
  3. r.source // "abc"

实例方法

RegExp.prototype.test()

正则实例对象的**test**方法返回一个布尔值,表示当前模式是否能匹配参数字符串

  1. /cat/.test('cats and dogs') // true

上面代码验证参数字符串之中是否包含cat,结果返回true

如果正则表达式带有g修饰符,则每一次test方法都从上一次结束的位置开始向后匹配。

  1. var r = /x/g;
  2. var s = '_x_x';
  3. r.lastIndex // 0
  4. r.test(s) // true
  5. r.lastIndex // 2
  6. r.test(s) // true
  7. r.lastIndex // 4
  8. r.test(s) // false

上面代码的正则表达式使用了g修饰符,表示是全局搜索,会有多个结果。接着,三次使用test方法,每一次开始搜索的位置都是上一次匹配的后一个位置。

带有g修饰符时,可以通过正则对象的lastIndex属性指定开始搜索的位置。

  1. var r = /x/g;
  2. var s = '_x_x';
  3. r.lastIndex = 4;
  4. r.test(s) // false
  5. r.lastIndex // 0
  6. r.test(s) // true

上面代码指定从字符串的第五个位置开始搜索,这个位置为空,所以返回false。同时,lastIndex属性重置为0,所以第二次执行r.test(s)会返回true

注意,带有g修饰符时,正则表达式内部会记住上一次的lastIndex属性,这时不应该更换所要匹配的字符串,否则会有一些难以察觉的错误。

  1. var r = /bb/g;
  2. r.test('bb') // true
  3. r.test('-bb-') // false

上面代码中,由于正则表达式r是从上一次的lastIndex位置开始匹配,导致第二次执行test方法时出现预期以外的结果。

**lastIndex**属性只对同一个正则表达式有效,所以下面这样写是错误的。

  1. var count = 0;
  2. while (/a/g.test('babaa')) count++;

上面代码会导致无限循环,因为while循环的每次匹配条件都是一个新的正则表达式,导致lastIndex属性总是等于0。

如果正则模式是一个空字符串,则匹配所有字符串

  1. new RegExp('').test('abc')
  2. // true

RegExp.prototype.exec()

正则实例对象的exec()方法,用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回null

  1. var s = '_x_x';
  2. var r1 = /x/;
  3. var r2 = /y/;
  4. r1.exec(s) // ["x"]
  5. r2.exec(s) // null

上面代码中,正则对象r1匹配成功,返回一个数组,成员是匹配结果;正则对象r2匹配失败,返回null

如果正则表示式包含圆括号(即含有“组匹配”),则返回的数组会包括多个成员。第一个成员是整个匹配成功的结果,后面的成员就是圆括号对应的匹配成功的组。也就是说,第二个成员对应第一个括号,第三个成员对应第二个括号,以此类推。整个数组的length属性等于组匹配的数量再加1。

  1. var s = '_x_x';
  2. var r = /_(x)/;
  3. r.exec(s) // ["_x", "x"]

上面代码的exec()方法,返回一个数组。第一个成员是整个匹配的结果,第二个成员是圆括号匹配的结果。

exec()方法的返回数组还包含以下两个属性:

  • input:整个原字符串。
  • index:模式匹配成功的开始位置(从0开始计数)。
  1. var r = /a(b+)a/;
  2. var arr = r.exec('_abbba_aba_');
  3. arr // ["abbba", "bbb"]
  4. arr.index // 1
  5. arr.input // "_abbba_aba_"

上面代码中的index属性等于1,是因为从原字符串的第二个位置开始匹配成功。

如果正则表达式加上g修饰符,则可以使用多次exec()方法,下一次搜索的位置从上一次匹配成功结束的位置开始。

  1. var reg = /a/g;
  2. var str = 'abc_abc_abc'
  3. var r1 = reg.exec(str);
  4. r1 // ["a"]
  5. r1.index // 0
  6. reg.lastIndex // 1
  7. var r2 = reg.exec(str);
  8. r2 // ["a"]
  9. r2.index // 4
  10. reg.lastIndex // 5
  11. var r3 = reg.exec(str);
  12. r3 // ["a"]
  13. r3.index // 8
  14. reg.lastIndex // 9
  15. var r4 = reg.exec(str);
  16. r4 // null
  17. reg.lastIndex // 0

上面代码连续用了四次exec()方法,前三次都是从上一次匹配结束的位置向后匹配。当第三次匹配结束以后,整个字符串已经到达尾部,匹配结果返回null,正则实例对象的lastIndex属性也重置为0,意味着第四次匹配将从头开始。

利用g修饰符允许多次匹配的特点,可以用一个循环完成全部匹配。

  1. var reg = /a/g;
  2. var str = 'abc_abc_abc'
  3. while(true) {
  4. var match = reg.exec(str);
  5. if (!match) break;
  6. console.log('#' + match.index + ':' + match[0]);
  7. }
  8. // #0:a
  9. // #4:a
  10. // #8:a

上面代码中,只要exec()方法不返回null,就会一直循环下去,每次输出匹配的位置和匹配的文本。

正则实例对象的lastIndex属性不仅可读,还可写。设置了g修饰符的时候,只要手动设置了lastIndex的值,就会从指定位置开始匹配。

字符串的实例方法

字符串的实例方法之中,有 4 种与正则表达式有关。

  • String.prototype.match():返回一个数组,成员是所有匹配的子字符串。
  • String.prototype.search():按照给定的正则表达式进行搜索,返回一个整数,表示匹配开始的位置。
  • String.prototype.replace():按照给定的正则表达式进行替换,返回替换后的字符串。
  • String.prototype.split():按照给定规则进行字符串分割,返回一个数组,包含分割后的各个成员。

String.prototype.match()

字符串实例对象的match方法对字符串进行正则匹配,返回匹配结果。

  1. var s = '_x_x';
  2. var r1 = /x/;
  3. var r2 = /y/;
  4. s.match(r1) // ["x"]
  5. s.match(r2) // null

从上面代码可以看到,字符串的match方法与正则对象的exec方法非常类似:匹配成功返回一个数组,匹配失败返回null

如果正则表达式带有g修饰符,则该方法与正则对象的exec方法行为不同,会一次性返回所有匹配成功的结果。

  1. var s = 'abba';
  2. var r = /a/g;
  3. s.match(r) // ["a", "a"]
  4. r.exec(s) // ["a"]

设置正则表达式的lastIndex属性,对match方法无效,匹配总是从字符串的第一个字符开始。

  1. var r = /a|b/g;
  2. r.lastIndex = 7;
  3. 'xaxb'.match(r) // ['a', 'b']
  4. r.lastIndex // 0

上面代码表示,设置正则对象的lastIndex属性是无效的。

String.prototype.search()

字符串对象的search方法,返回第一个满足条件的匹配结果在整个字符串中的位置。如果没有任何匹配,则返回-1

  1. '_x_x'.search(/x/)
  2. // 1

上面代码中,第一个匹配结果出现在字符串的1号位置。

String.prototype.replace()

字符串对象的replace方法可以替换匹配的值。它接受两个参数,第一个是正则表达式,表示搜索模式,第二个是替换的内容。

  1. str.replace(search, replacement)

正则表达式如果不加g修饰符,就替换第一个匹配成功的值,否则替换所有匹配成功的值。

  1. 'aaa'.replace('a', 'b') // "baa"
  2. 'aaa'.replace(/a/, 'b') // "baa"
  3. 'aaa'.replace(/a/g, 'b') // "bbb"

上面代码中,最后一个正则表达式使用了g修饰符,导致所有的a都被替换掉了。

replace方法的一个应用,就是消除字符串首尾两端的空格。

  1. var str = ' #id div.class ';
  2. str.replace(/^\s+|\s+$/g, '')
  3. // "#id div.class"

replace方法的第二个参数可以使用美元符号$,用来指代所替换的内容。

  • $&:匹配的子字符串。
  • `$``:匹配结果前面的文本。
  • $':匹配结果后面的文本。
  • $n:匹配成功的第n组内容,n是从1开始的自然数。
  • $$:指代美元符号$
  1. 'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')
  2. // "world hello"
  3. 'abc'.replace('b', '[$`-$&-$\']')
  4. // "a[a-b-c]c"

上面代码中,第一个例子是将匹配的组互换位置,第二个例子是改写匹配的值。

replace方法的第二个参数还可以是一个函数,将每一个匹配内容替换为函数返回值。

  1. '3 and 5'.replace(/[0-9]+/g, function (match) {
  2. return 2 * match;
  3. })
  4. // "6 and 10"
  5. var a = 'The quick brown fox jumped over the lazy dog.';
  6. var pattern = /quick|brown|lazy/ig;
  7. a.replace(pattern, function replacer(match) {
  8. return match.toUpperCase();
  9. });
  10. // The QUICK BROWN fox jumped over the LAZY dog.

作为replace方法第二个参数的替换函数,可以接受多个参数。其中,第一个参数是捕捉到的内容,第二个参数是捕捉到的组匹配(有多少个组匹配,就有多少个对应的参数)。此外,最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置(比如从第五个位置开始),最后一个参数是原字符串。下面是一个网页模板替换的例子。

  1. var prices = {
  2. 'p1': '$1.99',
  3. 'p2': '$9.99',
  4. 'p3': '$5.00'
  5. };
  6. var template = '<span id="p1"></span>'
  7. + '<span id="p2"></span>'
  8. + '<span id="p3"></span>';
  9. template.replace(
  10. /(<span id=")(.*?)(">)(<\/span>)/g,
  11. function(match, $1, $2, $3, $4){
  12. return $1 + $2 + $3 + prices[$2] + $4;
  13. }
  14. );
  15. // "<span id="p1">$1.99</span><span id="p2">$9.99</span><span id="p3">$5.00</span>"

上面代码的捕捉模式中,有四个括号,所以会产生四个组匹配,在匹配函数中用$1$4表示。匹配函数的作用是将价格插入模板中。

String.prototype.split()

字符串对象的split方法按照正则规则分割字符串,返回一个由分割后的各个部分组成的数组。

  1. str.split(separator, [limit])

该方法接受两个参数,第一个参数是正则表达式,表示分隔规则,第二个参数是返回数组的最大成员数。

  1. // 非正则分隔
  2. 'a, b,c, d'.split(',')
  3. // [ 'a', ' b', 'c', ' d' ]
  4. // 正则分隔,去除多余的空格
  5. 'a, b,c, d'.split(/, */)
  6. // [ 'a', 'b', 'c', 'd' ]
  7. // 指定返回数组的最大成员
  8. 'a, b,c, d'.split(/, */, 2)
  9. [ 'a', 'b' ]

上面代码使用正则表达式,去除了子字符串的逗号后面的空格。

  1. // 例一
  2. 'aaa*a*'.split(/a*/)
  3. // [ '', '*', '*' ]
  4. // 例二
  5. 'aaa**a*'.split(/a*/)
  6. // ["", "*", "*", "*"]

上面代码的分割规则是0次或多次的a,由于正则默认是贪婪匹配,所以例一的第一个分隔符是aaa,第二个分割符是a,将字符串分成三个部分,包含开始处的空字符串。例二的第一个分隔符是aaa,第二个分隔符是0个a(即空字符),第三个分隔符是a,所以将字符串分成四个部分。

如果正则表达式带有括号,则括号匹配的部分也会作为数组成员返回。

  1. 'aaa*a*'.split(/(a*)/)
  2. // [ '', 'aaa', '*', 'a', '*' ]

上面代码的正则表达式使用了括号,第一个组匹配是aaa,第二个组匹配是a,它们都作为数组成员返回。

问:Perl 5 是什么?

Perl 5是一种高级的、通用的、解释的、动态的编程语言。Perl 原本是为了文本处理而设计的,它在处理文本、尤其是处理正则表达式方面的能力特别强大。

Perl 5 的开发始于1994年,由 Larry Wall 领导的开发团队进行,这是 Perl 语言的主要版本,并且至今仍然在积极开发和维护中。Perl 5 引入了很多新的特性和模块,比如面向对象的编程支持、新的正则表达式引擎等等。

许多语言,包括 JavaScript、Python、Ruby、PHP 等,都受到了 Perl 在处理文本和正则表达式方面的影响,因此,你在这些语言中都可以看到类似于 Perl 的正则表达式语法。

总的来说,Perl 5 是一种十分重要的编程语言,对许多现代编程语言都产生了影响。

问:正则表达式是什么?

以下是一些常见的说法:

  1. 正则表达式是一个规则,用于验证字符串,该规则是国际标准,是跨越语言的。
  2. 正则就是用来匹配字符串的一种规则,正则表达式就是描述这种规则的式子。
  3. 正则表达式是一种在字符串中执行复杂检索和替换操作的强大工具,可以进行模式(pattern)匹配以及变量提取和替换等操作。
  4. 正则表达式是一个通用的概念,被很多编程语言如 JavaScript、Python、Java、PHP 等采用,并且每种语言对正则表达式的实现都大同小异。
  5. ……

问:正则表达式的组成?

简化版的回答:

一个正则表达式由两种字符构成:

  1. 普通字符(例如字母 a 到 z)用于精确匹配字符串
  2. 非普通字符(例如 *.?)用于定义一个搜索模式

我们学习正则,主要就是在学如何编写满足要求的“搜索模式”去匹配字符串。


更精确一些的回答:

正则表达式主要由以下部分组成:

  1. 普通字符:包括没有特殊定义的所有可打印和不可打印字符,如a-zA-Z0-9等。
  2. 特殊字符:包括 \, ^, $, ., |, ?, *, +, (, ), [, ], {, } 等,这些字符在正则表达式中都有特殊的含义。
  3. 字符类:用方括号表示,表示匹配指定范围内的字符。如[abc]表示匹配abc
  4. 预定义类:用来匹配常见字符类别的简写,如:
    • \d:匹配数字
    • \w:匹配字母,数字和下划线
    • \s:匹配空格字符(包括空格、制表符、换页符、换行符等)
    • \D, \W, \S分别表示\d, \w, \s的反义,即匹配非数字、非字母和数字、非空白字符。
  5. 边界:用来指定一个位置,常用的如:
    • ^:表示字符串的开始
    • $:表示字符串的结束
    • \b:表示单词的边界。
  6. 量词:用来指定一个模式出现的次数,常用的如:
    • *:表示0次或多次
    • +:表示1次或多次
    • ?:表示0次或1次
    • {n}:表示恰好n次
    • {n,}:表示至少n次
    • {n,m}:表示至少n次,最多m次。
  7. 分组:使用圆括号表示,用于把多个字符看作一个整体。
  8. 选择:使用竖线字符|表示,表示从多个模式中选择一个。
  9. 反向引用:表示引用之前的分组。
  10. 预查:用于声明一个必须存在或必须不存在的子串。

以上只是正则表达式的基本组成部分,不同的语言可能会提供更多的正则表达式特性。

问:JS 中正则表达式的本质是什么?

在 JS 中,正则表达式是有 RegExp 构造函数创建的一个实例对象。

问:正则表达式有什么用?

  1. 验证输入是否满足某种复杂规则。例如验证一个字符串是否为电子邮件地址、手机号码等。
  2. 在一个大的文本或字符串中寻找或替换特定的模式。例如在一个文本文档中替换所有匹配的字符串。
  3. 抽取信息。例如从日志文件、网络数据或其他文本信息中提取数据。
  4. ……

问:如何创建正则表达式?

  1. 方式1:通过字面量的方式来创建正则表达式 /正则字符串/flag
  2. 方式2:通过构造函数的方式来创建正则表达式 new RegExp("正则字符串", flag)new RegExp(正则对象, flag)
  3. 方式3:不使用 new 关键字,直接使用构造函数来创建 RegExp("正则字符串", flag)RegExp(正则对象, flag)

其中,方式 1 其实是方式 2 的语法糖,它们是等效的。至于方式 2 和方式 3,在传入的第一个参数是一个正则对象的时候,它们有一丢丢细微的差异:

  • new RegExp(正则对象, flag) 会返回一个全新的正则对象
  • RegExp(正则对象, flag) 会将原对象给返回

问:lastIndextestexec 你知道正则对象身上的这三个成员吗?

  • lastIndex 是正则对象的一个属性,表示的是下一次开始匹配的下标,它是可读可写的;
  • test 是正则对象的实例方法,用于检测某个字符串是否满足匹配要求,返回值是一个 boolean 类型;
  • exec 是正则对象的实例方法,相当于在执行匹配,返回的是一个数组,包含了本次匹配的相关数据;本次匹配的相关数据,包括但不限于:
    • 参与匹配的字符串
    • 满足匹配要求的部分
    • 满足匹配要求的字符串的出现位置
    • 捕获组

参考链接