Skip to content

正则扩展

RegExp 构造函数

ES5 - RegExp 有两种形式构造参数

字符串 + 修饰符

js
var regex = new RegExp('xyz', 'i')
// 等价于
var regex = /xyz/i

正则表达式

js
var regex = new RegExp(/xyz/i)
// 等价于
var regex = /xyz/i

ES6 - 引入第三种情况,正则表达式 + 修饰符

js
var regex = new RegExp(/abc/gi, 'i')
regex.flags // i

flag会覆盖原有修饰符

字符串正则方法

ES5 - match()replace()search()split()

ES6 - 将 String 对象方法关联到 RegExp 对象

  • String.prototype.match 调用 RegExp.prototype[Symbol.match]
  • String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
  • String.prototype.search 调用 RegExp.prototype[Symbol.search]
  • String.prototype.split 调用 RegExp.prototype[Symbol.split]

ES2020 - String.prototype.matchAll():一次性取出所有匹配。

返回值:遍历器(Iterator),而不是数组。

js
const string = 'test1test2test3'
const regex = /t(e)(st(\d?))/g

for (const match of string.matchAll(regex)) {
  console.log(match)
}
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"]
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]

u 修饰符

u修饰符:用来正确处理大于\uFFFF的 Unicode 字符

js
/^\uD83D/u.test('\uD83D\uDC2A') // false
/^\uD83D/.test('\uD83D\uDC2A') // true

y 修饰符

y 修饰符(粘连修饰符):全局匹配中后一次匹配都从上一次匹配成功的下一个位置开始。

g 修饰符 在剩余位置中取

lastIndex属性:每次搜索开始位置

js
var s = 'aaa_aa_a'
var r1 = /a+/g
var r2 = /a+/y

r1.exec(s) // ["aaa"] r1.lastIndex = 0
r1.exec(s) // ["aa"] r1.lastIndex = 3

r2.exec(s) // ["aaa"] r2.lastIndex = 0
r2.exec(s) // null r2.lastIndex = 3
js
const REGEX = /a/gy
'aaxa'.replace(REGEX, '-') // '--xa'
js
'a1a2a3'.match(/a\d/y) // ["a1"]
'a1a2a3'.match(/a\d/gy) // ["a1", "a2", "a3"]

==@DIF== - y 不会忽略非法字符

s 修饰符:dotAll 模式

·:代表任意单个字符。存在两个例外

  1. UTF-16 字符
  2. 行终止符,如下
  • U+000A 换行符(\n
  • U+000D 回车符(\r
  • U+2028 行分隔符(line separator
  • U+2029 段分隔符(paragraph separator)
js
/foo.bar/.test('foo\nbar')
// false
/foo[^]bar/.test('foo\nbar')
// true

ES2018 - s修饰符:使得.可以匹配任意单个字符

js
// es2018
;/foo.bar/s.test('foo\nbar') // true

这被称为dotAll模式,即点(dot)代表一切字符。

dotAll属性:正则表达式是否处在dotAll模式

js
const re = /foo.bar/s
// 另一种写法
// const re = new RegExp('foo.bar', 's');

re.test('foo\nbar') // true
re.dotAll // true
re.flags // 's'

RegExp.prototype.unicode 属性

unicode属性:表示是否设置了u修饰符

js
const r1 = /hello/
const r2 = /hello/u

r1.unicode // false
r2.unicode // true

RegExp.prototype.sticky 属性

sticky属性:表示是否设置了y修饰符

js
var r = /hello\d/y
r.sticky // true

RegExp.prototype.flags 属性

flags属性:会返回正则表达式的修饰符

js
// ES5 的 source 属性
// 返回正则表达式的正文
;/abc/gi.source /
  // "abc"

  // ES6 的 flags 属性
  // 返回正则表达式的修饰符
  abc /
  ig.flags
// 'gi'

先行断言和后行断言

先行断言 :x只有在y前面才匹配,必须写成/x(?=y)/

js
;/\d+(?=%)/.exec('100% of US presidents have been male') // ["100"]

先行否定断言:x只有不在y 前面才匹配,必须写成/x(?!y)/

js
;/\d+(?!%)/.exec('that’s all 44 of them') // ["44"]

ES2018 - 引入

后行断言:x只有在y后面才匹配,必须写成/(?<=y)x/

js
;/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill') // ["100"]

后行否定断言 :x只有不在y后面才匹配,必须写成/(?<!y)x/

js
;/(?<!\$)\d+/.exec('it’s is worth about €90') // ["90"]

后行断言组匹配:先匹配/(?<=y)x/x,然后再回到左边,匹配y的部分,先右再左

js
// 匹配在(\d+)(\d+)后面的串,第2个(\d+)先贪婪匹配
;/(?<=(\d+)(\d+))$/.exec('1053') // ["", "1", "053"]
// 第1个(\d+)先贪婪匹配
;/^(\d+)(\d+)$/.exec('1053') // ["1053", "105", "3"]

上面第一个匹配项都是贪婪匹配,053 和 105 ,只是顺序不同

补充:/(?<=(\d+)(\d+))$/ - 整个表达式都为断言,所以未匹配到串

js
;/(?<=(\d+)(\d+))1$/.exec('1053') // ["1", "0", "53"]

反斜杠引用与通常的顺序相反

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

补充:\1 表示 第一个捕获值。后行断言从右往左,第一行代码,\1 在 (\d) 之前(捕获值在 捕获之前),所以无法匹配。

Unicode 属性类

具名组匹配

使用圆括号进行组匹配

js
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

ES2018 - 具名组匹配

语法:?<组名>

为每一组匹配指定一个名称,未匹配值为undefined

js
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"

// const {groups: {year, month}} = RE_DATE.exec('1999-12-31');

引用组名

语法:$<组名>

js
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u

// params#2 String
'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// '02/01/2015'

// params#2 Function
'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<组名>

正则表达式内部引用,也可以用 \1进行捕获

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

正则匹配索引

exec()返回 index 属性:获取整个匹配结果开始位置。

问题:包含组匹配,无法拿到每个组的开始位置

js
const text = 'zabbcdef'
const re = /ab+(cd(ef))/
const result = re.exec(text)

result.index // 1
result.indices // [ [1, 8], [4, 8], [6, 8] ]

具名匹配:indices 会包含 groups 属性

js
const text = 'zabbcdef'
const re = /ab+(?<Z>cd)/
const result = re.exec(text)

result.indices.groups // { Z: [ 4, 6 ] }

相关链接

[-] 正则扩展