`:`/<[a-z]+>/i`** 实现: ```javascript alert( "
... ".match(/<[a-z]+>/gi) ); // ``` 我们查找字符 `'<'` 后跟一个或多个英文字母,然后是 `'>'`。 **正则表达式“打开没有属性的HTML标记”(改进版):`/<[a-z][a-z0-9]*>/i`** 更好的表达式:根据标准,HTML 标记名称可以在除了第一个位置以外的任意一个位置有一个数字,比如 `...';
let reg = //g;
// 错误!
alert( str.match(reg) ); // ...
```
我们会发现,这个正则表达式不仅匹配了一个链接,还匹配了包含 ` ` 中。
3. 所以有了如下匹配项:
```markup
...
```
所以,懒惰模式在这里不起作用。
我们需要寻找 ``,但贪婪和懒惰模式都有一些问题。
正确的做法应该是这样的:`href="[^"]*"`。它会获取 href 属性中的所有字符,正好符合我们的需求。
一个实例:
```javascript
let str1 = '...... ...';
let str2 = '...... ...';
let reg = //g;
// Works!
alert( str1.match(reg) ); // 没有匹配项,是正确的
alert( str2.match(reg) ); // ,
```
### 总结
量词有两种工作模式:
- **贪婪模式**
默认情况下,正则表达式引擎会尝试尽可能多地重复量词。例如,`\d+` 检测所有可能的字符。当不可能检测更多(没有更多的字符或到达字符串末尾)时,然后它再匹配模式的剩余部分。如果没有匹配,则减少重复的次数(回溯),并再次尝试。
- **懒惰模式**
通过在量词后添加问号 `?` 来启用。在每次重复量词之前,引擎会尝试去匹配模式的剩余部分。
正如我们所见,懒惰模式并不是针对贪婪搜索的灵丹妙药。另一种方式是“微调”贪婪搜索,我们很快就会见到更多的例子。
## 捕获组
模式的一部分可以用括号括起来 `(...)`。这称为“捕获组(capturing group)”。
这有两个影响:
1. 它允许将匹配的一部分作为结果数组中的单独项。
2. 如果我们将量词放在括号后,则它将括号视为一个整体。
### 示例:gogogo
不带括号,模式 `go+` 表示 `g` 字符,其后 `o` 重复一次或多次。例如 `goooo` 或 `gooooooooo`。
括号将字符组合,所以 `(go)+` 匹配 `go`,`gogo`,`gogogo`等。
```javascript
alert( 'Gogogo now!'.match(/(go)+/i) ); // "Gogogo"
```
例如:
```javascript
mail.com
users.mail.com
smith.users.mail.com
```
正如我们所看到的,一个域名由重复的单词组成,每个单词后面有一个点,除了最后一个单词。
在正则表达式中是 `(\w+\.)+\w+`:
```javascript
let regexp = /(\w+\.)+\w+/g;
// 一个或多个( 字母 + '.' ) + 一个或多个 字母
alert( "site.com my.site.com".match(regexp) ); // site.com,my.site.com
```
搜索有效,但是该模式无法匹配带有连字符的域名,例如 my-site.com,因为连字符不属于 `\w` 类。
我们可以通过用 `[\w-]` 替换 `\w` 来匹配除最后一个的每个单词:`([\w-]+\.)+\w+`。
```js
let regexp = /([\w-]+\.)+\w+/;
// 一个或多个( 一个或多个(字母 或 '-' ) + '.') + 一个或多个 字母
```
### 示例:email
前面的示例可以扩展。我们可以基于它为电子邮件创建一个正则表达式。
email 格式为:`name@domain`。名称可以是任何单词,可以使用连字符和点。在正则表达式中为 `[-.\w]+`。
模式:
```javascript
let regexp = /[-.\w]+@([\w-]+\.)+[\w-]+/g;
alert("my@mail.com @ his@site.com.uk".match(regexp)); // my@mail.com, his@site.com.uk
```
该正则表达式并不完美的,但多数情况下都可以工作,并且有助于修复意外的错误类型。唯一真正可靠的 email 检查只能通过发送 email 来完成。
### 匹配括号中的内容
括号从左到右编号。正则引擎会记住它们各自匹配的内容,并允许在结果中获得它。
方法 `str.match(regexp)`,如果 `regexp` 没有 `g` 标志,将查找第一个匹配并将它作为一个数组返回:
1. 在索引 `0` 处:完全匹配。
2. 在索引 `1` 处:第一个括号的内容。
3. 在索引 `2` 处:第二个括号的内容。
4. …等等…
例如,我们想找到 HTML 标记 `<.*?>` 并进行处理。这将很方便的把标签内容(尖括号内的内容)放在单独的变量中。
让我们将内部内容包装在括号中,像这样:`<(.*?)>`。
现在,我们能在结果数组中获取标签的整体 `` 及其内容 `h1`:
### 嵌套组
括号可以嵌套。在这种情况下,编号也从左到右。
例如,在搜索标签 `` 时我们可能会对以下内容感兴趣:
1. 整个标签内容:`span class="my"`。
2. 标签名称:`span`。
3. 标签属性:`class="my"`。
让我们为它们添加括号:`<(([a-z]+)\s*([^>]*))>`。
这是它们的编号方式(从左到右,由左括号开始):

