【JavaScript 漫游】【014】正则表达式通关


文章简介

JS 语言中的 RegExp 对象提供正则表达式的功能。本篇文章旨在对该对象的相关知识点进行总结。内容包括:

  1. 正则表达式概述
  2. RegExp 对象的实例属性
  3. RegExp 对象的实例方法
  4. 字符串与正则表达式相关的实例方法
  5. 正则表达式匹配规则

概述

正则表达式的概念

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

新建正则表达式的两种方法

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

js 复制代码
var regex = /xyz/;

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

js 复制代码
var regex = new RegExp('xyz');

两种写法的主要区别是,第一种方法在引擎编译代码时,就会新建正则表达式,第二种语法在运行时新建正则表达式,所以前者的效率较高。而且,前者比较便利和直观,所以实际应用中,基本上都采用字面量定义正则表达式。

实例属性

修饰符相关的实例属性

  • RegExp.prototype.ignoreCase:返回一个布尔值,表示是否设置了 i 修饰符
  • RegExp.prototype.global:返回一个布尔值,表示是否设置了 g 修饰符
  • RegExp.prototype.multiline:返回一个布尔值,表示是否设置了 m 修饰符
  • RegExp.prototype.flags:返回一个字符串,包含了已经设置的所有修饰符,按字母排序
js 复制代码
var r = /abc/igm;

r.ignoreCase; // true
r.global; // true
r.multiline; // true
r.flags; // 'gim'

与修饰符无关的属性

  • RegExp.prototype.lastIndex:返回一个整数,表示下一次开始搜索的位置。该属性可读写,但是只是进行连续搜索时有意义
  • RegExp.prototype.source:返回正则表达式的字符串形式(不包括反斜杠),该属性只读
js 复制代码
var r = /abc/igm;

r.lastIndex; // 0
r.source; // 'abc'

实例方法

RegExp.prototype.test()

该方法返回一个布尔值,表示当前模式是否能匹配参数字符串。

js 复制代码
/cat/.test('cats and dogs'); // true

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

js 复制代码
var r = /x/g;
var s = '_x_x';

r.lastIndex // 0
r.test(s) // true

r.lastIndex // 2
r.test(s) // true

r.lastIndex // 4
r.test(s) // false

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

js 复制代码
var r = /x/g;
var s = '_x_x';

r.lastIndex = 4;
r.test(s);

r.lastIndex; // 0
r.test(s);

RegExp.prototype.exec()

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

js 复制代码
var s = '_x_x';
var r1 = /x/;
var r2 = /y/;

r1.exec(s) // ["x"]
r2.exec(s) // null

如果正则表达式包含圆括号(即含有"组匹配"),则返回的数组会包括多个成员。第一个成员是整个匹配成功的结果,后面的成员就是圆括号对应的匹配成功的组。

js 复制代码
var s = '_x_x';
var r = /_(x)/;

r.exec(s) // ["_x", "x"]

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

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

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

js 复制代码
var reg = /a/g;
var str = 'abc_abc_abc';

var r1 = reg.exec(str);
r1 // ["a"]
r1.index // 0
reg.lastIndex // 1

var r2 = reg.exec(str);
r2 // ["a"]
r2.index // 4
reg.lastIndex // 5

var r3 = reg.exec(str);
r3 // ["a"]
r3.index // 8
reg.lastIndex // 9

var r4 = reg.exec(str);
r4 // null
reg.lastIndex // 0

字符串与正则表达式相关的实例方法

String.prototype.match()

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

js 复制代码
var s = '_x_x';
var r1 = /x/;
var r2 = /y/;

s.match(r1) // ["x"]
s.match(r2) // null

match() 与正则对象的 exec 方法非常类似:匹配成功返回一个数组,匹配失败返回 null

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

js 复制代码
var s = 'abba';
var r = /a/g;

s.match(r) // ["a", "a"]
r.exec(s) // ["a"]

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

js 复制代码
var r = /a|b/g;
r.lastIndex = 7;

