ES6 正则表达式

2,190 阅读2分钟

1. 正则修饰符

修饰符是影响正则表达式规则的特殊符号,会对匹配的结果产生不同的效果, 正则表达式中包含三个修饰符:

i(intensity):大小写不敏感
g(global):全局查找,对于一些特定的函数,将迭代完整的字符串,获得所有的匹配结果,而不仅仅在得到第一个匹配后就停止进行
m(multiple):检测字符串中的换行符,主要是影响字符串开始标识符^和结束标识符$的使用
u(unicode): ES6添加,用来正确处理大于\uFFFF的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码
y(sticky): ES6添加,与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

i修饰符

let s1 = 'Hello';
let s2 = 'hello';
let p1 = /Hello/;
let p2 = /Hello/i;

p1.test(s1); //true
p1.test(s2); //false
p2.test(s1); //true
p2.test(s2); //true

g修饰符

let p1 = /hello/;
let p2 = /hello/g;
let str = 'hello world, hello world';

str.match(p1); // ['hello']
str.match(p2); // ['hello', 'hello']

m修饰符

let str = 'hello world,\nhello world';
let p1 = /^hello/g;
let p2 = /^hello/gm;

str.match(p1); //['hello']
str.match(p2); //['hello', 'hello']

u修饰符:处理超出\uFFFF的4字节字符,能正确识别

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

u修饰符与点字符:正则中的点字符用来匹配换行符以外的任意字符,但是对于超过\uFFFF的字符,点字符不能正确识别,此时可以带u修饰符用以修正。

let s = '𠮷';

/^.$/.test(s) // false
/^.$/u.test(s) // true

u修饰符Unicode字符表示法:ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达式中必须加上u修饰符,才能识别。

/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true
/\u{20BB7}/u.test('𠮷') // true

u修饰符量词:使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的Unicode字符。

/a{2}/.test('aa') // true
/a{2}/u.test('aa') // true
/𠮷{2}/.test('𠮷𠮷') // false
/𠮷{2}/u.test('𠮷𠮷') // true

u修饰符的情况下,Unicode表达式当中的大括号才会被正确解读,否则会被解读为量词。

/^\u{3}$/.test('uuu') // true

u修饰符预定义符:能否正确识别码点大于0xFFFF的Unicode字符。

/^\S$/.test('𠮷') // false
/^\S$/u.test('𠮷') // true

利用这一点,可以写出一个正确返回字符串长度的函数。

function codePointLength(text) {
  var result = text.match(/[\s\S]/gu);
  return result ? result.length : 0;
}

var s = '𠮷𠮷';

s.length // 4
codePointLength(s) // 2

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

上面代码有两个正则表达式,一个使用g修饰符,另一个使用y修饰符。这两个正则表达式各执行了两次,第一次执行的时候,两者行为相同,剩余字符串都是_aa_a。由于g修饰没有位置要求,所以第二次执行会返回结果,而y修饰符要求匹配必须从头部开始,所以返回null。

如果改一下正则表达式,保证每次都能头部匹配,y修饰符就会返回结果了。

var s = 'aaa_aa_a';
var r = /a+_/y;

r.exec(s) // ["aaa_"]
r.exec(s) // ["aa_"]

进一步说,y修饰符号隐含了头部匹配的标志^,y修饰符的设计本意,就是让头部匹配的标志^在全局匹配中都有效。

/b/y.exec('aba')
// null

'x##'.split(/#/y)
// [ 'x##' ]

'##x'.split(/#/y)
// [ '', '', 'x' ]

'#x#'.split(/#/y)
// [ '', 'x#' ]

'##'.split(/#/y)
// [ '', '', '' ]

下面是字符串对象的replace方法的例子。

const REGEX = /a/gy;
'aaxa'.replace(REGEX, '-') // '--xa'
'a1a2a3'.match(/a\d/y) // ["a1"]
'a1a2a3'.match(/a\d/gy) // ["a1", "a2", "a3"]

y修饰符的一个应用,是从字符串提取token(词元),y修饰符确保了匹配之间不会有漏掉的字符。

const TOKEN_Y = /\s*(\+|[0-9]+)\s*/y;
const TOKEN_G  = /\s*(\+|[0-9]+)\s*/g;

tokenize(TOKEN_Y, '3 + 4')
// [ '3', '+', '4' ]
tokenize(TOKEN_G, '3 + 4')
// [ '3', '+', '4' ]

function tokenize(TOKEN_REGEX, str) {
  let result = [];
  let match;
  while (match = TOKEN_REGEX.exec(str)) {
    result.push(match[1]);
  }
  return result;
}

上面代码中,如果字符串里面没有非法字符,y修饰符与g修饰符的提取结果是一样的。但是,一旦出现非法字符,两者的行为就不一样了。g修饰符会忽略非法字符,而y修饰符不会,这样就很容易发现错误。

tokenize(TOKEN_Y, '3x + 4')
// [ '3' ]
tokenize(TOKEN_G, '3x + 4')
// [ '3', '+', '4' ]

2. flags属性

ES6为正则表达式新增了flags属性,会返回正则表达式的修饰符。

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

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