实际上:
```javascript
let str = '';
let regexp = /<(([a-z]+)\s*([^>]*))>/;
// < + (多个a-z之间的字母 + 零个或多个空格 + 零个或多个非 < 字符 ) + >
let result = str.match(regexp);
alert(result[0]); //
alert(result[1]); // span class="my"
alert(result[2]); // span
alert(result[3]); // class="my"
```
`result` 的零索引始终保持完全匹配。
然后按左括号将组从左到右编号。第一组返回为 `result[1]`。它包含了整个标签内容。
然后 `result[2]` 从第二个开始的括号中进入该组 `([a-z]+)` —— 标签名称,然后在 `result[3]` 标签中:`([^>]*)`。
字符串中每个组的内容:

### 可选组
即使组是可选的并且在匹配项中不存在(例如,具有数量词 `(...)?`),也存在相应的 `result` 数组项,并且等于 `undefined`。
例如,让我们考虑正则 `a(z)?(c)?`。它寻找 `"a"` ,然后是可选的 `"z"`,然后是可选的 `"c"`。
如果我们在单个字母的字符串上运行 `a`,则结果为:
```js
let match = 'a'.match(/a(z)?(c)?/);
alert( match.length ); // 3
alert( match[0] ); // a(完全匹配)
alert( match[1] ); // undefined
alert( match[2] ); // undefined
```
数组的长度为 `3`,但所有组均为空。
这是字符串的一个更复杂的匹配 `ac`:
```javascript
let match = 'ac'.match(/a(z)?(c)?/)
alert( match.length ); // 3
alert( match[0] ); // ac(完全匹配)
alert( match[1] ); // undefined,因为 (z)? 没匹配项
alert( match[2] ); // c
```
### 搜索所有具有组的匹配项:matchAll
> 📑 **`matchAll` 是一个新方法,可能需要使用 polyfill**
>
> 旧的浏览器不支持 `matchAll`。
>
> 可能需要一个 polyfill,例如 https://github.com/ljharb/String.prototype.matchAll.
当我们搜索所有匹配项(标志 `g`)时,`match` 方法不会返回组的内容。
例如,让我们查找字符串中的所有标签:
```javascript
let str = '
';
let tags = str.match(/<(.*?)>/g);
alert( tags ); //
,
```
结果是一个匹配数组,但没有每个匹配项的详细信息。但是实际上,我们通常需要在结果中获取捕获组的内容。
在使用 `match` 很长一段时间后,它作为“新的改进版本”被加入到 JavaScript 中。
就像 `match` 一样,它寻找匹配项,但有 3 个区别:
1. 它返回的不是数组,而是一个可迭代的对象。
2. 当标志 `g` 存在时,它将每个匹配组作为一个数组返回。
3. 如果没有匹配项,则不返回 `null`,而是返回一个空的可迭代对象。
例如:
```javascript
let results = '
'.matchAll(/<(.*?)>/gi);
// results - is not an array, but an iterable object
alert(results); // [object RegExp String Iterator]
alert(results[0]); // undefined (*)
results = Array.from(results); // let's turn it into array
alert(results[0]); //
,h1 (1st tag)
alert(results[1]); //
,h2 (2nd tag)
```
我们可以看到,第一个区别非常重要,如 `(*)` 行所示。我们无法获得 `results[0]` 的匹配内容,因为该对象是伪数组。我们可以使用 `Array.from` 把它变成一个真正的 `Array`。在 Iterable(可迭代对象)[Iterable object(可迭代对象)](https://zh.javascript.info/iterable)一文中有关于伪数组和可迭代对象的更多详细信息。
如果我们不需要遍历结果,则 `Array.from` 没有必要:
```javascript
let results = '
'.matchAll(/<(.*?)>/gi);
for(let result of results) {
alert(result);
// 第一个结果:
,h1
// 第二个结果:
,h2
}
```
……或使用解构:
```javascript
let [tag1, tag2] = '
'.matchAll(/<(.*?)>/gi);
```
由 `matchAll` 所返回的每个匹配,其格式与不带标志 `g` 的 `match` 所返回的格式相同:它是一个具有额外的 `index`(字符串中的匹配索引)属性和 `input`(源字符串)的数组:
```javascript
let results = '
'.matchAll(/<(.*?)>/gi);
let [tag1, tag2] = results;
alert( tag1[0] ); //
alert( tag1[1] ); // h1
alert( tag1.index ); // 0
alert( tag1.input ); //
```
> 📑 **为什么 `matchAll` 的结果是可迭代对象而不是数组?**
>
> 为什么这个方法这样设计?原因很简单 — 为了优化。
>
> 调用 `matchAll` 不会执行搜索。相反,它返回一个可迭代的对象,最初没有结果。每当我们对它进行迭代时才会执行搜索,例如在循环中。
>
> 因此,这将根据需要找到尽可能多的结果,而不是全部。
>
> 例如,文本中可能有 100 个匹配项,但是在一个 `for..of` 循环中,我们已经找到了 5 个匹配项,然后觉得足够了并做出一个 `break`。这时引擎就不会花时间查找其他 95 个匹配。
### 命名组
用数字记录组很困难。对于简单模式,它是可行的,但对于更复杂的模式,计算括号很不方便。我们有一个更好的选择:给括号起个名字。
这是通过在开始括号之后立即放置 `?
`,因此反向引用为 `\k
`:
```javascript
let str = `He said: "She's the one!".`;
let regexp = /(?
['"])(.*?)\k
/g;
alert( str.match(regexp) ); // "She's the one!"
```
## 选择(OR)|
选择是正则表达式中的一个术语,实际上是一个简单的“或”。
在正则表达式中,它用竖线 `|` 表示。
例如,我们需要找出编程语言:HTML、PHP、Java 或 JavaScript。
对应的正则表达式为:`html|php|java(script)?`。
用例如下:
```javascript
let reg = /html|php|css|java(script)?/gi;
let str = "First HTML appeared, then CSS, then JavaScript";
alert( str.match(reg) ); // 'HTML', 'CSS', 'JavaScript'
```
我们已知的一个相似符号 —— 方括号。就允许在许多字符中进行选择,例如 `gr[ae]y` 匹配 `gray` 或 `grey`。
选择符号并非在字符级别生效,而是在表达式级别。正则表达式 `A|B|C` 意思是命中 `A`、`B` 或 `C` 其一均可。
例如:
- `gr(a|e)y` 严格等同 `gr[ae]y`。
- `gra|ey` 匹配 “gra” or “ey”。
我们通常用圆括号把模式中的选择部分括起来,像这样 `before(XXX|YYY)after`。
选择是正则表达式中的一个术语,实际上是一个简单的“或”。
在正则表达式中,它用竖线 `|` 表示。
例如,我们需要找出编程语言:HTML、PHP、Java 或 JavaScript。
对应的正则表达式为:`html|php|java(script)?`。
用例如下:
```javascript
let reg = /html|php|css|java(script)?/gi;
let str = "First HTML appeared, then CSS, then JavaScript";
alert( str.match(reg) ); // 'HTML', 'CSS', 'JavaScript'
```
我们已知的一个相似符号 —— 方括号。就允许在许多字符中进行选择,例如 `gr[ae]y` 匹配 `gray` 或 `grey`。
选择符号并非在字符级别生效,而是在表达式级别。正则表达式 `A|B|C` 意思是命中 `A`、`B` 或 `C` 其一均可。
例如:
- `gr(a|e)y` 严格等同 `gr[ae]y`。
- `gra|ey` 匹配 “gra” or “ey”。
我们通常用圆括号把模式中的选择部分括起来,像这样 `before(XXX|YYY)after`。
### 时间正则表达式
在之前的章节中有个任务是构建用于查找形如 `hh:mm` 的时间字符串,例如 `12:00`。但是简单的 `\d\d:\d\d` 过于模糊。它同时匹配 `25:99`。
如何构建更优的正则表达式?
我们可以应用到更多的严格匹配结果中:
- 首个匹配数字必须是 `0` 或 `1`,同时其后还要跟随任一数字。
- 或者是数字 `2` 之后跟随 `[0-3]`。
构建正则表达式:`[01]\d|2[0-3]`。
接着可以添加冒号和分钟的部分。
分钟的部分必须在 `0` 到 `59` 区间,在正则表达式语言中含义为首个匹配数字 `[0-5]` 其后跟随任一数字 `\d`。
把它们拼接在一起形成最终的模式 `[01]\d|2[0-3]:[0-5]\d`。
快大功告成了,但仍然存在一个问题。选择符 `|` 在 `[01]\d` 和 `2[0-3]:[0-5]\d` 之间。这是错误的,因为它只匹配符号左侧或右侧任一表达式。
```javascript
let reg = /[01]\d|2[0-3]:[0-5]\d/g;
alert("12".match(reg)); // 12 (matched [01]\d)
```
这个错误相当明显,但也是初学正则表达式的常见错误。
我们需要添加一个插入语用于匹配时钟:`[01]\d` 或 `2[0-3]`。
以下为正确版本:
```javascript
let reg = /([01]\d|2[0-3]):[0-5]\d/g;
alert("00:00 10:10 23:59 25:99 1:2".match(reg)); // 00:00,10:10,23:59
```
## 前瞻断言与后瞻断言
有时候我们需要匹配后面跟着特定模式的一段模式。比如,我们要从 `1 turkey costs 30€` 这段字符中匹配价格数值。
我们需要获取 `€` 符号前面的数值(假设价格是整数)。
那就是前瞻断言要做的事情。
### 前瞻断言
语法为:`x(?=y)`,它表示 “匹配 `x`, 仅在后面是 `y` 的情况"”
那么对于一个后面跟着 `€` 的整数金额,它的正则表达式应该为:`\d+(?=€)`。
```javascript
let str = "1 turkey costs 30€";
alert( str.match(/\d+(?=€)/) ); // 30 (正确地跳过了单个的数字 1)
```
让我们来看另一种情况:这次我们想要一个数量,它是一个不被 `€` 跟着的数值。
这里就要用到前瞻否定断言了。
语法为:`x(?!y)`,意思是 “查找 `x`, 但是仅在不被 `y` 跟随的情况下匹配成功”。
```javascript
let str = "2 turkeys cost 60€";
alert( str.match(/\d+(?!€)/) ); // 2(正确地跳过了价格)
```
### 后瞻断言
前瞻断言允许添加一个“后面要跟着什么”的条件判断。
后瞻断言也是类似的,只不过它是在相反的方向上进行条件判断。也就是说,它只允许匹配前面有特定字符串的模式。
语法为:
- 后瞻肯定断言:`(?<=y)x`, 匹配 `x`, 仅在前面是 `y` 的情况。
- 后瞻否定断言:`(? 📑 **请注意:**
>
> 这些文章中有更多关于占有型量词和前瞻断言的的内容:[Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) 和 [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups)。
我们现在用前瞻断言重写第一个例子中的正则来防止回溯吧:
```javascript
let regexp = /^((?=(\w+))\2\s?)*$/;
alert( regexp.test("A good string") ); // true
let str = "An input string that takes a long time or even makes this regex to hang!";
alert( regexp.test(str) ); // false,执行得很快!
```
这里我们用 `\2` 代替 `\1`,因为这里附加了额外的外部括号。为了防止数字产生混淆,我们可以给括号命名,例如 `(?
Hello, world!
';
let regexp = /<(.*?)>/g;
let matchAll = str.matchAll(regexp);
alert(matchAll); // [object RegExp String Iterator],不是数组,而是一个可迭代对象
matchAll = Array.from(matchAll); // 现在返回的是数组
let firstMatch = matchAll[0];
alert( firstMatch[0] ); //
alert( firstMatch[1] ); // h1
alert( firstMatch.index ); // 0
alert( firstMatch.input ); //
Hello, world!
```
如果我们用 `for..of` 来循环 `matchAll` 的匹配项,那么我们就不需要 `Array.from` 了。
### str.split(regexp|substr, limit)
使用正则表达式(或子字符串)作为分隔符来分割字符串。
我们可以用 `split` 来分割字符串,如下所示:
```javascript
alert('12-34-56'.split('-')) // 数组 ['12', '34', '56']
```
但同样,我们也可以用正则表达式来做:
```javascript
alert('12, 34, 56'.split(/,\s*/)) // 数组 ['12', '34', '56']
```
### str.search(regexp)
方法 `str.search(regexp)` 返回第一个匹配项的位置,如果未找到,则返回 `-1`:
```javascript
let str = "A drop of ink may make a million think";
alert( str.search( /ink/i ) ); // 10(第一个匹配位置)
```
**重要限制:`search` 仅查找第一个匹配项。**
如果需要其他匹配项的位置,则应使用其他方法,例如用 `str.matchAll(regexp)` 查找所有位置。
### str.replace(str|regexp, str|func)
这是用于搜索和替换的通用方法,是最有用的方法之一。它是搜索和替换字符串的瑞士军刀。
我们可以不用正则表达式来搜索和替换子字符串:
```javascript
// 用冒号替换连字符
alert('12-34-56'.replace("-", ":")) // 12:34-56
```
不过有一个陷阱。
**当 `replace` 的第一个参数是字符串时,它仅替换第一个匹配项。**
您可以在上面的示例中看到:只有第一个 `"-"` 被 `":"` 替换了。
如要找到所有的连字符,我们不应该用字符串 `"-"`,而应使用带 `g` 标记的正则表达式 `/-/g`:
```javascript
// 将连字符替换为冒号
alert( '12-34-56'.replace( /-/g, ":" ) ) // 12:34:56
```
第二个参数是一个替代字符串。我们可以在其中使用特殊字符:
| 符号 | 替换字符串中的操作 |
| :-------- | :----------------------------------------------------------- |
| `$&` | 插入整个匹配项 |
| `$`` | 在匹配项之前插入字符串的一部分 |
| `$'` | 在匹配项之后插入字符串的一部分 |
| `$n` | 如果 `n` 是一个 1 到 2 位的数字,则插入第 n 个分组的内容,详见 [捕获组](chrome-extension://bfpfpfenkimhijpdcbbhmemcimbeehcl/more_split_010.html#regexp-groups) |
| `$