08.JavaScript基础系列:正则表达式

235 阅读8分钟

JavaScript基础系列

正则表达式是一个描述字符模式的对象,JavaScript 的 RegExp 类表示正则表达式,String 和 RegExp 都定义了方法。RegExp 使用正则表达式进行强大的模式匹配和文本检索与替换功能。

JavaScript 中的正则表达式用 RegExp 对象表示,可以使用 RegExp() 构造函数来创建 RegExp 对象,不过 RegExp 对象更多的是通过一种特殊的直接量语法来创建。

1.直接量字符

将直接量字符单独放进方括号内就组成了字符类,一个字符串类可以匹配它所包含的任意字符。

字符类

/[abc]/         // 匹配字母 a b c 中的任意一个
/[^abc]/        // 匹配除 a b c 之外中的所有字符
/[a-zA-Z0-9]/   // 匹配任何字母和数字

正则表达式的字符类

字符        匹配
[...]       方括号内的任意字符
[^...]      不在方括号内的任意字符
.           除换行符和其他 Unicode 行终止符之外的任意字符
\w          任何 ASCII字符串组成的单词,等价于 [a-zA-Z0-9]
\W          任何 不是 ASCII字符串组成的单词,等价于 [^a-zA-Z0-9]
\s          任何Unicode空白符
\S          任何非Unicode空白符的字符,与 \W 不同
\d          任何 ASCII 数字,等价于[0-9]
\D          除 ASCII 数字之外的任何字符,等价于[^0-9]

通过一些案例更输入的了解

/./.test('aaa')     // test
/./.test('')        // false
/^\s/.test(' ')     // true
/^\S/.test(' ')     // false

/\w/.test('h9')     // true
/\w/.test('%')      // false
/\S/.test('%')      // true

重复

可以在正则模式之后跟随用以指定字符重复的标记来表示重复出现的次数。

字符            含义
{n, m}          匹配前一项至少 n 次,但不能超过 m 次
{n,}            匹配前一项至少 n 次或者更多次
{n}             匹配前一项至 n 次
?               匹配前一项 0 次或 1 次,等价于{0, 1}
+               匹配前一项 1 次或多次,等价于 {1,}
*               匹配前一项 0 次或多次,,等价于 {0,}

通过一些案例更输入的了解

/\d{2,4}/       // 匹配2 -4 个数字
/\w{3}\d?/      // 精确匹配三个单词或一个可选的数字
/\s+java\s+/    // 匹配前后带有一个或多个空格的字符串 "java"

在使用 * 和 ? 时要注意,由于这些字符可能匹配 0 个字符,因此它们允许什么都不匹配。 例如,正则表达式 /a*/ 与字符串 'bbb' 匹配,因为它包含0个a。

非贪婪的重复

上面列出的匹配重复字符串是尽可能多地匹配,而且允许后续的正则表达式继续匹配,我们称之为贪婪的匹配。

我们可以使用正则表达式进行非贪婪匹配,只须在待匹配的字符串后跟随一个问号即可,"??"、"+?","*?" 或者 "{1,5}?"。

比如正则表达式 /a+/ 可以匹配一个或多个连续的字符a,当时有 "aaa" 作为字符串匹配时,正则表达式会匹配它的三个字符。

但是 /a+?/ 可以匹配一个或多个连续字母a,但它是尽可能少地匹配,最后只会匹配到第一个a。

'aaa'.match(/a+/);      // ["aaa", index: 0, input: "aaa"]
'aaa'.match(/a+?/);     // ["a", index: 0, input: "aaa"]

使用非贪婪的匹配模式所得到的的结果可能和预期并不一致,/a+?b/ 我们期望它能匹配一个a和最后一个b,但实际上,这个模式却匹配了整个字符串,和模式/a+b/匹配一模一样,这是因为正则表达式的模式匹配总是会寻找字符串中第一个可能匹配的位置。

由于该匹配是从字符串的第一个字符开发的,因此在这里不考虑它的子串中更短的匹配。

'aaab'.match(/a+b/);    // ["aaab", index: 0, input: "aaab"]
'aaab'.match(/a+?b/);   // ["aaab", index: 0, input: "aaab"]

选择、分组和引用

正则表达式的语法包括指定选择项、子表达式分组和引用前一子表达式的特殊字符。字符 "|" 用于分隔供选择的字符。例如 /ab|cd|ef/ 可以匹配字符串 "ab",也可以匹配字符串 "cd",还可以匹配字符串 "ef"。

选择项的尝试匹配次序是从左到右,直到发现了匹配项。如果左边的选择项匹配,就忽略右边的匹配项,即使它产生更好的匹配。

'ab'.match(/a|ab/)	// ["a", index: 0, input: "ab", groups: undefined]

在正则表达式中,圆括号的作用是在完整的模式中定义子模式,当一个正则表达式成功地和目标字符串相匹配时,可以从目标串抽出和圆括号中的子模式相匹配的部分。

/[a-z]+(\d+)/ 模式是匹配一个或多个小写字母后面跟随的一位或多位数字,同时能从检索到的匹配中抽取数字。

带圆括号的表达式允许在同一个正则表达式的后部引用前面的子表达式,通过在字符 "" 后加一位或多位数字来实现,这个数字指定了带圆括号的子表达式在正则表达式中的位置。eg: \1 引用的是第一个带圆括号的子表达式。

在正则表达式中可以实现不用创建带数字编码的引用,可以对子表达式进行分组,是通过 "(?:" 和 ")" 来进行分组。/([Jj]ava(?:[Ss]cript)?)(fun\w*)/ 模式中,\2 引用的是(fun\w*) 匹配的文本。

