概括

如果你曾用 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. SpringBoot基本配置详解

    SpringBoot项目有一些基本的配置,比如启动图案(banner),比如默认配置文件application.properties,以及相关的默认配置项. 示例项目代码在:https://githu ...

  2. 极&#183;Java速成教程 - (1)

    序言 众所周知,程序员需要快速学习新知识,所以就有了<21天精通C++>和<MySQL-从删库到跑路>这样的书籍,Java作为更"高级"的语言也不应该落后, ...

  3. 从最近面试聊聊我所感受的.net天花板

    #0 前言 入职新公司没多久,闲来无事在博客园闲逛,看到园友分享的面试经历,正好自己这段时间面试找工作,也挺多感想的,干脆趁这个机会总结整理一下.博主13年开始实习,14年毕业.到现在也工作五六年了. ...

  4. CentOs虚拟机配置

    1.打开“VMware”,点击“主页”,点“创建新的虚拟机”: 2.会弹出一个“新建虚拟机向导”,类型选择“典型”,点击“下一步”: 3.选择“稍后安装操作系统”,点击“下一步”: 4.我们用的是Li ...

  5. Easy 2048 Again(状压dp)

    题目链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3802 题意: 从数列A中, 删除若干个数(可以0个), 是删除 ...

  6. Java内存模型与volatile关键字

    Java内存模型与volatile关键字 一).并发程序开发 并行程序的开发要涉及多线程.多任务间的协作和数据共享问题. 常用的并发控制:内部锁.重入锁.读写锁.信号量. 二).线程的特点 线程的特点 ...

  7. proxy protocol

    Proxy protocol 是haproxy 作者开发和设计的一个inernet 协议, 用于获取客户端的IP地址. 在使用7层代理是可以向http协议添加X-Forword-For来实现,而4层协 ...

  8. 【2018寒假集训 Day1】【位运算】桐桐的运输方案

    桐桐的运输方案(transp) [问题描述] 桐桐有 N 件货物需要运送到目的地,它们的重量和价值分别记为: 重量:W1,W2,…,Wn: 价值:V1,V2,…,Vn: 已知某辆货车的最大载货量为 X ...

  9. Unicode和Ascii的区别

    计算机只能处理数字,如果要处理文本,就必须把文本转换成数字.    最早的计算机设计采用8bit作为一个字节,所以,一个字节只能表示的最大整数255.   0-255被用来表示数字和一些符号,这个编码 ...

  10. 2019牛客暑期多校训练营(第九场)Quadratic equation——二次剩余(模奇素数)

    题意:给定p=1e9+7,构造x,y使其满足(x+y) mod p = b,(x*y) mod p = c . 思路:不考虑取模的情况下, .在取模的意义下,,因为a是模p的二次剩余的充分必要条件为  ...