'xaxb'.match(r) // ['a', 'b']
r.lastIndex // 0

String.prototype.search()

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

js 复制代码
'_x_x'.search(/x/)
// 1

String.prototype.replace()

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

js 复制代码
str.replace(search, replacement);

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

js 复制代码
'aaa'.replace('a', 'b'); // 'baa'
'aaa'.replace(/a/, 'b'); // 'baa'
'aaa'.replace(/a/g, 'b'); // 'bbb'

String.prototype.split()

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

js 复制代码
str.split(separator, [limit]);

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

js 复制代码
// 非正则分隔
'a,  b,c, d'.split(',')
// [ 'a', '  b', 'c', ' d' ]

// 正则分隔,去除多余的空格
'a,  b,c, d'.split(/, */)
// [ 'a', 'b', 'c', 'd' ]

// 指定返回数组的最大成员
'a,  b,c, d'.split(/, */, 2)
[ 'a', 'b' ]

匹配规则

字面量字符和元字符

大部分字符在正则表达式中,就是字面的含义,比如 /a/ 匹配 a/b/ 匹配 b。如果在正则表达式之中,某个字符只表示它字面的含义,那么它们就叫做"字面量字符"(literal characters)

js 复制代码
/dog/.test('old dog'); // true

除了字面量字符以外,还有一部分字符有特殊含义,不代表字面的意思。它们叫做"元字符"(metacharacters),主要有以下几个

点字符 .

点字符. 匹配除回车\r、换行\n、行分隔符\u2028 和 段分割符\u2029以外的所有字符。注意,对于码点大于 0xFFFF 字符,点字符不能正确匹配,会认为这是两个字符。

js 复制代码
/c.t/.test('cat'); // true
/c.t/.test('caat'); // false

位置字符

位置字符用来提示字符所处的位置,主要有两个字符。

  • ^表示字符串的开始位置
  • $表示字符串的结束位置
js 复制代码
// test必须出现在开始位置
/^test/.test('test123') // true

// test必须出现在结束位置
/test$/.test('new test') // true

// 从开始位置到结束位置只有test
/^test$/.test('test') // true
/^test$/.test('test test') // false

选择符|

表示关系或,即 cat|dog表示匹配 catdog

其他的元字符还包括\\*+?()[]{} 等在后文会有解释。

转义符

正则表达式中那些有特殊含义的元字符,如果要匹配它们自身,就需要在它们前面要加上反斜杠。比如要匹配 +,就要写成\+

js 复制代码
/1+1/.test('1+1');
// false

/1\+1/.test('1+1');
// true

