JavaScript中的正则表达式及一些新特性

xiaoxiao2025-07-26  32

RegExp 对象

RegExp 对象表示正则表达式,它是对字符串执行模式匹配的强大工具。

直接量语法

/pattern/attributes

创建 RegExp 对象的语法:

new RegExp(pattern, attributes);

参数

参数 pattern 是一个字符串,指定了正则表达式的模式或其他正则表达式。

参数 attributes 是一个可选的字符串,包含属性 "g"、"i" 和 "m",分别用于指定全局匹配、区分大小写的匹配和多行匹配。ECMAScript 标准化之前,不支持 m 属性。如果 pattern 是正则表达式,而不是字符串,则必须省略该参数。

返回值

一个新的 RegExp 对象,具有指定的模式和标志。如果参数 pattern 是正则表达式而不是字符串,那么 RegExp() 构造函数将用与指定的 RegExp 相同的模式和标志创建一个新的 RegExp 对象。

如果不用 new 运算符,而将 RegExp() 作为函数调用,那么它的行为与用 new 运算符调用时一样,只是当 pattern 是正则表达式时,它只返回 pattern,而不再创建一个新的 RegExp 对象。

抛出

SyntaxError - 如果 pattern 不是合法的正则表达式,或 attributes 含有 "g"、"i" 和 "m" 之外的字符,抛出该异常。

TypeError - 如果 pattern 是 RegExp 对象,但没有省略 attributes 参数,抛出该异常。

修饰符

修饰符描述i执行对大小写不敏感的匹配。g执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。m执行多行匹配。

新特性:

u含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。y“粘连”(sticky)修饰符,确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。sES2018 引入s修饰符,使得.可以匹配任意单个字符。

 

方括号

方括号用于查找某个范围内的字符:

表达式描述[abc]查找方括号之间的任何字符。[^abc]查找任何不在方括号之间的字符。[0-9]查找任何从 0 至 9 的数字。[a-z]查找任何从小写 a 到小写 z 的字符。[A-Z]查找任何从大写 A 到大写 Z 的字符。[A-z]查找任何从大写 A 到小写 z 的字符。[adgk]查找给定集合内的任何字符。[^adgk]查找给定集合外的任何字符。(red|blue|green)查找任何指定的选项。

元字符

元字符(Metacharacter)是拥有特殊含义的字符:

元字符描述.查找单个字符,除了换行和行结束符。\w查找单词字符。\W查找非单词字符。\d查找数字。\D查找非数字字符。\s查找空白字符。\S查找非空白字符。\b匹配单词边界。\B匹配非单词边界。\0查找 NUL 字符。\n查找换行符。\f查找换页符。\r查找回车符。\t查找制表符。\v查找垂直制表符。\xxx查找以八进制数 xxx 规定的字符。\xdd查找以十六进制数 dd 规定的字符。\uxxxx查找以十六进制数 xxxx 规定的 Unicode 字符。

量词

量词描述n+匹配任何包含至少一个 n 的字符串。n*匹配任何包含零个或多个 n 的字符串。n?匹配任何包含零个或一个 n 的字符串。n{X}匹配包含 X 个 n 的序列的字符串。n{X,Y}匹配包含 X 至 Y 个 n 的序列的字符串。n{X,}匹配包含至少 X 个 n 的序列的字符串。n$匹配任何结尾为 n 的字符串。^n匹配任何开头为 n 的字符串。?=n匹配任何其后紧接指定字符串 n 的字符串。?!n匹配任何其后没有紧接指定字符串 n 的字符串。

RegExp 对象属性

属性描述FFIEglobalRegExp 对象是否具有标志 g。14ignoreCaseRegExp 对象是否具有标志 i。14lastIndex一个整数,标示开始下一次匹配的字符位置。14multilineRegExp 对象是否具有标志 m。14source正则表达式的源文本。14

新特性:

unicode是否设置了“u”修饰符。sticky是否设置了“y”修饰符。flags返回正则表达式的修饰符。

RegExp 对象方法

