在这里输入一些 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 中的大部分正则表达式知识,你可以为大多数情况编写正则表达式了。
进一步的阅读,试试以下的链接:
- Mozilla 开发者网络: 正则表达式
- JavaScript Regular Expression Enlightenment by Cody Lindley
- DZone: The Essential Regular Expressions Cheat Sheet by Callum Macrae
- RegExp playground by Lea Verou
- regular-expressions.info 非特定语言的通用指南