mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2025-05-24 10:22:23 +00:00
docs(regex): 新增具名组匹配
This commit is contained in:
parent
62a1cd45d7
commit
9deab619d1
@ -2,7 +2,7 @@
|
||||
|
||||
## Iterator(遍历器)的概念
|
||||
|
||||
JavaScript原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
|
||||
JavaScript 原有的表示“集合”的数据结构,主要是数组(`Array`)和对象(`Object`),ES6 又添加了`Map`和`Set`。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是`Map`,`Map`的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
|
||||
|
||||
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
|
||||
|
||||
@ -87,9 +87,7 @@ function idMaker() {
|
||||
|
||||
上面的例子中,遍历器生成函数`idMaker`,返回一个遍历器对象(即指针对象)。但是并没有对应的数据结构,或者说,遍历器对象自己描述了一个数据结构出来。
|
||||
|
||||
在ES6中,有些数据结构原生具备Iterator接口(比如数组),即不用任何处理,就可以被`for...of`循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了`Symbol.iterator`属性(详见下文),另外一些数据结构没有。凡是部署了`Symbol.iterator`属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
|
||||
|
||||
如果使用TypeScript的写法,遍历器接口(Iterable)、指针对象(Iterator)和next方法返回值的规格可以描述如下。
|
||||
如果使用 TypeScript 的写法,遍历器接口(Iterable)、指针对象(Iterator)和`next`方法返回值的规格可以描述如下。
|
||||
|
||||
```javascript
|
||||
interface Iterable {
|
||||
@ -106,13 +104,13 @@ interface IterationResult {
|
||||
}
|
||||
```
|
||||
|
||||
## 数据结构的默认Iterator接口
|
||||
## 默认 Iterator 接口
|
||||
|
||||
Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即`for...of`循环(详见下文)。当使用`for...of`循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
|
||||
|
||||
一种数据结构只要部署了 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
|
||||
const obj = {
|
||||
@ -131,7 +129,18 @@ const obj = {
|
||||
|
||||
上面代码中,对象`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
|
||||
let arr = ['a', 'b', 'c'];
|
||||
@ -145,11 +154,11 @@ iter.next() // { value: undefined, done: true }
|
||||
|
||||
上面代码中,变量`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 原生提供了。
|
||||
|
||||
一个对象如果要有可被`for...of`循环调用的Iterator接口,就必须在`Symbol.iterator`的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
|
||||
一个对象如果要具备可被`for...of`循环调用的 Iterator 接口,就必须在`Symbol.iterator`的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
|
||||
|
||||
```javascript
|
||||
class RangeIterator {
|
||||
@ -175,7 +184,7 @@ function range(start, stop) {
|
||||
}
|
||||
|
||||
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() {
|
||||
var iterator = {
|
||||
next: next
|
||||
};
|
||||
var iterator = { next: next };
|
||||
|
||||
var current = this;
|
||||
|
||||
@ -200,14 +207,9 @@ Obj.prototype[Symbol.iterator] = function() {
|
||||
if (current) {
|
||||
var value = current.value;
|
||||
current = current.next;
|
||||
return {
|
||||
done: false,
|
||||
value: value
|
||||
};
|
||||
return { done: false, value: value };
|
||||
} else {
|
||||
return {
|
||||
done: true
|
||||
};
|
||||
return { done: true };
|
||||
}
|
||||
}
|
||||
return iterator;
|
||||
@ -221,11 +223,8 @@ one.next = two;
|
||||
two.next = three;
|
||||
|
||||
for (var i of one){
|
||||
console.log(i);
|
||||
console.log(i); // 1, 2, 3
|
||||
}
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
```
|
||||
|
||||
上面代码首先在构造函数的原型链上部署`Symbol.iterator`方法,调用该方法会返回遍历器对象`iterator`,调用该对象的`next`方法,在返回一个值的同时,自动将内部指针移到下一个实例。
|
||||
@ -254,7 +253,7 @@ let obj = {
|
||||
};
|
||||
```
|
||||
|
||||
对于类似数组的对象(存在数值键名和length属性),部署Iterator接口,有一个简便方法,就是`Symbol.iterator`方法直接引用数组的Iterator接口。
|
||||
对于类似数组的对象(存在数值键名和`length`属性),部署 Iterator 接口,有一个简便方法,就是`Symbol.iterator`方法直接引用数组的 Iterator 接口。
|
||||
|
||||
```javascript
|
||||
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
|
||||
@ -304,7 +303,7 @@ obj[Symbol.iterator] = () => 1;
|
||||
[...obj] // TypeError: [] is not a function
|
||||
```
|
||||
|
||||
上面代码中,变量obj的Symbol.iterator方法对应的不是遍历器生成函数,因此报错。
|
||||
上面代码中,变量`obj`的`Symbol.iterator`方法对应的不是遍历器生成函数,因此报错。
|
||||
|
||||
有了遍历器接口,数据结构就可以用`for...of`循环遍历(详见下文),也可以使用`while`循环遍历。
|
||||
|
||||
@ -340,7 +339,7 @@ let [first, ...rest] = set;
|
||||
|
||||
**(2)扩展运算符**
|
||||
|
||||
扩展运算符(...)也会调用默认的iterator接口。
|
||||
扩展运算符(...)也会调用默认的 Iterator 接口。
|
||||
|
||||
```javascript
|
||||
// 例一
|
||||
@ -363,7 +362,7 @@ let arr = [...iterable];
|
||||
|
||||
**(3)yield* **
|
||||
|
||||
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
|
||||
`yield*`后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
|
||||
|
||||
```javascript
|
||||
let generator = function* () {
|
||||
|
@ -65,6 +65,7 @@
|
||||
- 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正则特性的详细介绍
|
||||
- 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): 具名组匹配的介绍
|
||||
|
||||
## 数值
|
||||
|
||||
|
207
docs/regex.md
207
docs/regex.md
@ -2,7 +2,7 @@
|
||||
|
||||
## RegExp 构造函数
|
||||
|
||||
在ES5中,RegExp构造函数的参数有两种情况。
|
||||
在 ES5 中,`RegExp`构造函数的参数有两种情况。
|
||||
|
||||
第一种情况是,参数是字符串,这时第二个参数表示正则表达式的修饰符(flag)。
|
||||
|
||||
@ -20,14 +20,14 @@ var regex = new RegExp(/xyz/i);
|
||||
var regex = /xyz/i;
|
||||
```
|
||||
|
||||
但是,ES5不允许此时使用第二个参数,添加修饰符,否则会报错。
|
||||
但是,ES5 不允许此时使用第二个参数添加修饰符,否则会报错。
|
||||
|
||||
```javascript
|
||||
var regex = new RegExp(/xyz/, 'i');
|
||||
// Uncaught TypeError: Cannot supply flags when constructing one RegExp from another
|
||||
```
|
||||
|
||||
ES6改变了这种行为。如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
|
||||
ES6 改变了这种行为。如果`RegExp`构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
|
||||
|
||||
```javascript
|
||||
new RegExp(/abc/ig, 'i').flags
|
||||
@ -40,7 +40,7 @@ new RegExp(/abc/ig, 'i').flags
|
||||
|
||||
字符串对象共有4个方法,可以使用正则表达式:`match()`、`replace()`、`search()`和`split()`。
|
||||
|
||||
ES6将这4个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。
|
||||
ES6 将这4个方法,在语言内部全部调用`RegExp`的实例方法,从而做到所有与正则相关的方法,全都定义在`RegExp`对象上。
|
||||
|
||||
- `String.prototype.match` 调用 `RegExp.prototype[Symbol.match]`
|
||||
- `String.prototype.replace` 调用 `RegExp.prototype[Symbol.replace]`
|
||||
@ -52,10 +52,8 @@ ES6将这4个方法,在语言内部全部调用RegExp的实例方法,从而
|
||||
ES6 对正则表达式添加了`u`修饰符,含义为“Unicode模式”,用来正确处理大于`\uFFFF`的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。
|
||||
|
||||
```javascript
|
||||
/^\uD83D/u.test('\uD83D\uDC2A')
|
||||
// false
|
||||
/^\uD83D/.test('\uD83D\uDC2A')
|
||||
// true
|
||||
/^\uD83D/u.test('\uD83D\uDC2A') // false
|
||||
/^\uD83D/.test('\uD83D\uDC2A') // true
|
||||
```
|
||||
|
||||
上面代码中,`\uD83D\uDC2A`是一个四个字节的 UTF-16 编码,代表一个字符。但是,ES5 不支持四个字节的 UTF-16 编码,会将其识别为两个字符,导致第二行代码结果为`true`。加了`u`修饰符以后,ES6 就会识别其为一个字符,所以第一行代码结果为`false`。
|
||||
@ -77,7 +75,7 @@ var s = '𠮷';
|
||||
|
||||
**(2)Unicode 字符表示法**
|
||||
|
||||
ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达式中必须加上`u`修饰符,才能识别。
|
||||
ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上`u`修饰符,才能识别当中的大括号,否则会被解读为量词。
|
||||
|
||||
```javascript
|
||||
/\u{61}/.test('a') // false
|
||||
@ -98,14 +96,6 @@ ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达
|
||||
/𠮷{2}/u.test('𠮷𠮷') // true
|
||||
```
|
||||
|
||||
另外,只有在使用`u`修饰符的情况下,Unicode表达式当中的大括号才会被正确解读,否则会被解读为量词。
|
||||
|
||||
```javascript
|
||||
/^\u{3}$/.test('uuu') // true
|
||||
```
|
||||
|
||||
上面代码中,由于正则表达式没有`u`修饰符,所以大括号被解读为量词。加上`u`修饰符,就会被解读为Unicode表达式。
|
||||
|
||||
**(4)预定义模式**
|
||||
|
||||
`u`修饰符也影响到预定义模式,能否正确识别码点大于`0xFFFF`的 Unicode 字符。
|
||||
@ -140,7 +130,7 @@ codePointLength(s) // 2
|
||||
/[a-z]/iu.test('\u212A') // true
|
||||
```
|
||||
|
||||
上面代码中,不加`u`修饰符,就无法识别非规范的K字符。
|
||||
上面代码中,不加`u`修饰符,就无法识别非规范的`K`字符。
|
||||
|
||||
## y 修饰符
|
||||
|
||||
@ -217,7 +207,7 @@ match.index // 3
|
||||
REGEX.lastIndex // 4
|
||||
```
|
||||
|
||||
进一步说,`y`修饰符号隐含了头部匹配的标志`^`。
|
||||
实际上,`y`修饰符号隐含了头部匹配的标志`^`。
|
||||
|
||||
```javascript
|
||||
/b/y.exec('aba')
|
||||
@ -255,7 +245,7 @@ const REGEX = /a/gy;
|
||||
'aaxa'.replace(REGEX, '-') // '--xa'
|
||||
```
|
||||
|
||||
上面代码中,最后一个`a`因为不是出现下一次匹配的头部,所以不会被替换。
|
||||
上面代码中,最后一个`a`因为不是出现在下一次匹配的头部,所以不会被替换。
|
||||
|
||||
单单一个`y`修饰符对`match`方法,只能返回第一个匹配,必须与`g`修饰符联用,才能返回所有匹配。
|
||||
|
||||
@ -321,51 +311,6 @@ ES6为正则表达式新增了`flags`属性,会返回正则表达式的修饰
|
||||
// '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 模式
|
||||
|
||||
正则表达式中,点(`.`)是一个特殊字符,代表任意的单个字符,但是行终止符(line terminator character)除外。
|
||||
@ -413,9 +358,7 @@ re.flags // 's'
|
||||
|
||||
## 后行断言
|
||||
|
||||
JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。
|
||||
|
||||
目前,有一个[提案](https://github.com/goyakin/es-regexp-lookbehind),引入后行断言。V8 引擎4.9版已经支持,Chrome 浏览器49版打开”experimental JavaScript features“开关(地址栏键入`about:flags`),就可以使用这项功能。
|
||||
JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。目前,有一个[提案](https://github.com/goyakin/es-regexp-lookbehind),引入后行断言,V8 引擎4.9版已经支持。
|
||||
|
||||
”先行断言“指的是,`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`的部分。这种“先右后左”的执行顺序,与所有其他正则操作相反,导致了一些不符合预期的行为。
|
||||
|
||||
首先,”后行断言“的组匹配,与正常情况下结果是不一样的。
|
||||
@ -453,7 +406,7 @@ JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先
|
||||
/(?<=\1d(o))r/.exec('hodor') // ["r", "o"]
|
||||
```
|
||||
|
||||
上面代码中,如果后行断言的反斜杠引用(`\1`)放在括号的后面,就不会得到匹配结果,必须放在前面才可以。
|
||||
上面代码中,如果后行断言的反斜杠引用(`\1`)放在括号的后面,就不会得到匹配结果,必须放在前面才可以。因为后行断言是先从左到右扫描,发现匹配以后再回过头,从右到左完成反斜杠引用。
|
||||
|
||||
## Unicode 属性类
|
||||
|
||||
@ -461,7 +414,7 @@ JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先
|
||||
|
||||
```javascript
|
||||
const regexGreekSymbol = /\p{Script=Greek}/u;
|
||||
regexGreekSymbol.test('π') // u
|
||||
regexGreekSymbol.test('π') // true
|
||||
```
|
||||
|
||||
上面代码中,`\p{Script=Greek}`指定匹配一个希腊文字母,所以匹配`π`成功。
|
||||
@ -515,3 +468,117 @@ const regexArrows = /^\p{Block=Arrows}+$/u;
|
||||
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
|
||||
```
|
||||
|
||||
|
@ -21,7 +21,7 @@ for (let i of s) {
|
||||
|
||||
上面代码通过`add`方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值。
|
||||
|
||||
Set 函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。
|
||||
Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
|
||||
|
||||
```javascript
|
||||
// 例一
|
||||
|
Loading…
x
Reference in New Issue
Block a user