ES6 4. 正则的扩展

1 RegExp构造函数

修饰符:

  • i:ignore-不区分大小写
  • g:global-全局匹配
  • m:multi line-多行匹配
  • s:使特殊字符圆点.中包含换行符\n

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

  • 参数为字符串,第二个参数表示正则表达式的修饰符(flag)

    var regex = new RegExp('xyz', 'i')
    // 等价于
    var regex = /xyz/i;
    
  • 参数是一个正则表达式,这时会返回一个原有正则表达式的拷贝,此时不允许有修饰符

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

ES6则允许重新指定修饰符。flags为获取正则表达式的修饰符。

new RegExp(/abc/ig, 'i').flags
// "i"
2 字符串的正则方法
  • match():String.prototype.match 调用 RegExp.prototype[Symbol.match]
  • replace():String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
  • search():String.prototype.search 调用 RegExp.prototype[Symbol.search]
  • split():String.prototype.split 调用 RegExp.prototype[Symbol.split]
3 u修饰符

ES6对正则表达式添加了u修饰符,含义为“Unicode模式”,用来正确处理大于\uFFFF的Unicode字符,也就是说,可以正确处理4个字节的UTF-16编码。

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

ES5不支持4字节的UTF-16编码,会将其识别为2个字符,导致第二行代码结果为true。加了u修饰符以后,ES6就会识别为其为一个字符,所以第一行代码结果为false。

3.1 点字符

点(.)字符在正则表达式中的含义是除换行符以外的任意单个字符。对于码点大于oxFFFFUnicode字符,点字符不能识别,必须加上u修饰符。

var s = "吉";

/^.$/.test(s); // false
/^.$/u.test(s); // true
3.2 Unicode字符表示法

ES6新增了使用大括号表示Unicode字符的表示法,这种表示法在正则表达式中必须加上u修饰符才能识别当中的大括号,否则会被解读为量词。

/\u{61}/.test('a'); // false 正则表达式会认为其匹配61个u
/\u{61}/u.test('a'); // true
/\u{20BB7}/u.test('吉'); // true
3.3 量词

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

/a{2}/.test('aa'); // true
/a{2}/u.test('aa'); // true
/{2}/.test('吉吉'); // false
/{2}/u.test('吉吉'); // true
3.4 预定义模式

\S是预定义模式,匹配所有不是空格的字符。只有加上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
3.5 修饰符

\u004B\u212A都是大写的K,不加u修饰符则无法识别非规范的K字符

4 y修饰符

粘连(sticky)修饰符,类似于g修饰符。**g修饰符只要在剩余的位置中存在匹配即可,而y修饰符必须保证匹配的是剩余的第一个位置开始。**实质上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

可以使用lastIndex属性来更好的说明y修饰符,该属性可以指定每次开始搜索的位置。同上操作,指定开始位置后,g只需要后面存在匹配即可;y必须在指定位置匹配。

在split方法中使用y修饰符,原字符串必须以分隔符开头。这也意味着,只要匹配成功,数组的第一个成员肯定是空字符串。

// 没有找到匹配
'x##'.split(/#/y);
// ['x##']

// 找到两个匹配
'##x'.split(/#/y);
// ['', '', 'x']

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

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

单独一个y修饰符对match方法之返回第一个匹配,必须与g修饰符联用才能返回所有匹配

const REGEX = /a/gy;
'aaxa'.replace(REGEX, "-"); // '--xa'

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;
}

但是一旦出现了非法字符,g修饰符会忽略非法字符,而y修饰符不会。

5 sticky属性

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

6 flags属性

获取正则表达式的修饰符

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

// ES6的flags属性
// 返回正则表达式的修饰符
/abc/ig.flags;
// 'gi'
7 s修饰符:dotAll模式

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

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

上面的代码中,因为.不匹配\n,所以正则表达式返回false

但是,很多时候我们希望能匹配人意单个字符,这时有一种变通的写法。

/foo[^]bar/.test('foo\nbar'); // true

提案:dotAll模式

引入s修饰符,使得.可以匹配任意单个字符。

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

可以用dotAll获取是否处于该模式。

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

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

/s修饰符和多行修饰符/m不冲突,一起使用时,"."匹配所有的字符,而^$匹配每一行的行首和行尾。

8 后行断言(提案)

JavaScript语言的正则表达式只支持先行断言(lookahead)和先行否定断言(negativelookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。目前,有一个提案(https://github.com/goyakin/es-regexp-lookbehind)被提出。引入后行断言,其中V8引擎4.9版本已经支持。

先行断言:x只有在y前面才匹配,必须写成/x(?=y)/的形式。比如,只匹配百分号之前的数字,要写成/\d+(?=%)

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

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

后行断言:同先行断言相反,形式为/(?<=y)x/

后行否定断言:同先行否定断言相反,形式为/(?<!y)x/

9 Unicode属性类(提案)

目前有个提案(github.com/mathiasbynens/es-regexp-unicode-property-escapes)中引用一种新的类的写法:\p{...}\P(...),允许正则表达式匹配符合Unicode某种属性的所有字符。

\P\p的反向匹配,即匹配不满足条件的字符。必须加u修饰符

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

Unicode属性类要指定属性名和属性值。\p{UnicodePropertyName=UnicodePropertyValue}

对于某些属性,可以只写属性名。\p{UnicodePropertyNmae}

  • Decimal_Number:匹配所有十进制字符
  • Number:罗马数字
  • Block=Arrows:匹配所有箭头
  • 。。。
10 具名组匹配
10.1 简介

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

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

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

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const year = matchObj[2]; // 12
const year = matchObj[3]; // 31

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

**提案:**允许为每一个组匹配指定一个名字,即便于阅读代码,又便于引用。(github.com/tc39/proposal-regexp-named-groups)。

在模式头部添加“问号 + 尖括号 + 组名”。同时,数字序号(matchObj[1])仍然有效

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 year = matchObj.groups.month; // 12
const year = matchObj.groups.day; // 31

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

10.2 解构赋值和替换
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

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

如果在正则表达式内部引用某个“具名组匹配”,可以使用\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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倾云鹤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值