Skip to content

Commit

Permalink
about regex
Browse files Browse the repository at this point in the history
Former-commit-id: 6ccdb1d
Former-commit-id: f6a0ded
  • Loading branch information
Jay.M.Hu committed May 27, 2017
1 parent 89a61e2 commit f3b39c3
Showing 1 changed file with 140 additions and 0 deletions.
140 changes: 140 additions & 0 deletions 正则表达式/你真的理解正则修饰符吗.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
title: 你真的理解正则修饰符吗
date: 2017-05-27 10:25:04
---

# 0、前言

一段代码引发的思考:

```js
var r = /\d/g;
console.log(r.test('1'));
console.log(r.test('1'));
console.log(r.test('2'));
console.log(r.test('2'));
```

先看看如上的代码,不要执行,自己先猜测下结果。

# 1、正则修饰符

正则在各个语言中,实现的标准并不完全一致。我们这里就讨论在 `JavaScript` 中的实现。

`JavaScript` 中,正则有四个修饰符: `global, ignoreCase, multiline, unicode`,详细请参考:[MDN RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp)

它们的含义如下:

* global(g) 针对字符串中所有可能的匹配来测试正则表达式
* ignoreCase(i) 匹配时忽略大小写
* multiline(m) 多行输入将被视为多行(此时开始^和结尾匹配$可以在每行中进行匹配)
* unicode(u) 对字符串采用unicode进行匹配

## 修饰符:`i`

`i` 比较易懂,在匹配的时候会忽略大小写,示例如下:

```js
var text = 'abc';
var r = /abc/;
console.log(r.test('abc')); // true
console.log(r.test('Abc')); // false
r = /abc/i;
console.log(r.test('abc')); // true
console.log(r.test('Abc')); // true
```

## 修饰符:`m`

当匹配多行时,默认是全文本匹配,使用 `m` 之后,将对每一行进行单独匹配。

```js
// 定义一个多行文本
var text = `a b c
A B C`;
var r = /c$/; // 匹配以C结尾的文本
console.log(r.test(text)); // false 全文本匹配,匹配不上
r = /c$/m;
console.log(r.test(text)); // true 多行匹配,c是第一行的末尾,匹配成功
```

## 修饰符:`u`

使用修饰符 `u`,将采用 `Unicode` 模式进行匹配,必须要在正则中包含unicode才能看到效果:

```js
var r = /\u{61}/; // 匹配61次u字符
console.log(r.test('a')); // false, 明显匹配不上
r = /\u{61}/u; // Unicode模式,\u{61} = 'a'
console.log(r.test('a')); // true, Unicode下能匹配
r = /\u{61}{3}/u; // 匹配3个a,注意正则写法
console.log(r.test('aaa')); // true
```

**注意:在/u模式下,正则中 \u{xxx} 是一个整体,不可拆分。**

# 2、正则修饰符:`g`

接下来,进入本文的重点,修饰符 `g` 的匹配模式。

首先,我们先回到开头的那段代码,我想大部分人的答案可能是:`true true true true` 吧。

实际上,正确答案是:`true false true false`,是不是觉得不可思议?接着往下看。

## RegExp.prototype.test()

`RegExp.prototype.test()` 方法的逻辑是:当找到第一个匹配项时,返回 `true`。查找整个字符串都没有找到匹配项,返回 `false`

那具体又是如何查找的呢?这里我们就要看 `RegExp` 的另外一个方法了:`RegExp.prototype.exec()`

注意观察这个方法的返回值:

```js
var r = /\d/;
// 注意看,返回值是一个数组,除了匹配到的元素之外,还有一个 index 属性
console.log(r.exec('123')); // ["1", index: 0, input: "123"]
```

关键就是 `exec` 返回值中的 `index` 属性,这个属性标识从输入文本的哪一个索引处开始查找匹配项。

如果不加 `g`,每次都是从索引0处开始查找。

```js
var r = /\d/;
console.log(r.exec('123')); // ["1", index: 0, input: "123"]
console.log(r.exec('123')); // ["1", index: 0, input: "123"]
console.log(r.exec('123')); // ["1", index: 0, input: "123"]
```

那如果加 `g` 呢?

```js
var r = /\d/g;
console.log(r.exec('123')); // ["1", index: 0, input: "123"]
console.log(r.exec('123')); // ["2", index: 1, input: "123"]
console.log(r.exec('123')); // ["3", index: 2, input: "123"]
console.log(r.exec('123')); // null
```

从结果我们可以看出,这个 `index` 是在变化的,当找不到匹配项时,会返回 null。

**总结一下:修饰符 `g` 的作用,是标识是否需要全局存储这个index。**

有了这个理解,那么回到之前的问题,那就说得通了。我们使用了 `g` 修饰符,那么会存储上一次的 `index`,当执行第二次 `console.log(r.test('1'));` 时,索引为1,当然就匹配不上了,所以就返回了 `false`

那问题又来了,为什么第三次,又返回了 `true` 呢?

还是先看代码:

```js
var r = /\d/g;
console.log(r.exec('12')); // ["1", index: 0, input: "12"]
console.log(r.exec('12')); // ["2", index: 1, input: "12"]
console.log(r.exec('12')); // null
console.log(r.exec('12')); // ["1", index: 0, input: "12"]
```

**当发现已经匹配不上元素时,会将这个 `index` 重新设置为 `0`**

这也就解释了最开始的整个代码。

0 comments on commit f3b39c3

Please sign in to comment.