概括

如果你曾用 JavaScript 进行过复杂的文本处理操作,那么你将会喜欢 ES2018 中引入的新特性。本文将详细介绍第9版标准如何提高 JavaScript 的文本处理能力。

大多数编程语言都支持正则表达式

它们是极其强大的文本处理工具。几十行的文本处理代码通常可以通过一行正则表达式来代替。虽然大多数语言中的内置函数足以对字符串执行搜索和替换操作,但更复杂的操作(例如验证文本输入)通常需要使用正则表达式。

自1999年推出 ECMAScript 标准第三版以来,正则表达式就成为 JavaScript 语言的一部分。ECMAScript 2018(简称 ES2018)是该标准的第九版,引入四个新特性进一步提高了 JavaScript 的文本处理能力:

  • 后行断言

  • 具名组匹配

  • s 修饰符:dotAll 模式

  • Unicode 属性类

以下小节详细介绍这些新特性

后行断言

断言能够根据之前或之后的内容匹配一系列字符,丢弃可能不需要的匹配。当需要处理大段字符串并且意外匹配的可能性很高时,这一特性尤为重要。幸运的是大多数正则表达式都支持后行断言和先行断言。

在 ES2018 之前,JavaScript 中只支持先行断言。先行断言指的是,x 只有在 y 前面才匹配。

先行断言有两种:肯定和否定。先行肯定断言的语法是 (?=...)。例如,正则表达式 /Item(?= 10)/Item 在空格和数字10前才匹配:

const re = /Item(?= 10)/;
console.log(re.exec('Item'));
// → null
console.log(re.exec('Item5'));
// → null
console.log(re.exec('Item 5'));
// → null
console.log(re.exec('Item 10'));
// → ["Item", index: 0, input: "Item 10", groups: undefined]

上面代码使用 exec() 方法在字符串中搜索匹配项。如果找到匹配项,则 exec() 返回一个数组,其第一个元素是匹配的字符串。数组中的 index 属性值是匹配字符串的索引, input 属性值是搜索执行的整个字符串。最后,如果在正则表达式中使用了具名组匹配,则保存在 groups 属性。在这种情况下, groups 值为 undefined 是因为没有具名组匹配。

先行否定断言的语法是 (?!...)。先行否定断言指的是,x 只有不在 y 前面才匹配。例如, /Red(?!head)/Red 不在 head 前才匹配:

const re = /Red(?!head)/;
console.log(re.exec('Redhead'));
// → null
console.log(re.exec('Redberry'));
// → ["Red", index: 0, input: "Redberry", groups: undefined]
console.log(re.exec('Redjay'));
// → ["Red", index: 0, input: "Redjay", groups: undefined]
console.log(re.exec('Red'));
// → ["Red", index: 0, input: "Red", groups: undefined]

ES2018 增加后行断言来完善先行断言。后行断言语法 (?<=...) 表示,x 只有在 y 后面才匹配。

假设以欧元为单位检索产品的价格而不匹配欧元符号。使用后行断言会变得很简单:

const re = /(?<=€)d+(.d*)?/;
console.log(re.exec('199'));
// → null
console.log(re.exec('$199'));
// → null
console.log(re.exec('€199'));
// → ["199", undefined, index: 1, input: "€199", groups: undefined]

注意:先行断言和后行断言通常被称为 “lookarounds”。

后行否定断言的语法为 (?<!...),x 只有不在 y 后面才匹配。例如, /(?<!d{3}) meters/,“ meters” 不在三个数字后才匹配:

const re = /(?<!d{3}) meters/;
console.log(re.exec('10 meters'));
// → [" meters", index: 2, input: "10 meters", groups: undefined]
console.log(re.exec('100 meters'));    
// → null

与先行断言一样,也可以连续使用多个后行断言(肯定或否定)来创建更复杂的模式。举个例子:

const re = /(?<=d{2})(?<!35) meters/;
console.log(re.exec('35 meters'));
// → null
console.log(re.exec('meters'));
// → null
console.log(re.exec('4 meters'));
// → null
console.log(re.exec('14 meters'));
// → [" meters", index: 2, input: "14 meters", groups: undefined]

字符串中  meters 在除了35以外的任意两个数字之后才匹配。后行肯定断言确保匹配的字符串前面有两个数字,后行否定断言确保数字不是35。

具名组匹配

正则表达式可以通过将字符封装在括号中对正则表达式的一部分进行分组,可以在内部反向引用匹配组。此外,还可以通过括号提取匹配值进行进一步处理。

