小试正则

在这里输入一些 JavaScript:

开始使用正则表达式

这是一个介绍正则表达式的交互教程,特别是 JavaScript 中的正则表达式。但仍然会教你编写可以在其他语言运行的正则表达式,不过你需要注意它们的不同之处。

左边是一个 JavaScript 控制台。 使用 setName('Your name') 告诉我们你的名字 (Your name 替换为你的名字)然后开始这个教程吧。

一些有用的命令:运行 help() 可以查看它们。

正则表达式是什么?

正则表达式是一个表示搜索模式的字符串(也称为正则),类似星号用于通配符文件名匹配,但是功能更强大(也因此更复杂)。

我们将从一个非常基础的例子开始,这样你将掌握正则表达式的语法以及在 JavaScript 中的使用技巧。

bio 变量是一个可能包含你名字的字符串。输入 bio.match(/{{ firstEscaped }}/) 查看它是否包含了你的名字。

It does!

你可以从前面的例子里得知两件事情。第一个是定义正则表达式的语法:简单的将你的表达式包含在一对正斜线中。

/your expression/

如果你将它输入在控制台里,你会看到被返回的正则表达式。

第二个是你可以使用字符串的 .match() 方法去测试一个表达式。还有一些其他的方法可以调用:例如你可以使用正则表达式的 .exec() 方法去检测字符串。输入 /{{ firstEscaped }}/.exec(bio)

简单的测试

.exec() 方法和 .match() 方法做同样的事情,但由正则表达式自身去调用。这是非常有用的。

另一个你可以使用的方法,可能也是所有方法中最简单的一个,.test() 方法。 它有点类似 .exec(),但返回的是一个布尔值。试试吧!

有用的提示:你可以使用键盘的向上箭头来返回之前的表达式。

字符串替换

最后一个我们会使用的方法是 .replace() ,它是一个字符串替换一些字符为另一些字符的方法。输入以下的命令将你的名字从 bio 变量中隐藏:

bio.replace(/{{ firstEscaped }}/, '[redacted]')

特殊字符

目前为止,我们使用的表达式都不是很有趣,它们都没有包含任何特殊字符。下面的字符在正则表达式中需要转义:

$()*+.?[^|]

使用一个反斜线去转义, 例如 /what\?/

写一个正则表达式查看 num 变量是否包含字符串 "3.5"。

点操作符

It doesn't! num 等于 123456,所以它没有包含字符串 "3.5"。

在正则表达式中,点号有一个特殊的含义: 它匹配除了换行符外的任何单一字符 (所以 /a.c/ 会匹配 "abc","a c","a$c" 等等)。 使用 /3.5/ (没有转义点号) 匹配存储在 num 中的字符串, 点号会匹配 "4"。

试试吧

量词

有一些“量词”,你可以用来指明某些字符应该匹配多少次。第一个是问号,在表达式中,它令前面的符号(前一字符或字符组)为可选的。

表达式 /regexp?/ 会匹配 "regex" 和 "regexp", 因为问号使得 P(但只有P)为可选的。

编写一个同时匹配 "frontend" 和 "front-end" 的表达式, 然后将其当做参数传递给 answer() 函数(例如 answer(/your expression/))。

加号

接下来的量词,我们将看到的是加号。它的意思是“一个或多个先前标记的”; /Princes+/ 会匹配 "Princes", "Princess", "Princesssss" 等等。 但它不会匹配 "Prince"。

现在来编写一个有点复杂的表达式。编写一个正则表达式从 shortStory (你可以通过输入 shortStory 查看变量的内容)变量提取开闭括号之间的内容。提示:你需要前面提到的点操作符。

星号

星号和加号有点类似;但不同于加号意味着“一个或多个”,星号的意思是“零个或多个”前面的标记。/Princes*/,除了会匹配由 /Princes+/ 匹配到的例子,同时也会匹配 "Prince"。

重复上一个例子,但是用星号代替加号。从 shortStory 变量提取括号之间所有的内容,即使有可能内容为空。

有限重复

最后一个量词是可以用来进行有限重复。语法是 {min,max} ,min 是重复的最小数字,最大数字是 max 。例如,/a{3,5}/ 会匹配 "aaa", "aaaa" 和 "aaaaa" ,但仅匹配这些而已。

编写一个表达式从 bracketNumbers 变量匹配括号之间的文本,但文本内容只能是5到8之间的字符长度。