方法描述FFIEcompile编译正则表达式。14exec检索字符串中指定的值。返回找到的值,并确定其位置。14test检索字符串中指定的值。返回 true 或 false。14

支持正则表达式的 String 对象的方法

方法描述FFIEsearch检索与正则表达式相匹配的值。14match找到一个或多个正则表达式的匹配。14replace替换与正则表达式匹配的子串。14split把字符串分割为字符串数组。14

新特性:

matchAll提案阶段,返回一个遍历器而不是一个数组

新的特性

1、后行断言:

JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。ES2018 引入后行断言,V8 引擎 4.9 版(Chrome 62)已经支持。

”先行断言“指的是,x只有在y前面才匹配,必须写成/x(?=y)/。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/。”先行否定断言“指的是,x只有不在y前面才匹配,必须写成/x(?!y)/。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/。

/\d+(?=%)/.exec('100% of US presidents have been male') // ["100"] /\d+(?!%)/.exec('that’s all 44 of them') // ["44"]

上面两个字符串,如果互换正则表达式,就不会得到相同结果。另外,还可以看到,”先行断言“括号之中的部分((?=%)),是不计入返回结果的。

“后行断言”正好与“先行断言”相反,x只有在y后面才匹配,必须写成/(?<=y)x/。比如,只匹配美元符号之后的数字,要写成/(?<=\$)\d+/。”后行否定断言“则与”先行否定断言“相反,x只有不在y后面才匹配,必须写成/(?<!y)x/。比如,只匹配不在美元符号后面的数字,要写成/(?<!\$)\d+/。

/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill') // ["100"] /(?<!\$)\d+/.exec('it’s is worth about €90') // ["90"]

上面的例子中,“后行断言”的括号之中的部分((?<=\$)),也是不计入返回结果。

下面的例子是使用后行断言进行字符串替换。

const RE_DOLLAR_PREFIX = /(?<=\$)foo/g; '$foo %foo foo'.replace(RE_DOLLAR_PREFIX, 'bar'); // '$bar %foo foo'

上面代码中,只有在美元符号后面的foo才会被替换。

“后行断言”的实现,需要先匹配/(?<=y)x/的x,然后再回到左边,匹配y的部分。这种“先右后左”的执行顺序,与所有其他正则操作相反,导致了一些不符合预期的行为。

首先,后行断言的组匹配,与正常情况下结果是不一样的。

/(?<=(\d+)(\d+))$/.exec('1053') // ["", "1", "053"] /^(\d+)(\d+)$/.exec('1053') // ["1053", "105", "3"]

上面代码中,需要捕捉两个组匹配。没有“后行断言”时,第一个括号是贪婪模式,第二个括号只能捕获一个字符,所以结果是105和3。而“后行断言”时,由于执行顺序是从右到左,第二个括号是贪婪模式,第一个括号只能捕获一个字符,所以结果是1和053。

其次,“后行断言”的反斜杠引用,也与通常的顺序相反,必须放在对应的那个括号之前。

/(?<=(o)d\1)r/.exec('hodor') // null /(?<=\1d(o))r/.exec('hodor') // ["r", "o"]

上面代码中,如果后行断言的反斜杠引用(\1)放在括号的后面,就不会得到匹配结果,必须放在前面才可以。因为后行断言是先从左到右扫描,发现匹配以后再回过头,从右到左完成反斜杠引用。

2、Unicode 属性类

ES2018 引入了一种新的类的写法\p{...}和\P{...},允许正则表达式匹配符合 Unicode 某种属性的所有字符。

const regexGreekSymbol = /\p{Script=Greek}/u; regexGreekSymbol.test('π') // true

上面代码中,\p{Script=Greek}指定匹配一个希腊文字母,所以匹配π成功。

Unicode 属性类要指定属性名和属性值。

\p{UnicodePropertyName=UnicodePropertyValue}

对于某些属性,可以只写属性名,或者只写属性值。

\p{UnicodePropertyName} \p{UnicodePropertyValue}

\P{…}是\p{…}的反向匹配,即匹配不满足条件的字符。

注意,这两种类只对 Unicode 有效,所以使用的时候一定要加上u修饰符。如果不加u修饰符,正则表达式使用\p和\P会报错,ECMAScript 预留了这两个类。

