RegExp构造函数
RegExp构造函数并非ES6中新增的,而是在ES5中就有的,首先来看ES5的用法。
在ES5中,RegExp构造函数有两种用法,可以传入一个参数,也可以传入两个参数
传入一个参数时,需要传入一个正则表达式,此时会返回该正则表达式的拷贝。
var regex = new RegExp(/xyz/i);
// 等价于
var regex = /xyz/i;
传入两个参数时,第一个参数为字符串,第二个参数为正则表达式的修饰符。
var regex = new RegExp('xyz', 'i');
// 等价于
var regex = /xyz/i;
在ES5中,传入两个参数时,如果第一个参数为正则表达式的话,那么会报错。
var regex = new RegExp(/xyz/, 'i');
// Uncaught TypeError: Cannot supply flags when constructing one RegExp from ano
ES6改变了这种行为,当第一个参数为正则表达式时,可以使用第二个参数来修改其修饰符,最后返回的正则表达式的修饰符以第二个参数为准,即使第一个参数里已经写出了修饰符,还是以第二个为准。
new RegExp(/abc/g, 'i').flags
// "i"
字符串的正则方法
在ES6之前,字符串中已经有了match(),replace(),search(),split()四个与正则有关的方法,而在ES6中,将这四个方法定义在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]
修饰符的扩展
u修饰符
u修饰符实际上就是“Unicode 模式”,u修饰符的出现,解决了在使用正则匹配时,超过\uFFFF的字符不能被正确匹配的问题,u修饰符的出现会解决下列匹配过程中出现的问题
1.点字符
在正则中,点字符表示单个除了换行和回车的任意字符,而对于超过\uFFFF的字符来说,会被认为是两个字符,所以下面没加u修饰符的结果为false
/^.$/u.test('\uD83D\uDC2A');
// true
/^.$/.test('\uD83D\uDC2A');
// false
2.Unicode字符表示法
在ES6中新增了使用{}表示Unicode字符,而如果不使用u修饰符的话,会被识别为量词
/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true
// a为'\u{61}'
如果要对一个Unicode字符使用量词,不加u修饰符甚至会报错
/\u{61}{2}/.test('aa');
// Uncaught SyntaxError: Invalid regular expression: /\u{61}{2}/: Nothing to repeat
/\u{61}{2}/u.test('aa')
// true
3.预定义模式
u修饰符使预定义模式正确识别Unicode字符
/^\S$/.test('?') // false
/^\S$/u.test('?') // true
/^\S$/匹配一个非空白字符,这里如果不能正确识别Unicode字符,就会返回false。
4.i修饰符
有些Unicode编码不同,但是字型相近,不加u修饰符不能识别非规范的字符
// 下面两个编码结果都为大写的K
'\u004B'
// "K"
'\u212A'
// "K"
/[a-z]/i.test('\u004B')
// true
/[a-z]/i.test('\u212A')
// false
/[a-z]/iu.test('\u212A')
// true
y修饰符
y修饰符被称为“粘连”(sticky)修饰符,其与g修饰符相似,都是全局匹配,但不同的是,g修饰符只要剩余位置有匹配上的就行,而y修饰符要求需要从上一次匹配位置的末尾的下一个位置进行匹配。
// exec方法返回被匹配的内容
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修饰符由于只要求剩余位置有的匹配就可以返回内容,所以返回了"aa",而y修饰符要求“粘连”,即要从_开始匹配,所以没有匹配上,返回了null。
单独使用y修饰符时使用match方法无法匹配所有内容,必须结合g修饰符才行。
'a1a2a3'.match(/a\d/y)
// ["a1"]
'a1a2a3'.match(/a\d/yg)
// ["a1", "a2", "a3"]
y修饰符遵守lastIndex属性,从其指定的位置开始匹配
var r=/a/y;
r.lastIndex=0; // 从第0个位置开始匹配
r.test('abab');
// true
r.lastIndex=1; // 从第2个位置开始匹配
r.test('abab');
// false
s修饰符
s修饰符是为了解决.匹配单个字符时的特殊情况,.可以匹配任意单个字符,但四个字节的 UTF-16 字符(可以使用u修饰符解决)和行终止符无法匹配。一下四个为行终止符
U+000A 换行符(\n
)
U+000D 回车符(\r
)
U+2028 行分隔符(line separator)
U+2029 段分隔符(paragraph separator)
/a.b/.test('a\nb');
// false
/a.b/s.test('a\nb');
// true
属性的扩展
RegExp.prototype.unicode
该属性值为布尔值,表示是否设置了u修饰符
/\u212A/.unicode
// false
/\u212A/u.unicode
// true
RegExp.prototype.sticky
该属性值为布尔值,表示是否设置了y修饰符
/a/y.sticky
// true
/a/.sticky
// false
RegExp.prototype.flags
/a/g.flags
// "g"
/a/gy.flags
// "gy"
/a/ugy.flags
// "guy"
后行断言
在之前的JavaScript中,只有先行断言,而在ES2018中引入了后行断言
先行断言
/x(?=y)/ 匹配在y前面的x
/x(?!y)/ 匹配x不在y前面的x
/\d(?=%)/.exec('1% 2') // 匹配后面有%的数字
// ["1"]
/\d(?!%)/.exec('1% 2') // 匹配后面没%的数字
// ["2"]
后行断言
/(?<=y)x/ 匹配在y后面的x
/(?<!y)x/ 匹配不在y后面的x
/(?<=\$)\d/.exec('$1 2')
// ["1"]
/(?<!\$)\d/.exec('$1 2')
// ["2"]
后行断言带来的问题
后行断言会按先右后左的匹配顺序,会导致一些不同的结果
/^(\d+)(\d+)$/.exec('1053') // ["1053", "105", "3"]
/(?<=(\d+)(\d+))$/.exec('1053') // ["", "1", "053"]
上面代码中,第一行的代码没有使用后行断言,由于在贪婪模式下(会尽可能地多匹配),所以第一个括号内匹配的数字个数为3个,第二个括号内匹配的数字个数为1个,所以是"105","3"。而第二段代码使用了后行断言,从右边匹配起,也同样在贪婪模式下,右边的括号内会匹配3个数字,左边的括号匹配1个数字,所以是"1","053"。
Unicode属性类
ES2018引入了匹配符合Unicode某种属性的写法,\p{...}和\P{...}
\p{...}是匹配符合某种属性的内容,而\P{...}是匹配不符合某种属性的内容
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true
\p{Script=Greek}
指定匹配一个希腊文字母,所以上面的'π'匹配成功
因为是用于匹配Unicode的某种属性,自然必须是对Unicode字符进行匹配,所以使用时必须加上u修饰符,否则会报错。
对于某些属性,可以只写属性名或者只写属性值
\p{UnicodePropertyName=UnicodePropertyValue}
\p{UnicodePropertyName}
\p{UnicodePropertyValue}
具名组匹配
在之前的JavaScript中,可以使用()分组进行匹配,而ES2018引入了具名组匹配后,可以通过id来分组
简介
具名组匹配通过在括号内以?<id>开头来进行分组,id即后面调用要用到的分组名,这些分组在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
如果没有对应得分组名,就会返回undefined
matchObj.groups.as // undefined
replace替换
在使用replace替换时,使用$<分组名>调用对应内容
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// '02/01/2015'
\k引用
使用\k<分组名>可引用正则表达式中具名组的值
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
参考自阮一峰的《ECMAScript6入门》
ES6学习笔记目录(持续更新中)