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
@ -1,14 +1,14 @@
|
|||||||
# Iterator和for...of循环
|
# Iterator 和 for...of 循环
|
||||||
|
|
||||||
## Iterator(遍历器)的概念
|
## Iterator(遍历器)的概念
|
||||||
|
|
||||||
JavaScript原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
|
JavaScript 原有的表示“集合”的数据结构,主要是数组(`Array`)和对象(`Object`),ES6 又添加了`Map`和`Set`。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是`Map`,`Map`的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
|
||||||
|
|
||||||
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
|
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
|
||||||
|
|
||||||
Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令`for...of`循环,Iterator接口主要供`for...of`消费。
|
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令`for...of`循环,Iterator接口主要供`for...of`消费。
|
||||||
|
|
||||||
Iterator的遍历过程是这样的。
|
Iterator 的遍历过程是这样的。
|
||||||
|
|
||||||
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
|
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ function makeIterator(array) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
由于Iterator只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构。下面是一个无限运行的遍历器对象的例子。
|
由于 Iterator 只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构。下面是一个无限运行的遍历器对象的例子。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var it = idMaker();
|
var it = idMaker();
|
||||||
@ -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,11 +184,11 @@ 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
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码是一个类部署Iterator接口的写法。`Symbol.iterator`属性对应一个函数,执行后返回当前对象的遍历器对象。
|
上面代码是一个类部署 Iterator 接口的写法。`Symbol.iterator`属性对应一个函数,执行后返回当前对象的遍历器对象。
|
||||||
|
|
||||||
下面是通过遍历器实现指针结构的例子。
|
下面是通过遍历器实现指针结构的例子。
|
||||||
|
|
||||||
@ -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,16 +223,13 @@ 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`方法,在返回一个值的同时,自动将内部指针移到下一个实例。
|
||||||
|
|
||||||
下面是另一个为对象添加Iterator接口的例子。
|
下面是另一个为对象添加 Iterator 接口的例子。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let obj = {
|
let obj = {
|
||||||
@ -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`循环遍历。
|
||||||
|
|
||||||
@ -320,13 +319,13 @@ while (!$result.done) {
|
|||||||
|
|
||||||
上面代码中,`ITERABLE`代表某种可遍历的数据结构,`$iterator`是它的遍历器对象。遍历器对象每次移动指针(`next`方法),都检查一下返回值的`done`属性,如果遍历还没结束,就移动遍历器对象的指针到下一步(`next`方法),不断循环。
|
上面代码中,`ITERABLE`代表某种可遍历的数据结构,`$iterator`是它的遍历器对象。遍历器对象每次移动指针(`next`方法),都检查一下返回值的`done`属性,如果遍历还没结束,就移动遍历器对象的指针到下一步(`next`方法),不断循环。
|
||||||
|
|
||||||
## 调用Iterator接口的场合
|
## 调用 Iterator 接口的场合
|
||||||
|
|
||||||
有一些场合会默认调用Iterator接口(即`Symbol.iterator`方法),除了下文会介绍的`for...of`循环,还有几个别的场合。
|
有一些场合会默认调用 Iterator 接口(即`Symbol.iterator`方法),除了下文会介绍的`for...of`循环,还有几个别的场合。
|
||||||
|
|
||||||
**(1)解构赋值**
|
**(1)解构赋值**
|
||||||
|
|
||||||
对数组和Set结构进行解构赋值时,会默认调用`Symbol.iterator`方法。
|
对数组和 Set 结构进行解构赋值时,会默认调用`Symbol.iterator`方法。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let set = new Set().add('a').add('b').add('c');
|
let set = new Set().add('a').add('b').add('c');
|
||||||
@ -340,7 +339,7 @@ let [first, ...rest] = set;
|
|||||||
|
|
||||||
**(2)扩展运算符**
|
**(2)扩展运算符**
|
||||||
|
|
||||||
扩展运算符(...)也会调用默认的iterator接口。
|
扩展运算符(...)也会调用默认的 Iterator 接口。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 例一
|
// 例一
|
||||||
@ -353,9 +352,9 @@ let arr = ['b', 'c'];
|
|||||||
// ['a', 'b', 'c', 'd']
|
// ['a', 'b', 'c', 'd']
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码的扩展运算符内部就调用Iterator接口。
|
上面代码的扩展运算符内部就调用 Iterator 接口。
|
||||||
|
|
||||||
实际上,这提供了一种简便机制,可以将任何部署了Iterator接口的数据结构,转为数组。也就是说,只要某个数据结构部署了Iterator接口,就可以对它使用扩展运算符,将其转为数组。
|
实际上,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组。也就是说,只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let arr = [...iterable];
|
let arr = [...iterable];
|
||||||
@ -363,7 +362,7 @@ let arr = [...iterable];
|
|||||||
|
|
||||||
**(3)yield* **
|
**(3)yield* **
|
||||||
|
|
||||||
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
|
`yield*`后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let generator = function* () {
|
let generator = function* () {
|
||||||
@ -392,9 +391,9 @@ iterator.next() // { value: undefined, done: true }
|
|||||||
- Promise.all()
|
- Promise.all()
|
||||||
- Promise.race()
|
- Promise.race()
|
||||||
|
|
||||||
## 字符串的Iterator接口
|
## 字符串的 Iterator 接口
|
||||||
|
|
||||||
字符串是一个类似数组的对象,也原生具有Iterator接口。
|
字符串是一个类似数组的对象,也原生具有 Iterator 接口。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var someString = "hi";
|
var someString = "hi";
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
- kangax, [Javascript quiz. ES6 edition](http://perfectionkills.com/javascript-quiz-es6/): ES6小测试
|
- kangax, [Javascript quiz. ES6 edition](http://perfectionkills.com/javascript-quiz-es6/): ES6小测试
|
||||||
- Jeremy Fairbank, [HTML5DevConf ES7 and Beyond!](https://speakerdeck.com/jfairbank/html5devconf-es7-and-beyond): ES7新增语法点介绍
|
- Jeremy Fairbank, [HTML5DevConf ES7 and Beyond!](https://speakerdeck.com/jfairbank/html5devconf-es7-and-beyond): ES7新增语法点介绍
|
||||||
|
|
||||||
## let和const
|
## let 和 const
|
||||||
|
|
||||||
- Kyle Simpson, [For and against let](http://davidwalsh.name/for-and-against-let): 讨论let命令的作用域
|
- Kyle Simpson, [For and against let](http://davidwalsh.name/for-and-against-let): 讨论let命令的作用域
|
||||||
- kangax, [Why typeof is no longer “safe”](http://es-discourse.com/t/why-typeof-is-no-longer-safe/15): 讨论在块级作用域内,let命令的变量声明和赋值的行为
|
- kangax, [Why typeof is no longer “safe”](http://es-discourse.com/t/why-typeof-is-no-longer-safe/15): 讨论在块级作用域内,let命令的变量声明和赋值的行为
|
||||||
@ -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): 具名组匹配的介绍
|
||||||
|
|
||||||
## 数值
|
## 数值
|
||||||
|
|
||||||
|
259
docs/regex.md
259
docs/regex.md
@ -1,8 +1,8 @@
|
|||||||
# 正则的扩展
|
# 正则的扩展
|
||||||
|
|
||||||
## 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,31 +40,29 @@ 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]`
|
||||||
- `String.prototype.search` 调用 `RegExp.prototype[Symbol.search]`
|
- `String.prototype.search` 调用 `RegExp.prototype[Symbol.search]`
|
||||||
- `String.prototype.split` 调用 `RegExp.prototype[Symbol.split]`
|
- `String.prototype.split` 调用 `RegExp.prototype[Symbol.split]`
|
||||||
|
|
||||||
## u修饰符
|
## u 修饰符
|
||||||
|
|
||||||
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`。
|
||||||
|
|
||||||
一旦加上`u`修饰符号,就会修改下面这些正则表达式的行为。
|
一旦加上`u`修饰符号,就会修改下面这些正则表达式的行为。
|
||||||
|
|
||||||
**(1)点字符**
|
**(1)点字符**
|
||||||
|
|
||||||
点(`.`)字符在正则表达式中,含义是除了换行符以外的任意单个字符。对于码点大于`0xFFFF`的Unicode字符,点字符不能识别,必须加上`u`修饰符。
|
点(`.`)字符在正则表达式中,含义是除了换行符以外的任意单个字符。对于码点大于`0xFFFF`的 Unicode 字符,点字符不能识别,必须加上`u`修饰符。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var s = '𠮷';
|
var s = '𠮷';
|
||||||
@ -75,9 +73,9 @@ var s = '𠮷';
|
|||||||
|
|
||||||
上面代码表示,如果不添加`u`修饰符,正则表达式就会认为字符串为两个字符,从而匹配失败。
|
上面代码表示,如果不添加`u`修饰符,正则表达式就会认为字符串为两个字符,从而匹配失败。
|
||||||
|
|
||||||
**(2)Unicode字符表示法**
|
**(2)Unicode 字符表示法**
|
||||||
|
|
||||||
ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达式中必须加上`u`修饰符,才能识别。
|
ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上`u`修饰符,才能识别当中的大括号,否则会被解读为量词。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
/\u{61}/.test('a') // false
|
/\u{61}/.test('a') // false
|
||||||
@ -89,7 +87,7 @@ ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达
|
|||||||
|
|
||||||
**(3)量词**
|
**(3)量词**
|
||||||
|
|
||||||
使用`u`修饰符后,所有量词都会正确识别码点大于`0xFFFF`的Unicode字符。
|
使用`u`修饰符后,所有量词都会正确识别码点大于`0xFFFF`的 Unicode 字符。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
/a{2}/.test('aa') // true
|
/a{2}/.test('aa') // true
|
||||||
@ -98,24 +96,16 @@ 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 字符。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
/^\S$/.test('𠮷') // false
|
/^\S$/.test('𠮷') // false
|
||||||
/^\S$/u.test('𠮷') // true
|
/^\S$/u.test('𠮷') // true
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码的`\S`是预定义模式,匹配所有不是空格的字符。只有加了`u`修饰符,它才能正确匹配码点大于`0xFFFF`的Unicode字符。
|
上面代码的`\S`是预定义模式,匹配所有不是空格的字符。只有加了`u`修饰符,它才能正确匹配码点大于`0xFFFF`的 Unicode 字符。
|
||||||
|
|
||||||
利用这一点,可以写出一个正确返回字符串长度的函数。
|
利用这一点,可以写出一个正确返回字符串长度的函数。
|
||||||
|
|
||||||
@ -131,20 +121,20 @@ s.length // 4
|
|||||||
codePointLength(s) // 2
|
codePointLength(s) // 2
|
||||||
```
|
```
|
||||||
|
|
||||||
**(5)i修饰符**
|
**(5)i 修饰符**
|
||||||
|
|
||||||
有些Unicode字符的编码不同,但是字型很相近,比如,`\u004B`与`\u212A`都是大写的`K`。
|
有些 Unicode 字符的编码不同,但是字型很相近,比如,`\u004B`与`\u212A`都是大写的`K`。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
/[a-z]/i.test('\u212A') // false
|
/[a-z]/i.test('\u212A') // false
|
||||||
/[a-z]/iu.test('\u212A') // true
|
/[a-z]/iu.test('\u212A') // true
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,不加`u`修饰符,就无法识别非规范的K字符。
|
上面代码中,不加`u`修饰符,就无法识别非规范的`K`字符。
|
||||||
|
|
||||||
## y 修饰符
|
## y 修饰符
|
||||||
|
|
||||||
除了`u`修饰符,ES6还为正则表达式添加了`y`修饰符,叫做“粘连”(sticky)修饰符。
|
除了`u`修饰符,ES6 还为正则表达式添加了`y`修饰符,叫做“粘连”(sticky)修饰符。
|
||||||
|
|
||||||
`y`修饰符的作用与`g`修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,`g`修饰符只要剩余位置中存在匹配就可,而`y`修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
|
`y`修饰符的作用与`g`修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,`g`修饰符只要剩余位置中存在匹配就可,而`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`修饰符联用,才能返回所有匹配。
|
||||||
|
|
||||||
@ -264,7 +254,7 @@ const REGEX = /a/gy;
|
|||||||
'a1a2a3'.match(/a\d/gy) // ["a1", "a2", "a3"]
|
'a1a2a3'.match(/a\d/gy) // ["a1", "a2", "a3"]
|
||||||
```
|
```
|
||||||
|
|
||||||
`y`修饰符的一个应用,是从字符串提取token(词元),`y`修饰符确保了匹配之间不会有漏掉的字符。
|
`y`修饰符的一个应用,是从字符串提取 token(词元),`y`修饰符确保了匹配之间不会有漏掉的字符。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const TOKEN_Y = /\s*(\+|[0-9]+)\s*/y;
|
const TOKEN_Y = /\s*(\+|[0-9]+)\s*/y;
|
||||||
@ -296,76 +286,31 @@ tokenize(TOKEN_G, '3x + 4')
|
|||||||
|
|
||||||
上面代码中,`g`修饰符会忽略非法字符,而`y`修饰符不会,这样就很容易发现错误。
|
上面代码中,`g`修饰符会忽略非法字符,而`y`修饰符不会,这样就很容易发现错误。
|
||||||
|
|
||||||
## sticky属性
|
## sticky 属性
|
||||||
|
|
||||||
与`y`修饰符相匹配,ES6的正则对象多了`sticky`属性,表示是否设置了`y`修饰符。
|
与`y`修饰符相匹配,ES6 的正则对象多了`sticky`属性,表示是否设置了`y`修饰符。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var r = /hello\d/y;
|
var r = /hello\d/y;
|
||||||
r.sticky // true
|
r.sticky // true
|
||||||
```
|
```
|
||||||
|
|
||||||
## flags属性
|
## flags 属性
|
||||||
|
|
||||||
ES6为正则表达式新增了`flags`属性,会返回正则表达式的修饰符。
|
ES6 为正则表达式新增了`flags`属性,会返回正则表达式的修饰符。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ES5的source属性
|
// ES5 的 source 属性
|
||||||
// 返回正则表达式的正文
|
// 返回正则表达式的正文
|
||||||
/abc/ig.source
|
/abc/ig.source
|
||||||
// "abc"
|
// "abc"
|
||||||
|
|
||||||
// ES6的flags属性
|
// ES6 的 flags 属性
|
||||||
// 返回正则表达式的修饰符
|
// 返回正则表达式的修饰符
|
||||||
/abc/ig.flags
|
/abc/ig.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,20 +406,20 @@ JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先
|
|||||||
/(?<=\1d(o))r/.exec('hodor') // ["r", "o"]
|
/(?<=\1d(o))r/.exec('hodor') // ["r", "o"]
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,如果后行断言的反斜杠引用(`\1`)放在括号的后面,就不会得到匹配结果,必须放在前面才可以。
|
上面代码中,如果后行断言的反斜杠引用(`\1`)放在括号的后面,就不会得到匹配结果,必须放在前面才可以。因为后行断言是先从左到右扫描,发现匹配以后再回过头,从右到左完成反斜杠引用。
|
||||||
|
|
||||||
## Unicode属性类
|
## Unicode 属性类
|
||||||
|
|
||||||
目前,有一个[提案](https://github.com/mathiasbynens/es-regexp-unicode-property-escapes),引入了一种新的类的写法`\p{...}`和`\P{...}`,允许正则表达式匹配符合Unicode某种属性的所有字符。
|
目前,有一个[提案](https://github.com/mathiasbynens/es-regexp-unicode-property-escapes),引入了一种新的类的写法`\p{...}`和`\P{...}`,允许正则表达式匹配符合 Unicode 某种属性的所有字符。
|
||||||
|
|
||||||
```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}`指定匹配一个希腊文字母,所以匹配`π`成功。
|
||||||
|
|
||||||
Unicode属性类要指定属性名和属性值。
|
Unicode 属性类要指定属性名和属性值。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
\p{UnicodePropertyName=UnicodePropertyValue}
|
\p{UnicodePropertyName=UnicodePropertyValue}
|
||||||
@ -480,9 +433,9 @@ Unicode属性类要指定属性名和属性值。
|
|||||||
|
|
||||||
`\P{…}`是`\p{…}`的反向匹配,即匹配不满足条件的字符。
|
`\P{…}`是`\p{…}`的反向匹配,即匹配不满足条件的字符。
|
||||||
|
|
||||||
注意,这两种类只对Unicode有效,所以使用的时候一定要加上`u`修饰符。如果不加`u`修饰符,正则表达式使用`\p`和`\P`会报错,ECMAScript预留了这两个类。
|
注意,这两种类只对 Unicode 有效,所以使用的时候一定要加上`u`修饰符。如果不加`u`修饰符,正则表达式使用`\p`和`\P`会报错,ECMAScript 预留了这两个类。
|
||||||
|
|
||||||
由于Unicode的各种属性非常多,所以这种新的类的表达能力非常强。
|
由于 Unicode 的各种属性非常多,所以这种新的类的表达能力非常强。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const regex = /^\p{Decimal_Number}+$/u;
|
const regex = /^\p{Decimal_Number}+$/u;
|
||||||
@ -504,10 +457,10 @@ regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true
|
|||||||
下面是其他一些例子。
|
下面是其他一些例子。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 匹配各种文字的所有字母,等同于Unicode版的\w
|
// 匹配各种文字的所有字母,等同于 Unicode 版的 \w
|
||||||
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
|
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
|
||||||
|
|
||||||
// 匹配各种文字的所有非字母的字符,等同于Unicode版的\W
|
// 匹配各种文字的所有非字母的字符,等同于 Unicode 版的 \W
|
||||||
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
|
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
|
||||||
|
|
||||||
// 匹配所有的箭头字符
|
// 匹配所有的箭头字符
|
||||||
@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ for (let i of s) {
|
|||||||
|
|
||||||
上面代码通过`add`方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值。
|
上面代码通过`add`方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值。
|
||||||
|
|
||||||
Set 函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。
|
Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 例一
|
// 例一
|
||||||
|
Loading…
x
Reference in New Issue
Block a user