更多有限重复

除了指定重复的范围,你还可以指定一个确切的重复数字,通过使用 {n},n 是指重复的次数。例如,表达式 a{6},会正好匹配字母 a 的6次重复。

在花括号中,你可以省略掉最大值,表达式会匹配至少 最小值 ,但没有最大值的限制。例如, /a{5,}/ 会匹配5个或更多字母 a。

传递给 answer() 函数一个与 /a?b+c*/ 等价的表达式,但不能使用这些字符的任何一个:?*+

标志符

标志符被用来修改正则表达式的行为,它们在表达式的后面指定(例如 /your expression/ig)。 每个标志由一个字母代表,JavaScript 支持4种标志符,这个教程会提及其中的两种。i 标志让表达式不区分大小写,当没有标志 i 时 /a/ 会匹配 "a" 但不匹配 "A",/a/i 则会匹配 "a" 和 "A"。

运行 /CAT/i.exec('Category') 查看 i 标志的作用。

全局标志

第二个经常用到的标志符是全局标志,由字母 g 表示。/a/ 只匹配给它的字符串中的第一个字符 a ,/a/g 会匹配所有单个字母 a。

写一个正则表达式将 shortStory 变量中的每一个字母 "a" 替换为 "e"。

记住字符串有一个 .replace(expr, replace) 方法可以用来做替换。

字符类

字符类允许你指定一个字符集合或字符范围来匹配。/[aeiou]/ 匹配任何元音字母,/[a-m]/ 匹配字母表前半部分的任何小写字母, /[aeiou0-9]/ 匹配任何元音字母或者数字。

注意在字符类里,点号是不需要转义的,它会直接按照字面量进行匹配。如果你需要匹配一个连字符,你仍然需要转义它。

我们给出一个包含由5到12位字母(大写或小写)或连字符组成的用户名。编写一些代码,如果 username 变量包含一个有效的用户名则返回 true 值。

否定字符类

一个 否定 字符类会匹配那些 不在 这个字符类中的字符。在字符类的开头放入一个插入字符(^)来否定一个字符类。例如, /[^a-m]/ 会匹配 "z" 和 "$",但不会匹配 "c"。

重点是要注意 "not [a-m]" 和 "something that isn't [a-m]" 的区别。 /c[^a]t/ 会匹配 "cut",但它不会匹配 "cat" 也不会匹配 "ct" - 这点很重要。

用户名现在可以包含任何不是空格的字符(但长度仍然是5到12之间)。编写一个新的表达式去验证 username 变量。

字符类型

字符类型可以作为常见字符类的简写。有六个字符类型: \d 匹配十进制数字 (0-9), \s 匹配空白字符, \w 匹配单词字符(字母(包括国际字符)数字和下划线)。

其他三个字符类型可以通过前三个字符类型来发现,它们的作用与前三个相反;例如 \S 匹配任何非空白字符。

编写表达式匹配一个单词,后面跟一个空格,然后是一串数字。 用它来测试 charTypeTest 变量:不要使用任何字面量字符。

位置

如果你确定表达式在一个确切的位置以一个单词开始或结束,例如你想要确保一个字符串以一个大写字符开始,这时候你需要使用锚。美元符号匹配字符串的结尾,插入符号(^)匹配字符串开头。 /^cat$/ 只会匹配 "cat",而 /cat/ 则会匹配包含 "cat" 的字符串。

编写表达式测试 possibleUrl 是否以 "http://" 或 "https://" 开始,并不包含空格。

提示:对协议部分使用问号,然后其余部分使用一个否定字符类。你需要两个锚点。

捕获组

你可以使用括号来创建分组,它可以将多个标记组织在一起或者存储一个结果供之后引用:

/"(.+)"/

这是一个捕获组的例子,意思是括号内匹配的字符串稍后会保存到通过 .match().exec() 返回的数组中。

拿我们之前的例子,使用类似 /\(.{5,8}\)/.exec(shorterStory) 的表达式抓取括号之间的数据。试着再次运行它,然后用括号包起 ".{5,8}" 再试一次。

非捕获组

你可以看到现在这个数组有两个项的长度:第一项是整个匹配,第二项只有捕获组匹配的数据。