由于 Unicode 的各种属性非常多,所以这种新的类的表达能力非常强。

const regex = /^\p{Decimal_Number}+$/u; regex.test('????????????????') // true

上面代码中,属性类指定匹配所有十进制字符,可以看到各种字型的十进制字符都会匹配成功。

\p{Number}甚至能匹配罗马数字。

// 匹配所有数字 const regex = /^\p{Number}+$/u; regex.test('²³¹¼½¾') // true regex.test('㉛㉜㉝') // true regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true

下面是其他一些例子。

// 匹配所有空格 \p{White_Space} // 匹配各种文字的所有字母,等同于 Unicode 版的 \w [\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}] // 匹配各种文字的所有非字母的字符,等同于 Unicode 版的 \W [^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}] // 匹配 Emoji /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu // 匹配所有的箭头字符 const regexArrows = /^\p{Block=Arrows}+$/u; regexArrows.test('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true

3、具名组匹配 

正则表达式使用圆括号进行组匹配。

const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;

上面代码中,正则表达式里面有三组圆括号。使用exec方法,就可以将这三组匹配结果提取出来。

const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/; const matchObj = RE_DATE.exec('1999-12-31'); const year = matchObj[1]; // 1999 const month = matchObj[2]; // 12 const day = matchObj[3]; // 31

组匹配的一个问题是,每一组的匹配含义不容易看出来,而且只能用数字序号(比如matchObj[1])引用,要是组的顺序变了,引用的时候就必须修改序号。

ES2018 引入了具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; const matchObj = RE_DATE.exec('1999-12-31'); const year = matchObj.groups.year; // 1999 const month = matchObj.groups.month; // 12 const day = matchObj.groups.day; // 31

上面代码中,“具名组匹配”在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(?<year>),然后就可以在exec方法返回结果的groups属性上引用该组名。同时,数字序号(matchObj[1])依然有效。

具名组匹配等于为每一组匹配加上了 ID,便于描述匹配的目的。如果组的顺序变了,也不用改变匹配后的处理代码。

如果具名组没有匹配,那么对应的groups对象属性会是undefined。

const RE_OPT_A = /^(?<as>a+)?$/; const matchObj = RE_OPT_A.exec(''); matchObj.groups.as // undefined 'as' in matchObj.groups // true

上面代码中,具名组as没有找到匹配,那么matchObj.groups.as属性值就是undefined,并且as这个键名在groups是始终存在的。

解构赋值和替换:

有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。

let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar'); one // foo two // bar

字符串替换时,使用$<组名>引用具名组。

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u; '2015-01-02'.replace(re, '$<day>/$<month>/$<year>') // '02/01/2015'

上面代码中,replace方法的第二个参数是一个字符串,而不是正则表达式。

replace方法的第二个参数也可以是函数,该函数的参数序列如下。

'2015-01-02'.replace(re, ( matched, // 整个匹配结果 2015-01-02 capture1, // 第一个组匹配 2015 capture2, // 第二个组匹配 01 capture3, // 第三个组匹配 02 position, // 匹配开始的位置 0 S, // 原字符串 2015-01-02 groups // 具名组构成的一个对象 {year, month, day} ) => { let {day, month, year} = groups; return `${day}/${month}/${year}`; });

具名组匹配在原来的基础上,新增了最后一个函数参数:具名组构成的一个对象。函数内部可以直接对这个对象进行解构赋值。

引用:

如果要在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>的写法。

const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/; RE_TWICE.test('abc!abc') // true RE_TWICE.test('abc!ab') // false

数字引用(\1)依然有效。

const RE_TWICE = /^(?<word>[a-z]+)!\1$/; RE_TWICE.test('abc!abc') // true RE_TWICE.test('abc!ab') // false

这两种引用语法还可以同时使用。

const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/; RE_TWICE.test('abc!abc!abc') // true RE_TWICE.test('abc!abc!ab') // false

 

转载请注明原文地址: https://www.6miu.com/read-5033749.html

最新回复(0)