以下代码演示如何在字符串中查找.jpg 扩展名的文件名并提取文件名:

const re = /(w+).jpg/;
const str = 'File name: cat.jpg';
const match = re.exec(str);
const fileName = match[1];
// The second element in the resulting array holds the portion of the string that parentheses matched
console.log(match);
// → ["cat.jpg", "cat", index: 11, input: "File name: cat.jpg", groups: undefined]
console.log(fileName);
// → cat

在更复杂的模式中,使用数字索引只会使已经神秘的正则表达式语法更加混乱。假设匹配日期,由于在某些地区日期和月份的位置交换,因此不清楚哪个组指的是月份,哪个组指的是日期:

const re = /(d{4})-(d{2})-(d{2})/;
const match = re.exec('2020-03-04');
console.log(match[0]);    // → 2020-03-04
console.log(match[1]);    // → 2020
console.log(match[2]);    // → 03
console.log(match[3]);    // → 04

ES2018 针对此问题的解决方法是新增更具表现力的具名组匹配,语法为 (?<name>...):

const re = /(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/;
const match = re.exec('2020-03-04');
console.log(match.groups);          // → {year: "2020", month: "03", day: "04"}
console.log(match.groups.year);     // → 2020
console.log(match.groups.month);    // → 03
console.log(match.groups.day);      // → 04

生成的对象可能包含与具名组同名的属性,所以所有具名组都在 groups 对象里。

许多新的和传统的编程语言中都存在类似的结构。例如,Python 使用 (?P<name>) 表示具名组。Perl 支持具名组,语法与 JavaScript 相同(JavaScript 模仿了 Perl 的正则表达式语法)。Java 也使用与 Perl 相同的语法。

除了能够通过 groups 对象引用具名组,还可以使用数字索引 - 类似于常规捕获组:

const re = /(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/;
const match = re.exec('2020-03-04');
console.log(match[0]);    // → 2020-03-04
console.log(match[1]);    // → 2020
console.log(match[2]);    // → 03
console.log(match[3]);    // → 04

新语法也适用于解构赋值:

const re = /(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/;
const [match, year, month, day] = re.exec('2020-03-04');
console.log(match);    // → 2020-03-04
console.log(year);     // → 2020
console.log(month);    // → 03
console.log(day);      // → 04

即使正则表达式中没有具名组, exec 方法返回的结果中也始终创建 groups 对象:

const re = /d+/;
const match = re.exec('123');
console.log('groups' in match);    // → true

如果可选的具名组没有匹配到, groups 对象仍有该具名组属性,但属性值为 undefined

const re = /d+(?<ordinal>st|nd|rd|th)?/;
let match = re.exec('2nd');
console.log('ordinal' in match.groups);    // → true
console.log(match.groups.ordinal);         // → nd
match = re.exec('2');
console.log('ordinal' in match.groups);    // → true
console.log(match.groups.ordinal);         // → undefined

反向引用某个“常规捕获组”,可以在其后使用 的写法。例如,以下代码使用常规组匹配连续重复的两个字母:

console.log(/(ww)/.test('abab'));    // → true
// if the last two letters are not the same
// as the first two, the match will fail
console.log(/(ww)/.test('abcd'));    // → false

反向引用某个“具名组匹配”,可以使用 /k<name>/ 的写法:

const re = /(?<dup>w+)s+k<dup>/;
const match = re.exec("I'm not lazy, I'm on on energy saving mode");        
console.log(match.index);    // → 18
console.log(match[0]);       // → on on

此正则表达式在句子中查找连续的重复单词。也可以使用 的写法:

const re = /(?<dup>w+)s+/;
const match = re.exec("I'm not lazy, I'm on on energy saving mode");        
console.log(match.index);    // → 18
console.log(match[0]);       // → on on

/k<name>/ 两种写法可以同时使用:

const re = /(?<digit>d)::k<digit>/;
const match = re.exec('5:5:5');        
console.log(match[0]);    // → 5:5:5

与使用数字索引常规捕获组类似, replace() 方法第二个参数可以为具名组,表示方法 $<name>。例如:

const str = 'War & Peace';
console.log(str.replace(/(War) & (Peace)/, '$2 & $1'));    
// → Peace & War
console.log(str.replace(/(?<War>War) & (?<Peace>Peace)/, '$<Peace> & $<War>'));    
// → Peace & War

如果 replace() 方法第二个参数是函数,可以用数字索引的方式引用具名组。该函数的第二个参数为第一个组匹配的值,第三个参数为第二个组匹配的值:

const str = 'War & Peace';
const result = str.replace(/(?<War>War) & (?<Peace>Peace)/, function(match, group1, group2, offset, string) {
   return group2 + ' & ' + group1;
});
console.log(result);    // → Peace & War

s 修饰符:dotAll 模式

默认情况下,点( .)元字符匹配除行终止符(换行符( )和回车符( ))之外的任何字符:

console.log(/./.test('
'));    // → false
console.log(/./.test('
'));    // → false

尽管有这个缺点,JavaScript 开发人员仍然可以通过使用两个相反的字符类来匹配所有字符,例如 [wW],表示匹配字符( w)或非字符( W):

console.log(/[wW]/.test('
'));    // → true
console.log(/[wW]/.test('
'));    // → true

ES2018 通过引入 s( dotAll) 修饰符来解决这个问题。使用了此修饰符后,它会更改( .)元字符的行为使换行符也被匹配:

console.log(/./s.test('
'));    // → true
console.log(/./s.test('
'));    // → true

s 修饰符可以使用在所有正则表达式上,且不会改变依赖于点元字符之前的表现。除了 JavaScript 之外,还有许多其他语言,如 Perl 和 PHP 也有 s 修饰符。

Unicode 属性类

ES2015 中引入 Unicode 感知。但是 u 修饰符仍然无法匹配 Unicode 字符。

考虑以下示例:

const str = '?';
console.log(/d/.test(str));     // → false
console.log(/d/u.test(str));    // → false

? 被认为是一个数字,但 d 只能匹配 ASCII [0-9],所以 test() 方法返回 false。因为改变字符组的行为会破坏现有的正则表达式的表现,所以引入一种新的转义序列。

在 ES2018 中,当设置 u 修饰符时, p{...}可以匹配 Unicode 字符。现在要匹配任何 Unicode 数字,只需使用 p{Number},如下所示:

const str = '?';
console.log(/p{Number}/u.test(str));     // → true

要匹配 Unicode 文字字符,使用 p{Alphabetic}

const str = '漢';
console.log(/p{Alphabetic}/u.test(str));     // → true
// the w shorthand cannot match 漢
console.log(/w/u.test(str));    // → false

P{...}p{...} 的反向匹配,匹配任何 p{...} 不符合的字符:

console.log(/P{Number}/u.test('?'));    // → false
console.log(/P{Number}/u.test('漢'));    // → true
console.log(/P{Alphabetic}/u.test('?'));    // → true
console.log(/P{Alphabetic}/u.test('漢'));    // → false

请注意, p{...} 中使用不支持的属性会导致 SyntaxError

console.log(/p{undefined}/u.test('漢'));    // → SyntaxError

兼容性

总结

ES2018 在之前标准上增加正则表达式特性。新特性包括后行断言,具名组匹配,s 修饰符:dotAll 模式,Unicode 属性类。后行断言,x 只有在 y 后面才匹配。与常规捕获组相比,具名组匹配使用更具表现力的语法。 s( dotAll)修饰符改变 .元字符的表现,匹配换行符。最后,Unicode 属性类提供了一种新的转义序列。

在编写复杂正则表达式时,测试正则表达式通常很有好处。一个好的测试工具提供针对字符串测试正则表达式的接口并展示引擎解析每一步。这在理解其他人编写的表达式时很有用。它还可以检测正则表达式中可能出现的语法错误。Regex101 和 RegexBuddy 是两个流行正则表达式测试工具。

原创系列推荐



4. 
5. 
6. 
7. 

回复“加群”与大佬们一起交流学习~

点这,与大家一起分享本文吧~

【JS】380- JavaScript 正则新特性的更多相关文章

  1. Atitit.js模块化 atiImport 的新特性javascript import

    Atitit.js模块化 atiImport 的新特性javascript import 1. 常见的js import规范amd ,cmd ,umd1 1.1. Require更多流行3 2. at ...

  2. Atitit js版本es5 es6新特性

    Atitit js版本es5 es6新特性 Es5( es5 其实就是adobe action script的标准化)1 es6新特性1 Es5( es5 其实就是adobe action scrip ...

  3. atitit.js 各版本 and 新特性跟浏览器支持报告

    atitit.js 各版本 and 新特性跟浏览器支持报告 一个完整的JavaScript实现是由以下3个不同部分组成的 •核心(ECMAScript)--JavaScript的核心ECMAScrip ...

  4. atitit.atiOrm.js v2 q61 版本新特性.docx

    atitit.atiOrm.js v2 q61 版本新特性.docx 1. V1新特性如下1 1.1. V2规划,直接生成sql在js端1 2. Orm设计框架图1 2.1. atiOrm.js的原理 ...

  5. 介绍Ext JS 4.2的新特性的《深入浅出Ext JS》上市

    以用户为中心的时代,应用的界面外观变得越来越重要.然而,很多程序员都缺乏美术功底,要开发出界面美观的应用实属不易.Ext JS的出现,为广大程序员解决了这一难题.它有丰富多彩的界面和强大的功能,是开发 ...

  6. 【译】 Node.js v0.12的新特性 -- Cluster模式采用Round-Robin负载均衡

    原文:https://strongloop.com/strongblog/whats-new-in-node-js-v0-12-cluster-round-robin-load-balancing 本 ...

  7. javascript ES6 新特性之 扩展运算符 三个点 ...

    对于 ES6 新特性中的 ... 可以简单的理解为下面一句话就可以了: 对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中. 作用类似于 Object.assign() ...

  8. JavaScript ECAMScript5 新特性——get/set访问器

    之前对get/set的理解一直有误,觉得get set 是对象属性方法.看了别人的博客也有很多疑问,今天系统的做了很多测试终于弄明白了.(自己通过看书和写demo测试的,如有不对欢迎大家批评指正) g ...

  9. JavaScript ES6 新特性详解

    JavaScript ES6 带来了新的语法和新的强大功能,使您的代码更现代,更易读 const ,  let and var 的区别: const , let 是 ES6 中用于声明变量的新关键字. ...

随机推荐

  1. 腾讯Techo开发者大会PPT分享

    腾讯云年度的开发者大会已经落幕,大会包括1场前沿技术主峰会,18个技术专场,150位海内外技术专家,28个互动展区,8场动手实验室,23小时小程序云开发极限编程,1场数据库诊断大赛. 内容上涵盖了最新 ...

  2. Cache地址映射

    原理:程序访问局部性         在较短时间内由程序产生的地址往往集中在存储器逻辑地址空间的很小范围内         时间:在一小段时间内,最近被访问过的程序和数据很可能再次被访问       ...

  3. nyoj 33-蛇形填数 (循环,模拟)

    33-蛇形填数 内存限制:64MB 时间限制:3000ms Special Judge: No accepted:15 submit:38 题目描述: 在n*n方陈里填入1,2,...,n*n,要求填 ...

  4. Python3.7.1学习(二)使用schedule模块定时执行任务

    python中有一个轻量级的定时任务调度的库:schedule.他可以完成每分钟,每小时,每天,周几,特定日期的定时任务.因此十分方便我们执行一些轻量级的定时任务. 1 安装  1.1在cmd中输入p ...

  5. Linux注意事项

    一.学习 Linux 的注意事项 1. Linux 严格区分大小写 Linux 是严格区分大小写的,这一点和 Windows 不一样,所以操作时要注意区分大小写的不同,包括文件名和目录名.命令.命令选 ...

  6. windwos 10 安装flask

    1 安装python2.7.13 安装文件为:python-2.7.13.amd64.msi,因为python2.7.13中已经包含了pip. 在安装过程中选中[Add python.exe to P ...

  7. 认证域名与SSL证书的区别

    一.认证域名与SSL证书的区别 SSL 证书使访问者的 Web 浏览器和网站的服务器之间的安全. 加密连接,并确保交易的安全从篡改和拦截.认证域名向网站访客的注册和控制该网站的域名已验证.认证域名并不 ...

  8. 更换JDK

    1.更换JDK 1).卸载原有jdk 检查一下系统中的jdk版本 java -version 显示 java version "1.6.0_24" OpenJDK Runtime ...

  9. Few-shot Object Detection via Feature Reweighting (ICCV2019)

    论文:https://arxiv.org/abs/1812.01866 代码:https://github.com/bingykang/Fewshot_Detection 1.研究背景 深度卷积神经网 ...

  10. Spring与Shiro整合 登陆操作

    Spring与Shiro整合 登陆操作 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 编写登陆Controller方法  讲解: 首先,如果你登陆失败的时候,它会把你的异常信息丢到 ...