正则表达式中,需要反斜杠转义的,一共有12个字符:^.[$()|*+?{\。需要特别注意的是,如果使用 RegExp 方法生成正则对象,转义需要使用两个斜杠,因为字符串内部会先转义一次。

字符类

字符类(class)表示有一系列字符可供选择,只要匹配其中一个就可以了。所以可供选择的字符都放在方括号内,比如[xyz]表示xyz之中任选一个匹配。

js 复制代码
/[abc]/.test('hello world') // false
/[abc]/.test('apple') // true

有两个字符在字符类中有特殊含义。

脱字符^

如果方括号内的第一个字符是[^],则表示除了字符类之中的字符,其他字符都可以匹配。比如,[^xyz] 表示除了 xyz 之外都可以匹配。

js 复制代码
/[^abc]/.test('bbc news'); // true
/[^abc]/.test('bbc'); // false

如果方括号内没有其他字符,即只有[^],就表示匹配一切字符,其中包括换行符。相比之下,点号作为元字符.是不包括换行符的。

js 复制代码
var s = 'Please yes\nmake my day!';
s.match(/yes.*day/) // null
s.match(/yes[^]*day/) // [ 'yes\nmake my day']

注意,脱字符只有在字符类的第一个位置才有特殊含义,否则就是字面含义

连字符-

某些情况下,对于连续序列的字符,连字符-用来提供简写形式,表示字符的连续范围。比如,[abc]可以写成[a-c][0123456789]可以写成[0-9],同理[A-Z]表示26个大写字母。

js 复制代码
/a-z/.test('b') // false
/[a-z]/.test('b') // true

注意,当连字符不出现在方括号之中,就不具备简写的作用,只代表字面的含义,所以不匹配字符b。只有当连字符用在方括号之中,才表示连续的字符序列。

以下都是合法的字符类简写形式。

js 复制代码
[0-9.,]
[0-9a-fA-F]
[a-zA-Z0-9-]
[1-31]

上面代码中最后一个字符类[1-31],不代表131,只代表13

预定义模式

预定义模式指的是某些常见模式的简写方式。

  • \d 匹配0-9之间的任一数字,相当于[0-9]
  • \D 匹配所有0-9以外的字符,相当于[^0-9]
  • \w 匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]
  • \W 除所有字母、数字和下划线以外的字符,相当于[^A-Zz-z0-9_]
  • \s 匹配空格(包括换行符、制表符、空格符等),相等于[ \t\r\n\v\f]
  • \S 匹配非空格的字符,相当于[^ \t\r\n\v\f]
  • \b 匹配词的边界
  • \B 匹配非词边界,即在词的内部
js 复制代码
// \s 的例子
/\s\w*/.exec('hello world') // [" world"]

// \b 的例子
/\bworld/.test('hello world') // true
/\bworld/.test('hello-world') // true
/\bworld/.test('helloworld') // false

// \B 的例子
/\Bworld/.test('hello-world') // false
/\Bworld/.test('helloworld') // true

重复类

模式的精确匹配次数,使用大括号{}表示。{n}表示恰好重复n次,{n,}表示至少重复n次,{n,m}表示重复不少于n次,不多于m次。

js 复制代码
/lo{2}k/.test('look') // true
/lo{2,5}k/.test('looook') // true

量词符

量词符用来设定某个模式出现的次数。

  • ? 问号表示某个模式出现0次或1次,等同于{0, 1}
  • * 星号表示某个模式出现0次或多次,等同于{0,}
  • +加号表示某个模式出现1次或多次,等同于{1,}
js 复制代码
// t 出现0次或1次
/t?est/.test('test') // true
/t?est/.test('est') // true
// t 出现1次或多次
/t+est/.test('test') // true
/t+est/.test('ttest') // true
/t+est/.test('est') // false
// t 出现0次或多次
/t*est/.test('test') // true
/t*est/.test('ttest') // true
/t*est/.test('tttest') // true
/t*est/.test('est') // true

贪婪模式

上面的三个量词符,默认情况下都是最大可能匹配,即匹配到下一个字符不满足匹配规则为止。这被称为贪婪模式。

js 复制代码
var s = 'aaa';
s.match(/a+/) // ["aaa"]

除了贪婪模式,还有非贪婪模式,即最小可能匹配。只要一发现匹配,就返回结果,不要往下检查。如果想将贪婪模式改为非贪婪模式,可以在量词符后面加一个问号。

js 复制代码
var s = 'aaa';
s.match(/a+?/) // ["a"]

除了非贪婪模式的加号+?,还有非贪婪模式的星号*?和非贪婪模式的问号??

  • +?:表示某个模式出现1次或多次,匹配时采用非贪婪模式
  • *?:表示某个模式出现0次或多次,匹配时采用非贪婪模式
  • ??:表示某个模式出现0次或1次,匹配时采用非贪婪模式
js 复制代码
'abb'.match(/ab*/) // ["abb"]
'abb'.match(/ab*?/) // ["a"]

'abb'.match(/ab?/) // ["ab"]
'abb'.match(/ab??/) // ["a"]

修饰符

修饰符(modifier)表示模式的附加规则,放在正则模式的最尾部。

修饰符可以单个使用,也可以多个一起使用。

js 复制代码
// 单个修饰符
var regex = /test/i;

// 多个修饰符
var regex = /test/ig;

g 修饰符

默认情况下,第一次匹配成功后,正则对象就停止向下匹配了。g 修饰符表示全局匹配(global),加上它以后,正则对象将匹配全部符合条件的结果,主要用于搜索和替换。

js 复制代码
var regex = /b/g;
var str = 'abba';
regex.test(str); // true
regex.test(str); // true
regex.test(str); // false

i 修饰符

默认情况下,正则对象区分字母的大小写,加上i修饰符以后表示忽略大小写ignoreCase

js 复制代码
/abc/.test('ABC') // false
/abc/i.test('ABC') // true

m 修饰符

m修饰符表示多行模式(multiline),会修改^&的行为。默认情况下(即不加m修饰符时),^$匹配字符串的开始处和结尾处,加上m修饰符以后,^$还会匹配行首和行尾,即^$会识别换行符(\n)。

js 复制代码
/world$/.test('hello world\n') // false
/world$/m.test('hello world\n') // true

组匹配

概述

正则表达式的括号表示分组匹配,括号中的模式可以用来匹配分组的内容。

js 复制代码
/fred+/.test('fredd') // true
/(fred)+/.test('fredfred') // true

注意,使用组匹配时,不宜同时使用g修饰符,否则match方法不会捕获分组的内容。

js 复制代码
var m = 'abcabc'.match(/(.)b(.)/g);
m // ['abc', 'abc']

正则表达式内部,还可以用\n引用括号匹配的内容,n是从1开始自然数,表示对应顺序的括号。

js 复制代码
/(.)b(.)\1b\2/.test("abcabc")
// true

括号还可以嵌套。

js 复制代码
/y((..)\2)\1/.test('yabababab') // true

非捕获组

(?:x)称为非捕获组,表示不返回该组匹配的内容,即匹配的结果中不计入这个括号。

非捕获组的作用请考虑一个场景,假定需要匹配foo或者foofoo,正则表达式就应该写成/(foo){1, 2},但是这样会占用一个组匹配。这时,就可以使用非捕获组,将正则表达式改为/(?:foo){1, 2},它的作用与前一个正则是一样的,但是不会单独输出括号内部的内容。

js 复制代码
var m = 'abc'.match(/(?:.)b(.)/);
m // ["abc", "c"]

下面是用来分解网址的正则表达式。

js 复制代码
// 正常匹配
var url = /(http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;

url.exec('http://google.com/');
// ["http://google.com/", "http", "google.com", "/"]

// 非捕获组匹配
var url = /(?:http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;

url.exec('http://google.com/');
// ["http://google.com/", "google.com", "/"]

先行断言

x(?=y)称为先行断言,x只有在y前面才匹配,y不会被计入返回结果。比如,要匹配后面跟着百分号的数字,可以写成/\d+(?=%)/

先行断言中,括号里的部分是不会返回的。

js 复制代码
var m = 'abc'.match(/b(?=c)/);
m // ["b"]

先行否定断言

x(?!y) 称为先行否定断言,x只有不在y前面才匹配,y不会被计入返回结果。比如,要匹配后面跟的不是百分号的数字,就要写成/\d+(?!%)/

js 复制代码
/\d+(?!\.)/.exec('3.14')
// ["14"]

先行否定断言中,括号里的部分是不会返回的。

js 复制代码
var m = 'abd'.match(/b(?!c)/);
m // ['b']
相关推荐
Nan_Shu_61415 分钟前
学习: Threejs (2)
前端·javascript·学习
G_G#23 分钟前
纯前端js插件实现同一浏览器控制只允许打开一个标签,处理session变更问题
前端·javascript·浏览器标签页通信·只允许一个标签页
@大迁世界38 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路1 小时前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug1 小时前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu121381 小时前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中1 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路1 小时前
GDAL 实现矢量合并
前端
hxjhnct1 小时前
React useContext的缺陷
前端·react.js·前端框架
冰暮流星1 小时前
javascript逻辑运算符
开发语言·javascript·ecmascript