字符        含义
|           选择,匹配的是该符号左边的子表达式或右边子表达式
(...)       组合,将几个项组合为一个单元,而且可以记住和这个组合匹配的字符串供此后引用
(?:...)     只组成,把项组合到一个单一,但不记忆与该组相匹配的字符
\n          和第n个分组第一次匹配的字符相匹配,组是圆括号中的子表达式

指定匹配位置

有一些正则表达式的元素匹配的是字符之间的位置,而不是实际的字符,例如,\b 匹配一个单词的边界,即位于 \w(ASCII单词)字符和 \W(非ASCII单词)之间的边界,或位于一个 ASCII 单词与字符串的开始和结尾之间的边界。

像 \b 这样的元素不匹配某个可见的字符,它指定匹配发生的合法位置,我们称这些元素为正则表达式的描,因为它们将模式定位在搜索字符串的特定位置上。

最常见的锚元素是 ^,它用来匹配字符串的开始,锚元素是 $,它用来匹配字符串的结束。

/\bJava\b/.test( 'Java ')           // true
/\bJava\b/.test( 'JavaScript ')     // false

/\BJava\b/.test( 'PostJava ')       // true
/\BJava\b/.test( 'JavaScaript')	    // false

在符号 "(?=" 和 ")" 之间加入一个表达式,它就是一个先行断言,用以说明圆括号内的表达式必须正确匹配,但并不是真正意义上的匹配。

/Java(?=Script)([A-Z]\w*)/.test('JavaBeans')        // false
/Java(?=Script)([A-Z]\w*)/.test('JavaScript')       // true
/Java(?=Script)([A-Z]\w*)/.test('JavaScriptPost')   // true

// ["JavaScriptPost", "ScriptPost", index: 0, input: "JavaScriptPost"]
'JavaScriptPost'.match(/Java(?=Script)([A-Z]\w*)/)

// ["JavaScriptPost", "Script", "Post", index: 0, input: "JavaScriptPost"]
'JavaScriptPost'.match(/Java(Script)([A-Z]\w*)/)

带有 "(?!" 的断言是负向先行断言,用以指定接下来的字符都不必匹配。

/Java(?!Script)([A-Z]\w*)/.test('JavaBeans')    // true
/Java(?!Script)([A-Z]\w*)/.test('Javanese')     // false
/Java(?!Script)([A-Z]\w*)/.test('JavaScript')   // false
/Java(?!Script)([A-Z]\w*)/.test('JavaScripter') // false
字符        含义
^           匹配字符串的开头,在多行检索中,匹配一行的开头
$           匹配字符串的结尾,在多行检索中,匹配一行的结尾
\b          匹配一个单词的边界
\B          匹配非单词边界的位置
(?=p)       零宽正向先行断言,要求接下来的字符都与p匹配,但不能包括匹配p的那些字符
(?!p)       零宽负向先行断言,要求接下来的字符不与p匹配

修饰符

修饰符用以说明高级匹配模式的规则。修饰符是放在 '/' 符合之外的,它不出现在两条斜线之间,而是第二条斜线之后。

  • 修饰符 i: 执行不区分大小写的匹配
  • 修饰符 g: 执行一个全局匹配,简言之,即找到所有的匹配,而不是在找到第一个之后就停止
  • 修饰符 m: 多行模式匹配,^ 匹配行的开头和字符串的开头,$ 匹配行的结束和字符串的结尾
'java\nis fun'.match(/java$/gi)     // null
'java\nis fun'.match(/java$/gim)    // ["java"]

用于模式匹配的 String 方法

  • search(): 返回第一个与之匹配的子串的起始位置,不支持全局修饰符g
  • replace(): 执行检索和替换操作,支持全局修饰符g
  • match(): 返回的是一个由匹配结果组成的数组,支持全局修饰符g
  • split()
// 4
'JavaScript'.search(/script/i);
 
// "JavaBean"
'JavaScript'.replace(/script/i, 'Bean');  

// ["Script", index: 4, input: "JavaScript", groups: undefined]
'JavaScript'.match(/script/i);

// ["1", "2", "3", "4", "5", "6"]
'1, 2, 3, 4,5 ,6'.split(/\s*,\s*/);

RegExp 对象

RegExp() 构造函数带有两个字符串参数,其中第二个参数是可选的,RegExp() 用以创建新的 RegExp 对象,第一个参数包含正则表达式的主体部分,也就是正则表达式直接量中两条斜线之间的文本。

第二个参数是可选的,用于指定正则表达式的修饰符。不过只能传入修饰符 g、i、m 或者它们组合。

// 全局匹配字符串中 5 个数字,注意这里使用了 "\\",而不是 "\"
new RegExp("\\d{5}", g);

Regexp() 构造函数非常有用,特别是在需要动态创建正则表达式的时候。

每个 RegExp 对象包含 5 个属性
  • 属性 source 是一个只读的字符串,包含正则表达的文本
  • 属性 global 是一个只读的布尔值,用以说明正则表达式是否带有修饰符 g
  • 属性 ignoreCase 是一个只读的布尔值,用以说明正则表达式是否带有修饰符 i
  • 属性 multiline 是一个只读的布尔值,用以说明正则表达式是否带有修饰符 m
  • 属性 lastIndex 是一个可读/写的整数,如果匹配模式带有 g 修饰符,这个属性存储在整个字符串中下一个检索的开始位置
RegExp 的方法
  • exec()
  • match()