1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 10:22:23 +00:00

docs(regex): 新增具名组匹配

This commit is contained in:
ruanyf 2017-07-01 21:09:04 +08:00
parent 62a1cd45d7
commit 9deab619d1
4 changed files with 209 additions and 142 deletions

View File

@ -2,7 +2,7 @@
## Iterator遍历器的概念 ## Iterator遍历器的概念
JavaScript原有的表示“集合”的数据结构主要是数组Array和对象ObjectES6又添加了Map和Set。这样就有了四种数据集合用户还可以组合使用它们定义自己的数据结构比如数组的成员是MapMap的成员是对象。这样就需要一种统一的接口机制来处理所有不同的数据结构。 JavaScript 原有的表示“集合”的数据结构,主要是数组(`Array`)和对象(`Object`ES6 又添加了`Map``Set`。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是`Map``Map`的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器Iterator就是这样一种机制。它是一种接口为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口就可以完成遍历操作即依次处理该数据结构的所有成员 遍历器Iterator就是这样一种机制。它是一种接口为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口就可以完成遍历操作即依次处理该数据结构的所有成员
@ -87,9 +87,7 @@ function idMaker() {
上面的例子中,遍历器生成函数`idMaker`,返回一个遍历器对象(即指针对象)。但是并没有对应的数据结构,或者说,遍历器对象自己描述了一个数据结构出来。 上面的例子中,遍历器生成函数`idMaker`,返回一个遍历器对象(即指针对象)。但是并没有对应的数据结构,或者说,遍历器对象自己描述了一个数据结构出来。
在ES6中有些数据结构原生具备Iterator接口比如数组即不用任何处理就可以被`for...of`循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了`Symbol.iterator`属性(详见下文),另外一些数据结构没有。凡是部署了`Symbol.iterator`属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。 如果使用 TypeScript 的写法遍历器接口Iterable、指针对象Iterator`next`方法返回值的规格可以描述如下。
如果使用TypeScript的写法遍历器接口Iterable、指针对象Iterator和next方法返回值的规格可以描述如下。
```javascript ```javascript
interface Iterable { interface Iterable {
@ -106,13 +104,13 @@ interface IterationResult {
} }
``` ```
## 数据结构的默认Iterator接口 ## 默认 Iterator 接口
Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即`for...of`循环(详见下文)。当使用`for...of`循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。 Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即`for...of`循环(详见下文)。当使用`for...of`循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
一种数据结构只要部署了 Iterator 接口我们就称这种数据结构是”可遍历的“iterable 一种数据结构只要部署了 Iterator 接口我们就称这种数据结构是”可遍历的“iterable
ES6规定默认的Iterator接口部署在数据结构的`Symbol.iterator`属性,或者说,一个数据结构只要具有`Symbol.iterator`属性就可以认为是“可遍历的”iterable`Symbol.iterator`属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名`Symbol.iterator`,它是一个表达式,返回`Symbol`对象的`iterator`属性这是一个预定义好的、类型为Symbol的特殊值所以要放在方括号内参见Symbol一章 ES6 规定,默认的 Iterator 接口部署在数据结构的`Symbol.iterator`属性,或者说,一个数据结构只要具有`Symbol.iterator`属性就可以认为是“可遍历的”iterable`Symbol.iterator`属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名`Symbol.iterator`,它是一个表达式,返回`Symbol`对象的`iterator`属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内(参见 Symbol 一章)。
```javascript ```javascript
const obj = { const obj = {
@ -131,7 +129,18 @@ const obj = {
上面代码中,对象`obj`是可遍历的iterable因为具有`Symbol.iterator`属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有`next`方法。每次调用`next`方法,都会返回一个代表当前成员的信息对象,具有`value``done`两个属性。 上面代码中,对象`obj`是可遍历的iterable因为具有`Symbol.iterator`属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有`next`方法。每次调用`next`方法,都会返回一个代表当前成员的信息对象,具有`value``done`两个属性。
在ES6中有三类数据结构原生具备Iterator接口数组、某些类似数组的对象、Set和Map结构。 ES6 的有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被`for...of`循环遍历。原因在于,这些数据结构原生部署了`Symbol.iterator`属性(详见下文),另外一些数据结构没有(比如对象)。凡是部署了`Symbol.iterator`属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
下面的例子是数组的`Symbol.iterator`属性。
```javascript ```javascript
let arr = ['a', 'b', 'c']; let arr = ['a', 'b', 'c'];
@ -145,11 +154,11 @@ iter.next() // { value: undefined, done: true }
上面代码中,变量`arr`是一个数组,原生就具有遍历器接口,部署在`arr``Symbol.iterator`属性上面。所以,调用这个属性,就得到遍历器对象。 上面代码中,变量`arr`是一个数组,原生就具有遍历器接口,部署在`arr``Symbol.iterator`属性上面。所以,调用这个属性,就得到遍历器对象。
上面提到原生就部署Iterator接口的数据结构有三类对于这三类数据结构,不用自己写遍历器生成函数,`for...of`循环会自动遍历它们。除此之外其他数据结构主要是对象的Iterator接口都需要自己在`Symbol.iterator`属性上面部署,这样才会被`for...of`循环遍历。 对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,`for...of`循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在`Symbol.iterator`属性上面部署,这样才会被`for...of`循环遍历。
对象Object之所以没有默认部署 Iterator 接口是因为对象的哪个属性先遍历哪个属性后遍历是不确定的需要开发者手动指定。本质上遍历器是一种线性处理对于任何非线性的数据结构部署遍历器接口就等于部署一种线性转换。不过严格地说对象部署遍历器接口并不是很必要因为这时对象实际上被当作Map结构使用ES5 没有 Map 结构,而 ES6 原生提供了。 对象Object之所以没有默认部署 Iterator 接口是因为对象的哪个属性先遍历哪个属性后遍历是不确定的需要开发者手动指定。本质上遍历器是一种线性处理对于任何非线性的数据结构部署遍历器接口就等于部署一种线性转换。不过严格地说对象部署遍历器接口并不是很必要因为这时对象实际上被当作Map结构使用ES5 没有 Map 结构,而 ES6 原生提供了。
一个对象如果要有可被`for...of`循环调用的Iterator接口,就必须在`Symbol.iterator`的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。 一个对象如果要具备可被`for...of`循环调用的 Iterator 接口,就必须在`Symbol.iterator`的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
```javascript ```javascript
class RangeIterator { class RangeIterator {
@ -175,7 +184,7 @@ function range(start, stop) {
} }
for (var value of range(0, 3)) { for (var value of range(0, 3)) {
console.log(value); console.log(value); // 0, 1, 2
} }
``` ```
@ -190,9 +199,7 @@ function Obj(value) {
} }
Obj.prototype[Symbol.iterator] = function() { Obj.prototype[Symbol.iterator] = function() {
var iterator = { var iterator = { next: next };
next: next
};
var current = this; var current = this;
@ -200,14 +207,9 @@ Obj.prototype[Symbol.iterator] = function() {
if (current) { if (current) {
var value = current.value; var value = current.value;
current = current.next; current = current.next;
return { return { done: false, value: value };
done: false,
value: value
};
} else { } else {
return { return { done: true };
done: true
};
} }
} }
return iterator; return iterator;
@ -221,11 +223,8 @@ one.next = two;
two.next = three; two.next = three;
for (var i of one){ for (var i of one){
console.log(i); console.log(i); // 1, 2, 3
} }
// 1
// 2
// 3
``` ```
上面代码首先在构造函数的原型链上部署`Symbol.iterator`方法,调用该方法会返回遍历器对象`iterator`,调用该对象的`next`方法,在返回一个值的同时,自动将内部指针移到下一个实例。 上面代码首先在构造函数的原型链上部署`Symbol.iterator`方法,调用该方法会返回遍历器对象`iterator`,调用该对象的`next`方法,在返回一个值的同时,自动将内部指针移到下一个实例。
@ -254,7 +253,7 @@ let obj = {
}; };
``` ```
对于类似数组的对象存在数值键名和length属性部署Iterator接口有一个简便方法就是`Symbol.iterator`方法直接引用数组的Iterator接口。 对于类似数组的对象(存在数值键名和`length`属性),部署 Iterator 接口,有一个简便方法,就是`Symbol.iterator`方法直接引用数组的 Iterator 接口。
```javascript ```javascript
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
@ -304,7 +303,7 @@ obj[Symbol.iterator] = () => 1;
[...obj] // TypeError: [] is not a function [...obj] // TypeError: [] is not a function
``` ```
上面代码中变量obj的Symbol.iterator方法对应的不是遍历器生成函数因此报错。 上面代码中,变量`obj``Symbol.iterator`方法对应的不是遍历器生成函数,因此报错。
有了遍历器接口,数据结构就可以用`for...of`循环遍历(详见下文),也可以使用`while`循环遍历。 有了遍历器接口,数据结构就可以用`for...of`循环遍历(详见下文),也可以使用`while`循环遍历。
@ -340,7 +339,7 @@ let [first, ...rest] = set;
**2扩展运算符** **2扩展运算符**
扩展运算符(...)也会调用默认的iterator接口。 扩展运算符(...)也会调用默认的 Iterator 接口。
```javascript ```javascript
// 例一 // 例一
@ -363,7 +362,7 @@ let arr = [...iterable];
**3yield* ** **3yield* **
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。 `yield*`后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
```javascript ```javascript
let generator = function* () { let generator = function* () {

View File

@ -65,6 +65,7 @@
- Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 - Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符
- Axel Rauschmayer, [New regular expression features in ECMAScript 6](http://www.2ality.com/2015/07/regexp-es6.html)ES6正则特性的详细介绍 - Axel Rauschmayer, [New regular expression features in ECMAScript 6](http://www.2ality.com/2015/07/regexp-es6.html)ES6正则特性的详细介绍
- Yang Guo, [RegExp lookbehind assertions](http://v8project.blogspot.jp/2016/02/regexp-lookbehind-assertions.html):介绍后行断言 - Yang Guo, [RegExp lookbehind assertions](http://v8project.blogspot.jp/2016/02/regexp-lookbehind-assertions.html):介绍后行断言
- Axel Rauschmayer, [ES proposal: RegExp named capture groups](http://2ality.com/2017/05/regexp-named-capture-groups.html): 具名组匹配的介绍
## 数值 ## 数值

View File

@ -2,7 +2,7 @@
## RegExp 构造函数 ## RegExp 构造函数
在ES5中RegExp构造函数的参数有两种情况。 ES5 中,`RegExp`构造函数的参数有两种情况。
第一种情况是参数是字符串这时第二个参数表示正则表达式的修饰符flag 第一种情况是参数是字符串这时第二个参数表示正则表达式的修饰符flag
@ -20,14 +20,14 @@ var regex = new RegExp(/xyz/i);
var regex = /xyz/i; var regex = /xyz/i;
``` ```
但是ES5不允许此时使用第二个参数添加修饰符,否则会报错。 但是ES5 不允许此时使用第二个参数添加修饰符,否则会报错。
```javascript ```javascript
var regex = new RegExp(/xyz/, 'i'); var regex = new RegExp(/xyz/, 'i');
// Uncaught TypeError: Cannot supply flags when constructing one RegExp from another // Uncaught TypeError: Cannot supply flags when constructing one RegExp from another
``` ```
ES6改变了这种行为。如果RegExp构造函数第一个参数是一个正则对象那么可以使用第二个参数指定修饰符。而且返回的正则表达式会忽略原有的正则表达式的修饰符只使用新指定的修饰符。 ES6 改变了这种行为。如果`RegExp`构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
```javascript ```javascript
new RegExp(/abc/ig, 'i').flags new RegExp(/abc/ig, 'i').flags
@ -40,7 +40,7 @@ new RegExp(/abc/ig, 'i').flags
字符串对象共有4个方法可以使用正则表达式`match()``replace()``search()``split()` 字符串对象共有4个方法可以使用正则表达式`match()``replace()``search()``split()`
ES6将这4个方法在语言内部全部调用RegExp的实例方法从而做到所有与正则相关的方法全都定义在RegExp对象上。 ES6 将这4个方法在语言内部全部调用`RegExp`的实例方法,从而做到所有与正则相关的方法,全都定义在`RegExp`对象上。
- `String.prototype.match` 调用 `RegExp.prototype[Symbol.match]` - `String.prototype.match` 调用 `RegExp.prototype[Symbol.match]`
- `String.prototype.replace` 调用 `RegExp.prototype[Symbol.replace]` - `String.prototype.replace` 调用 `RegExp.prototype[Symbol.replace]`
@ -52,10 +52,8 @@ ES6将这4个方法在语言内部全部调用RegExp的实例方法从而
ES6 对正则表达式添加了`u`修饰符含义为“Unicode模式”用来正确处理大于`\uFFFF`的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。 ES6 对正则表达式添加了`u`修饰符含义为“Unicode模式”用来正确处理大于`\uFFFF`的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。
```javascript ```javascript
/^\uD83D/u.test('\uD83D\uDC2A') /^\uD83D/u.test('\uD83D\uDC2A') // false
// false /^\uD83D/.test('\uD83D\uDC2A') // true
/^\uD83D/.test('\uD83D\uDC2A')
// true
``` ```
上面代码中,`\uD83D\uDC2A`是一个四个字节的 UTF-16 编码代表一个字符。但是ES5 不支持四个字节的 UTF-16 编码,会将其识别为两个字符,导致第二行代码结果为`true`。加了`u`修饰符以后ES6 就会识别其为一个字符,所以第一行代码结果为`false` 上面代码中,`\uD83D\uDC2A`是一个四个字节的 UTF-16 编码代表一个字符。但是ES5 不支持四个字节的 UTF-16 编码,会将其识别为两个字符,导致第二行代码结果为`true`。加了`u`修饰符以后ES6 就会识别其为一个字符,所以第一行代码结果为`false`
@ -77,7 +75,7 @@ var s = '𠮷';
**2Unicode 字符表示法** **2Unicode 字符表示法**
ES6新增了使用大括号表示Unicode字符这种表示法在正则表达式中必须加上`u`修饰符,才能识别。 ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上`u`修饰符,才能识别当中的大括号,否则会被解读为量词
```javascript ```javascript
/\u{61}/.test('a') // false /\u{61}/.test('a') // false
@ -98,14 +96,6 @@ ES6新增了使用大括号表示Unicode字符这种表示法在正则表达
/𠮷{2}/u.test('𠮷𠮷') // true /𠮷{2}/u.test('𠮷𠮷') // true
``` ```
另外,只有在使用`u`修饰符的情况下Unicode表达式当中的大括号才会被正确解读否则会被解读为量词。
```javascript
/^\u{3}$/.test('uuu') // true
```
上面代码中,由于正则表达式没有`u`修饰符,所以大括号被解读为量词。加上`u`修饰符就会被解读为Unicode表达式。
**4预定义模式** **4预定义模式**
`u`修饰符也影响到预定义模式,能否正确识别码点大于`0xFFFF`的 Unicode 字符。 `u`修饰符也影响到预定义模式,能否正确识别码点大于`0xFFFF`的 Unicode 字符。
@ -140,7 +130,7 @@ codePointLength(s) // 2
/[a-z]/iu.test('\u212A') // true /[a-z]/iu.test('\u212A') // true
``` ```
上面代码中,不加`u`修饰符就无法识别非规范的K字符。 上面代码中,不加`u`修饰符,就无法识别非规范的`K`字符。
## y 修饰符 ## y 修饰符
@ -217,7 +207,7 @@ match.index // 3
REGEX.lastIndex // 4 REGEX.lastIndex // 4
``` ```
进一步说`y`修饰符号隐含了头部匹配的标志`^` 实际上`y`修饰符号隐含了头部匹配的标志`^`
```javascript ```javascript
/b/y.exec('aba') /b/y.exec('aba')
@ -255,7 +245,7 @@ const REGEX = /a/gy;
'aaxa'.replace(REGEX, '-') // '--xa' 'aaxa'.replace(REGEX, '-') // '--xa'
``` ```
上面代码中,最后一个`a`因为不是出现下一次匹配的头部,所以不会被替换。 上面代码中,最后一个`a`因为不是出现下一次匹配的头部,所以不会被替换。
单单一个`y`修饰符对`match`方法,只能返回第一个匹配,必须与`g`修饰符联用,才能返回所有匹配。 单单一个`y`修饰符对`match`方法,只能返回第一个匹配,必须与`g`修饰符联用,才能返回所有匹配。
@ -321,51 +311,6 @@ ES6为正则表达式新增了`flags`属性,会返回正则表达式的修饰
// 'gi' // 'gi'
``` ```
## RegExp.escape()
字符串必须转义,才能作为正则模式。
```javascript
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
}
let str = '/path/to/resource.html?search=query';
escapeRegExp(str)
// "\/path\/to\/resource\.html\?search=query"
```
上面代码中,`str`是一个正常字符串,必须使用反斜杠对其中的特殊字符转义,才能用来作为一个正则匹配的模式。
已经有[提议](https://esdiscuss.org/topic/regexp-escape)将这个需求标准化作为RegExp对象的静态方法[RegExp.escape()](https://github.com/benjamingr/RexExp.escape)放入ES7。2015年7月31日TC39认为这个方法有安全风险又不愿这个方法变得过于复杂没有同意将其列入ES7但这不失为一个真实的需求。
```javascript
RegExp.escape('The Quick Brown Fox');
// "The Quick Brown Fox"
RegExp.escape('Buy it. use it. break it. fix it.');
// "Buy it\. use it\. break it\. fix it\."
RegExp.escape('(*.*)');
// "\(\*\.\*\)"
```
字符串转义以后可以使用RegExp构造函数生成正则模式。
```javascript
var str = 'hello. how are you?';
var regex = new RegExp(RegExp.escape(str), 'g');
assert.equal(String(regex), '/hello\. how are you\?/g');
```
目前,该方法可以用上文的`escapeRegExp`函数或者垫片模块[regexp.escape](https://github.com/ljharb/regexp.escape)实现。
```javascript
var escape = require('regexp.escape');
escape('hi. how are you?');
// "hi\\. how are you\\?"
```
## s 修饰符dotAll 模式 ## s 修饰符dotAll 模式
正则表达式中,点(`.`是一个特殊字符代表任意的单个字符但是行终止符line terminator character除外。 正则表达式中,点(`.`是一个特殊字符代表任意的单个字符但是行终止符line terminator character除外。
@ -413,9 +358,7 @@ re.flags // 's'
## 后行断言 ## 后行断言
JavaScript 语言的正则表达式只支持先行断言lookahead和先行否定断言negative lookahead不支持后行断言lookbehind和后行否定断言negative lookbehind JavaScript 语言的正则表达式只支持先行断言lookahead和先行否定断言negative lookahead不支持后行断言lookbehind和后行否定断言negative lookbehind。目前有一个[提案](https://github.com/goyakin/es-regexp-lookbehind)引入后行断言V8 引擎4.9版已经支持。
目前,有一个[提案](https://github.com/goyakin/es-regexp-lookbehind)引入后行断言。V8 引擎4.9版已经支持Chrome 浏览器49版打开”experimental JavaScript features“开关地址栏键入`about:flags`),就可以使用这项功能。
”先行断言“指的是,`x`只有在`y`前面才匹配,必须写成`/x(?=y)/`。比如,只匹配百分号之前的数字,要写成`/\d+(?=%)/`。”先行否定断言“指的是,`x`只有不在`y`前面才匹配,必须写成`/x(?!y)/`。比如,只匹配不在百分号之前的数字,要写成`/\d+(?!%)/` ”先行断言“指的是,`x`只有在`y`前面才匹配,必须写成`/x(?=y)/`。比如,只匹配百分号之前的数字,要写成`/\d+(?=%)/`。”先行否定断言“指的是,`x`只有不在`y`前面才匹配,必须写成`/x(?!y)/`。比如,只匹配不在百分号之前的数字,要写成`/\d+(?!%)/`
@ -435,6 +378,16 @@ JavaScript 语言的正则表达式只支持先行断言lookahead和先
上面的例子中,“后行断言”的括号之中的部分(`(?<=\$)`),也是不计入返回结果。 上面的例子中,“后行断言”的括号之中的部分(`(?<=\$)`),也是不计入返回结果。
下面的例子是使用后行断言进行字符串替换。
```javascript
const RE_DOLLAR_PREFIX = /(?<=\$)foo/g;
'$foo %foo foo'.replace(RE_DOLLAR_PREFIX, 'bar');
// '$bar %foo foo'
```
上面代码中,只有在美元符号后面的`foo`才会被替换。
“后行断言”的实现,需要先匹配`/(?<=y)x/``x`,然后再回到左边,匹配`y`的部分。这种“先右后左”的执行顺序,与所有其他正则操作相反,导致了一些不符合预期的行为。 “后行断言”的实现,需要先匹配`/(?<=y)x/``x`,然后再回到左边,匹配`y`的部分。这种“先右后左”的执行顺序,与所有其他正则操作相反,导致了一些不符合预期的行为。
首先,”后行断言“的组匹配,与正常情况下结果是不一样的。 首先,”后行断言“的组匹配,与正常情况下结果是不一样的。
@ -453,7 +406,7 @@ JavaScript 语言的正则表达式只支持先行断言lookahead和先
/(?<=\1d(o))r/.exec('hodor') // ["r", "o"] /(?<=\1d(o))r/.exec('hodor') // ["r", "o"]
``` ```
上面代码中,如果后行断言的反斜杠引用(`\1`)放在括号的后面,就不会得到匹配结果,必须放在前面才可以。 上面代码中,如果后行断言的反斜杠引用(`\1`)放在括号的后面,就不会得到匹配结果,必须放在前面才可以。因为后行断言是先从左到右扫描,发现匹配以后再回过头,从右到左完成反斜杠引用。
## Unicode 属性类 ## Unicode 属性类
@ -461,7 +414,7 @@ JavaScript 语言的正则表达式只支持先行断言lookahead和先
```javascript ```javascript
const regexGreekSymbol = /\p{Script=Greek}/u; const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // u regexGreekSymbol.test('π') // true
``` ```
上面代码中,`\p{Script=Greek}`指定匹配一个希腊文字母,所以匹配`π`成功。 上面代码中,`\p{Script=Greek}`指定匹配一个希腊文字母,所以匹配`π`成功。
@ -515,3 +468,117 @@ const regexArrows = /^\p{Block=Arrows}+$/u;
regexArrows.test('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true regexArrows.test('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true
``` ```
## 具名组匹配
### 简介
正则表达式使用圆括号进行组匹配。
```javascript
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
```
上面代码中,正则表达式里面有三组圆括号。使用`exec`方法,就可以将这三组匹配结果提取出来。
```javascript
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31
```
组匹配的一个问题是,每一组的匹配含义不容易看出来,而且只能用数字序号引用,要是组的顺序变了,引用的时候就必须修改序号。
现在有一个“具名组匹配”Named Capture Groups的[提案](https://github.com/tc39/proposal-regexp-named-groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。
```javascript
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
```
上面代码中,“具名组匹配”在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(`?<year>`),然后就可以在`exec`方法返回结果的`groups`属性上引用该组名。同时,数字序号(` matchObj[1]`)依然有效。
具名组匹配等于为每一组匹配加上了 ID便于描述匹配的目的。如果组的顺序变了也不用改变匹配后的处理代码。
如果具名组没有匹配,那么对应的`groups`对象属性会是`undefined`
```javascript
const RE_OPT_A = /^(?<as>a+)?$/;
const matchObj = RE_OPT_A.exec('');
matchObj.groups.as // undefined
'as' in matchObj.groups // true
```
上面代码中,具名组`as`没有找到匹配,那么`matchObj.groups.as`属性值就是`undefined`,并且`as`这个键名在`groups`是始终存在的。
### 解构赋值和替换
有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。
```javascript
let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
one // foo
two // bar
```
字符串替换时,使用`$<组名>`引用具名组。
```javascript
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// '02/01/2015'
```
上面代码中,`replace`方法的第二个参数是一个字符串,而不是正则表达式。
`replace`方法的第二个参数也可以是函数,该函数的参数序列如下。
```javascript
'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} = args[args.length - 1];
return `${day}/${month}/${year}`;
});
```
具名组匹配在原来的基础上,新增了最后一个函数参数:具名组构成的一个对象。函数内部可以直接对这个对象进行解构赋值。
### 引用
如果要在正则表达式内部引用某个“具名组匹配”,可以使用`\k<组名>`的写法。
```javascript
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
```
数字引用(`\1`)依然有效。
```javascript
const RE_TWICE = /^(?<word>[a-z]+)!\1$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
```
这两种引用语法还可以同时使用。
```javascript
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/;
RE_TWICE.test('abc!abc!abc') // true
RE_TWICE.test('abc!abc!ab') // false
```

View File

@ -21,7 +21,7 @@ for (let i of s) {
上面代码通过`add`方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值。 上面代码通过`add`方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值。
Set 函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。 Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
```javascript ```javascript
// 例一 // 例一