正则的扩展

68 阅读3分钟

RegExp 构造函数

在 ES5 中,RegExp 构造函数的参数有两种情况

  • 参数是字符串,第二个参数标识正则表达式的修饰符
  • 参数是正则表达式,返回一个原正则表达式的拷贝,此时不允许在使用第二个参数添加修饰符,会报错

ES6 针对第二种情况作了改变:当第一个参数为整个表达式时,允许使用第二个参数指定修饰符,同时会忽略原有正则表达式的修饰符,只使用新指定的修饰符

u 修饰符

ES6 对正则表达式添加了 u 修饰符,含义为「Unicode模式」,用来正确处理大于\uFFFF的Unicode字符

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

y 修饰符

ES6 还添加了 y 修饰符,叫做「粘连」修饰符

y 修饰符和 g 修饰符类似,都是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同的是,g 修饰符只要剩余位置中存在匹配即可,而 y 修饰符需要确保匹配必须从剩余的第一个位置开始

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

r1.exec(s) // ['aaa']
r2.exec(s) // ['aaa']

r1.exec(s) // ['aa']
r2.exec(s) // null

第一次执行结果相同,都是从第一个位置开始匹配,而第二次执行,从剩余字符串 _aa_a 中进行匹配,g 修饰符没有位置要求,得到结果,y 修饰符从头开始,没有匹配到结果,所以返回 null

sticky 属性

与 y 修饰符相匹配,表示是否设置了 y 修饰符

var r = /hello\d/y

r.sticky // true

flags 属性

ES6 新增 flags 属性,用于返回正则表达式的修饰符

// ES5 的 source 属性
// 返回正则表达式的正文
/abc/ig.source // 'abc'

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

s 修饰符:dotAll 模式

正则表达式中,点(.)是一个特殊字符,它代表任意的单个字符,但是行终止符除外

  • U+000A 换行符(\n)
  • U+000D 回车符(\r)
  • U+2028 行分隔符
  • U+2029 段分隔符
/foo.bar/.test('foo\nbar') // false

因为点(.)不匹配 \n ,所以返回 false

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

后行断言

JavaScript 语言的正则表达式只支持先行断言和先行否定断言,不支持后行断言和后行否定断言

  • 先行断言:x 只有在 y 前面才匹配,必须写成 /x(?=y)/ 的形式,比如,只匹配百分号之前的数字
  • 先行否定断言:x 只有不在 y 前面才匹配,必须写成 /x(?!y)/ 的形式,比如,只匹配不在百分号之前的数字
  • 后行断言:x 只有在 y 后面才匹配,必须写成 /(?<=y)x/ 的形式,比如,只匹配美元符号之后的数字
  • 后行否定断言:x 只有不在 y 后面才匹配,必须写成 /(?<!y)x/ 的形式,比如,只匹配不在美元符号后面的数字

具名组匹配

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

组匹配存在的问题

  • 每一组的匹配含义不容易看出来
  • 只能用数字序号引用
  • 如果顺序变了,引用的时候必须修改序号

使用「具名组匹配」

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 re = /(?<year>\d{4}-(?<month>\d{2})-(?<day>\d{2}))/

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

还可以通过 \k<组名> 的写法在正则表达式内部引用某个具名组匹配

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

数字引用 (\1) 依然有效,两种引用语法还可以同时使用