正则表达式(regular expression)是描述字符模式的对象,JS的 RedExp 类表示正则表达式。
正则表达式的定义有两种方式,
(1)直接量(常用)
var pattern = /s$/;
(2)构造函数
var pattern = new RegExp()/"s$"/;
说明:ECMAScript 3规范规定,一个正则表达式直接量会在执行到它时转换为一个RegExp对象,同一段代码所表示正则表达式直接量的每次运算都返回同一个对象。ECMAScript 5规范则做了相反的规定,同一段代码所表示的正则表达式直接量的每次运算都返回新对象。
10.1.1 直接量字符
字母、数字 -> 自身
非字母、数字字符 ->需要转义,如\t制表符
在正则表达式中,许多标点符号具有特殊含义,它们是:
^ $ . * + ? = ! : | \ /()[ ] { }
比如,正则表达式“/\\/”用以匹配任何包含反斜线的字符串。
10.1.2 字符类
字符 | 匹配 |
[...] 例子 [abc] | 方括号内的任意字符,例子匹配 “a”,“b”,“c” 中的任意一个 |
[^...] 例子 [^abc] | 非方括号里的任意字符 |
. 等价类 [^\r\n] | 除Unicode行终止符(换行符、回车符)的任意字符 |
\w 等价类 [a-zA-Z_0-9] | 单词字符(字符、数字、下划线) *注1 |
\W 等价类 [a-zA-Z_0-9] | 非单词字符 |
\s 等价类 [ \t\n\x0B\f\r] *注2 | Unicode空白符(空格符、\x0B 垂直tab、\f 换页符...) |
\S 等价类 [^ \t\n\x0B\f\r] *注2 | 非Unicode空白符 |
\d 等价类 [0-9] | 数字字符 |
\D 等价类 [^0-9] | 非数字字符 |
[\b] | 退格直接量(特例) |
注:1. 原书没有下划线,但是试了试可以匹配下划线 2. \t前面有个空格不要漏了
本节其它参考资料:JS正则表达式 2-5 JS预定义类及边界 https://www.imooc.com/video/12531
10.1.3 重复
{n,m} 匹配前一项至少n次,但不超过m次
{n,} 匹配前一项至少n次
{n} 匹配前一项n次
? 匹配前一项0次或1次,即前一项可选,等价于{0,1}
+ 匹配前一项1次或多次,等价于{1,}
* 匹配前一项0次或多次,等价于{0,}
在使用“*”和“?”时要注意,由于这些字符可能匹配0个字符,因此它们允许什么都不匹配。例如,正则表达式/a*/实际上与字符串“bbbb”匹配,因为这个字符串含有0个a。
以上匹配重复字符是尽可能多地匹配,而且允许后续的正则表达式继续匹配。因此,我们称之为“贪婪的”匹配。
如何使用正则表达式进行非贪婪匹配?只须在重复量词后跟随一个问号即可:“??”、“+?”、“*?”或“{1,5}?”。
注意:使用非贪婪的匹配模式所得到的结果可能和期望并不一致。考虑以下正则表达式/a+b/,当使用“aaab”作为匹配字符串时,它会匹配整个字符串。再试一下非贪婪匹配的版本/a+?b/,它匹配尽可能少的a和一个b。当用它来匹配“aaab”时,这个模式也匹配了整个字符串。这是因为正则表达式的模式匹配总是会寻找字符串中第一个可能匹配的位置。由于该匹配是从字符串的第一个字符开始的,因此在这里不考虑它的子串中更短的匹配。
10.1.4 选择、分组和引用
选择
字符“|”用于分隔供选择的字符。
例如,
/\d{3}|[a-z]{4}/ 匹配三位数字或者四个小写字母
/a|ab/ 匹配字符串“a”或“ab”,但匹配“ab”时,它只能匹配第一个字符。
注意:选择项的尝试匹配次序是从左到右,直到发现了匹配项。如果左边的选择项匹配,就忽略右边的匹配项,即使它产生更好的匹配。
分组和引用
正则表达式中的圆括号有多种作用,
1.把单独的项组合成子表达式,以便可以像处理一个独立的单元那样用“|”、“*”、“+”或者“?”等来对单元内的项进行处理。
例如,
/java(script)?/ 匹配字符串“java”或“javascript”
/(ab|cd)+ef/ 匹配字符串“ef”,也可以匹配字符串“ab”或“cd”的一次或多次重复
2.在完整的模式中定义子模式。
例如,假定我们正在检索的模式是一个或多个小写字母后面跟随了一位或多位数字,则可以使用/[a-z]+\d+/。但假定我们真正关心的是每个匹配尾部的数字,那么如果将模式的数字部分放在括号中(/[a-z]+(\d+)/),就可以从检索到的匹配中抽取数字了。
3.允许在同一正则表达式的后部引用前面的子表达式。这是通过在字符“\”后加一位或多位数字来实现的。
\1引用的是第一个带圆括号的子表达式,\3引用的是第三个带圆括号的子表达式。
注意:子表达式可以嵌套另一个子表达式,所以它的位置是参与计数的左括号的位置。
例如,在下面的正则表达式中,嵌套的子表达式([Ss]cript)可以用\2来指代:
/([Jj]ava([Ss]cript)?)\sis\s(fun\w*)/
注意:对正则表达式中前一个子表达式的引用,并不是指对子表达式模式的引用,而指的是与那个模式相匹配的文本的引用。
例如,下面的正则表达式匹配的就是位于单引号或双引号之内的0个或多个字符,并且要匹配左右侧的引号:
/(['"])[^'"]*\1/
仅分组不生成引用
在正则表达式中如果不需要生成引用可以使用“(?:”和“)”来进行分组,例如,
/([Jj]ava(?:[Ss]cript)?)\sis\s(fun\w*)/
子表达式(?:[Ss]cript) 仅用于分组,在这个正则表达式中,\2引用了与(fun\W*)匹配的文本。
10.1.5 指定匹配位置
有一些正则表达式的元素匹配的是字符之间的位置。例如,\b匹配一个单词的边界,即位于\w字符和\W之间的边界,或位于一个单词与字符串的开始或结尾之间的边界 。像\b这样的元素不匹配某个可见的字符,它们指定匹配发生的合法位置。有时我们称这些元素为正则表达式的锚。
正则表达式中的锚字符
^ 匹配字符串的开头,多行检索中匹配一行的开头
$ 匹配字符串的结尾,多行检索中匹配一行的结尾
\b 匹配一个单词的边界
\B 匹配非单词边界的位置
(?=p) 正向前瞻断言,要求接下来的字符与p匹配,注:JS不支持后顾
(?!p) 负向前瞻断言,要求接下来的字符不与p匹配
例子:
/^JavaScript$/ 匹配“JavaScript”
/\bJava\b/ 匹配“Java”
/Java([Ss]cript)?(?=\:)/ 匹配Java和JavaScript(或Javascript),但只在其后有冒号时才匹配。这个正则表达式匹配“JavaScript: The Definitive Guide”中的“JavaScript”,但不匹配“Java in a Nutshell”中的“Java”,因为它后面没有冒号。
10.1.6 修饰符
修饰符位于第二条斜线之后。JavaScript支持三个修饰符:
i (ignore case) 模式匹配是不区分大小写
g (global) 模式匹配应该是全局的,即找出所有匹配
m (multiline) 在多行模式中执行匹配,如果待检索的字符串包含多行,那么^和$锚字符除了匹配整个字符串的开始和结尾之外,还能匹配每行的开始和结尾。
例如,/java$/im可以匹配“java”也可以匹配“Java\nis fun”
10.2 用于匹配模式的String方法
String支持4种使用正则表达式的方法。
search()方法
- 参数是一个正则表达式,若不是正则表达式,则首先会通过RegExp构造函数将它转换成正则表达式。
- 返回第一个与之匹配的子串的起始位置,如果找不到匹配的子串,它将返回-1。
- search()方法不支持全局检索,因为它忽略正则表达式参数中的修饰符g。
例:
"JavaScript".search(/script/i); //调用返回值为4
replace()方法
- 第一个参数是一个正则表达式,第二个参数是要进行替换的字符串。若第一个参数是字符串而不是正则表达式,则将直接搜索这个字符串。
- 用以执行检索与替换操作。如果正则表达式中设置了修饰符g,那么源字符串中所有与模式匹配的子串都将替换成第二个参数指定的字符串;如果不带修饰符g,则只替换所匹配的第一个子串。
例1.将所有不区分大小写的javascript都替换成大小写正确的JavaScript
text.replace(/javascript/gi, "JavaScript");
例2. 将一个字符串中的英文引号替换为中文半角引号:
// 一段引用文本起始于引号,结束于引号,中间的内容区域不能包含引号
var quote = /"([^"]*)"/g;
// 用中文半角引号替换英文引号,同时要保持引号之间的内容(存储在$1中)没有被修改
text.replace(quote, '“$1”');
例3. 把格式为yyyy-mm-dd的日期转换为格式mm/dd/yyyy
'2019-06-30'.replace(/(\d{4})-(\d{2})-(\d{2})/g,'$2\/$3\/$1');
match()方法
是最常用的String正则表达式方法。它的唯一
- 参数是一个正则表达式(或通过RegExp()构造函数将其转换为正则表达式)
- 返回是一个由匹配结果组成的数组
- 如果该正则表达式设置了修饰符g,则该方法返回的数组包含字符串中的所有匹配结果。
- 如果该正则表达式没有设置修饰符g,则该方法只检索第一个匹配。在这种情况下,也返回一个数组,数组的第一个元素就是匹配的字符串,余下的元素则是正则表达式中用圆括号括起来的子表达式。因此,如果match()返回一个数组a,那么a[0]存放的是完整的匹配,a[1]存放的则是与第一个用圆括号括起来的表达式相匹配的子串,以此类推。为了和方法replace()保持一致,a[n]存放的是$n的内容。
例1.
"1 plus 2 equals 3".match(/\d+/g) // 返回 ["1", "2", "3"]
例2.使用如下的代码来解析一个URL:
var url = /(\w+):\/\/([\w.]+)\/(\S*)/;
var text = "Visit my blog at http://www.example.com/~david";
var result = text.match(url);
if(result != null){
var fullurl = result[0]; // 包含 "http://www.example.com/~david"
var protocol = result[1]; // 包含 "http"
var host = result[2]; // 包含 "www.example.com"
var path = result[3]; // 包含 "~david"
}
split()方法
- 参数是一个分隔符,也可以是一个正则表达式
- 这个方法用以将调用它的字符串拆分为一个子串组成的数组
例1.
"123,456,789".split(","); // 返回 ["123","456","789"]
例2. 指定分隔符,允许两边可以留有任意多的空白符
"1, 2, 3, 4, 5".split(/\s*,\s*/); // 返回 ["1","2","3","4","5"]
10.3 RegExp对象
RegExp()构造函数用以创建新的RegExp对象,带有两个字符串参数:
- 第一个参数包含正则表达式的主体部分,也就是正则表达式直接量中两条斜线之间的文本
注意:不论是字符串直接量还是正则表达式,都使用“\”字符作为转义字符的前缀,因此当给RegExp()传入一个字符串表述的正则表达式时,必须将“\”替换成“\\”。
- 第二个参数是可选的,指定正则表达式的修饰符 g、i、m或者它们的组合
// 全局匹配字符串中的5个数字,注意这里使用了"\\",而不是"\"
var zipcode = new RegExp("\\d{5}", "g");
每个RegExp对象都包含5个属性:
- 属性source是一个只读的字符串,包含正则表达式的文本
- 属性global是一个只读的布尔值,用以说明这个正则表达式是否带有修饰符g
- 属性ignoreCase也是一个只读的布尔值,用以说明正则表达式是否带有修饰符I
- 属性multiline是一个只读的布尔值,用以说明正则表达式是否带有修饰符m
- 属性lastIndex,它是一个可读/写的整数。如果匹配模式带有g修饰符,这个属性存储在整个字符串中下一次检索的开始位置,这个属性会被exec()和test()方法用到
exec()方法对一个指定的字符串执行一个正则表达式,在一个字符串中执行匹配检索。属性index包含了发生匹配的字符位置,属性input引用的是正在检索的字符串。
- 如果它没有找到任何匹配,它就返回null
- 如果它找到了一个匹配,它将返回一个数组,第一个元素包含的是与正则表达式相匹配的字符串,余下的元素是与圆括号内的子表达式相匹配的子串。
- 当调用exec()的正则表达式对象具有修饰符g时,它将把当前正则表达式对象的lastIndex属性设置为紧挨着匹配子串的字符位置。当同一个正则表达式第二次调用exec()时,它将从lastIndex属性所指示的字符处开始检索。如果exec()没有发现任何匹配结果,它会将lastIndex重置为0(在任何时候都可以将lastIndex属性设置为0,每当在字符串中找最后一个匹配项后,在使用这个RegExp对象开始新的字符串查找之前,都应当将lastIndex设置为0)。
例:
var pattern = /Java/g;
var text = "JavaScript is more fun than Java!";
var result;
while((result = pattern.exec(text))!= null){
alert("Matched '" + result[0] + "'" +
" at position " + result.index +
"; next search begins at " + pattern.lastIndex);
}
RegExp方法是exec()和String方法match()主要区别:
1)RegExp方法的参数是一个字符串,而String方法的参数是一个RegExp对象。
2)不管正则表达式是否具有全局修饰符g,exec()都会返回一样的数组。
test()方法,比exec()更简单一些。它的参数是一个字符串,用test()对某个字符串进行检测,如果包含正则表达式的一个匹配结果,则返回true。
例:
var pattern = /java/i;
pattern.test("JavaScript"); // 返回true
调用test()和调用exec()等价,当exec()的返回结果不是null时,test()返回true。由于这种等价性,当一个全局正则表达式调用方法test()时,它的行为和exec()相同,因为它从lastIndex指定的位置处开始检索某个字符串,如果它找到了一个匹配结果,那么它就立即设置lastIndex为当前匹配子串的结束位置。这样一来,就可以使用test()来遍历字符串,就像用exec()方法一样。
与exec()和test()不同,String方法search()、replace()和match()并不会用到lastIndex属性。实际上,String方法只是简单地将lastIndex属性值重置为0。如果让一个带有修饰符g的正则表达式对多个字符串执行exec()或test(),要么在每个字符串中找出所有的匹配以便将lastIndex自动重置为零,要么显式将lastIndex手动设置为0(当最后一次检索失败时需要手动设置lastIndex)。如果忘了手动设置lastIndex的值,那么下一次对新字符串进行检索时,执行检索的起始位置可能就不是字符串的开始位置,而可能是任意位置 。
如果RegExp不带有修饰符g,则不必担心会发生这种情况。同样要记住,在ECMAScript 5中,正则表达式直接量的每次计算都会创建一个新的RegExp对象,每个新RegExp对象具有各自的lastIndex属性,这势必会大大减少“残留”lastIndex对程序造成的意外影响。