还有一种分组的类型叫做非捕获组。 这一类的分组,有一个稍微不同的语法,不存储值到数组。如果你不需要向后引用一个分组,你更应该使用非捕获组:它可以让返回的数组更简洁。通过在分组的开头(点号之前)插入 "?:" 把前一个表达式中的捕获组改为非捕获组

量词

非捕获组的主要用途是对一些标记应用量词。以下会匹配 "I ate" 和 "{{ firstName }} and I ate",但没有其他的了:

/^(?:{{ firstEscaped }} and )?I ate$/

编写一个表达式匹配 "ha" 重复两词或更多(例如,"haha" 或 "hahahahaha"),然后将表达式传给 answer() 函数。

提示:你的表达式应该不匹配 "hahah"。使用锚来确保它不会匹配。

管道符号

通过管道符号 (|) 你可以指定一个 "或"。以下会匹配 "The dog ate" 和 "The cat ate":

/The (dog|cat) ate/

我们也可以使用非捕获组,但在这个例子中我们想要访问分组捕获的结果。你可以在一个分组中使用多个管道。使之前的表达式在之前匹配的基础上再匹配 "The rabbit ate" (已存储在 rabbit 变量中)。

反向引用

在同一个表达式中,你可以引用之前捕获组的值。简单的在反斜线之后添加一个捕获组的编号(返回数组中的索引)。例如,以下会匹配 "The cat ate with the other cat" 和 "The dog ate with the other dog",但不会匹配 "the cat ate with the other dog" :

/The (dog|cat) ate with the other \1/

写一个表达式来匹配一行中相同的单词(如 "hello hello world"):像前面的例子一样,将表达式传给 answer() 函数。

正则表达式对象

除了字面量操作符(斜线),JavaScript 还提供了一个正则表达式构造函数,它允许你以字符串的形式指定想要的表达式。当要将变量放入表达式中时,正则表达式对象会很有用。它是这样工作的:

// Same as /regexp?/ig
new RegExp('regexp?', 'ig');

用户名还是一个变量。 userData 变量包含用户数据:在控制台打印出来可查看数据格式。使用 username 变量来提取与用户相关联的词。请把你的答案放在一行,以便于验证。

高级替换

我们已经看到了使用捕获组捕获值的两种方式:第一种是返回的数组,第二种是反向引用。你还可以从字符串 .replace() 方法的第二个参数中访问它们:

var text = '*italic text*';
var replace = '<em>$1</em>';
text.replace(/\*([^*]+)\*/, replace);

编写类似的代码,但是将 boldText 变量的值改为 <strong> 元素。

懒惰与贪婪匹配

默认的,JavaScript 中的匹配模式是“贪婪的”,意味着它会尽可能的匹配更多:

'"Hi", "Hello"'.match(/".+"/)

上面的代码将返回 "Hi", "Hello",因为它匹配了最外面的两个引号。懒惰模式匹配则与贪婪模式匹配相反,它会尽可能少的匹配,所以在上面代码中,只会匹配 "Hi"

懒惰模式可以通过在量词后面添加一个问号来实现,用懒惰模式试一下上面的例子。

断言

断言是一个应该被匹配但不存储的模式:所以不是“匹配 a 然后 b”, 而是“匹配后面跟着 b 的 a,但不匹配 b”。JavaScript 支持两种类型的断言,正向前瞻断言和负向前瞻断言。 前瞻意味着向前看,JavaScript 不支持回顾断言。

一个正向前瞻断言,意味着为了匹配我们需要向前查看。查找一个跟着 b 的 a,我们可以使用 /a(?=b)/

使用断言从 partialSums 变量提取 "6+3"。不要使用任何字面量数字,用 \d

反向断言

断言也可以是反的,意思是你想匹配的东西后面没有跟着什么东西。 注意与字符类不同,这可能会匹配一些不想匹配的东西,如果你想匹配“a 后面没有跟着 b”,匹配到的 a 可能是字符串结尾的 a。

负向前瞻断言的语法类似于正向前瞻断言,但你需要将等号替换为感叹号:例如,/a(?!b)/ 会寻找后面没有跟着字母 b 的字母 a。

使用一个正向前瞻断言,接着一个负向前瞻断言从 partialSums 变量中提取 "3+3"。

你完成了!

恭喜,{{ firstName }}, 完成了小试正则。你已经简要的尝试了 JavaScript 中的大部分正则表达式知识,你可以为大多数情况编写正则表达式了。

进一步的阅读,试试以下的链接:

Fork me on GitHub