The safest way to try to get what you want is to try to deserve what you want

js中的正则表达式

Posted on By Frank·J

文章总结于 《JavaScript权威指南》

我为什么要学正则

其实我在日常的开发过程中用到正则的情况还是比较少的,经常用到的一些场景无非就是:用户输入的校验,包括一些邮箱,电话等等。自己看得一些零零碎碎的正则知识其实就够了,但是作为一个有着学习热情,并享受代码的人来说,这显然不符合我的作风(这波操作应该不算过分)!那就翻开书开始啃吧…下面就是我认为比较有价值的部分(当然精华太多,我也不可能全记下来)拿出来跟大家分享一下…独乐乐不如众乐乐 ^_^

正则表达式的定义

Javascript中的正则表达式用 RegExp 对象表示,可以使用 RegExp() 构造函数来创建,不过我们通常使用直接量的语法来创建。例如:

// 这个 RegExp 对象用来匹配所有以字母 ‘s’ 结尾的字符串
var pattern = /s$/;

// 构造函数定义的方式如下:
var pattern = new RegExp('s$');

直接量字符

正则表达式中的所有字母和数字都是按照字面含义进行匹配的。JavasCript 正则表达式语法也支持非字母的字符匹配,这些字符需要通过反斜线(\)作为前缀进行转义。下表列出了这些转义字符:

| 字符 | 匹配 | | ————- | :————–| | 字母和数字字符 | 自身| | \o | NUL 字符(\u0000)| | \t | 制表符(\u0009) | | \n | 换行符(\u000A) | | \v | 垂直制表符(\u000B)| | \f | 换页符(\u000C) | | \r | 回车符(\u000D)| | \xnn | 由十六进制数nn指定的拉丁字符,例:\x0A 等价于 \n | | \uxxxx | 由十六进制数xxxx指定的Unicode字符,例:\u0009 等价于 \t | | \cX | 控制字符^X,例:\cJ 等价于换行符 \n | 在正则表达式中,许多标点符号基友特殊含义,它们是:

^ $ . * + ? = ! : | \ / () [] {}

PS: JavaScript 中的所有的函数都是闭包:它们都是对象,它们都关联到作用域链。

字符类(Character class)

将直接量字符单独放进方括号内就组成了字符类,

// 一个字符类可以匹配它所包含的任意字符
var partten = /[abc]/;
partten.test('a'); // => true
partten.test('b'); // => true
partten.test('c'); // => true
partten.test('abcd'); // => true
partten.test('d'); // => false

// 否定字符类 ^
var partten = /[^abc]/;
partten.test('a');  // => false
partten.test('d');  // => true
partten.test('ad'); // => true

// 使用连字符(-),表示字符范围
// 匹配拉丁字母中任何字母和数字
var partten = /[a-zA-Z0-9]/;
partten.test('a0');  // => true
partten.test('a0_'); // => true
partten.test('_');   // => false

由于某些字符类很常用,因此在 JavaScript 的正则表达式中使用了这些特殊字符的转义字符来表示她们。

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

重复

用以上的语法可以把两位数描述成 /\d\d/,四位数描述成 /\d\d\d\d/,但是当情况较为复杂时比如描述三个字符和一个数字,这些情况都提到了 “重复出现次数” ,由于某些重复种类非常常用,因此就有一些专门用于表示这种情况的特殊字符。下表总结了这些表示重复的正则语法:

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

以下是一些例子:

/\d{2,4}/   // 匹配2~4个数字
/\w{3}\d?/  // 匹配3个单词和1个可选的数字
/\s+JS\s+/  // 匹配前后1个或多个空格的字符串 ‘JS’
/[^(]*/     // 匹配一个或多个非左括号的字符

PS: 由于 “*” 和 “?” 允许匹配 0 个字符,因此 /a*/ 实际上与字符串 “b” 匹配,因为这个字符串包含 0 个 a

非贪婪的重复

上表中的匹配重复字符都是尽可能多的匹配,而且允许后续的正则表达式继续匹配,因此我们称之为 “贪婪匹配”。我们同样可以使用正则表达式进行非贪婪匹配,只需在待匹配的字符后跟随一个 “?” 即可:“??”、“+?”、“*?”或“{1,5}?”。

比如: /a+/ 匹配一个或多个连续的字母a,/a+?/ 也可以匹配一个或多个连续的字母a,但它是尽可能少地匹配,对于字符串 “aaa” 前者会匹配它的三个字符,后者只会匹配第一个a

PS: 正则表达式的模式匹配总是会寻找字符串中第一个可能匹配的位置;以下是一个例子:对于字符串 “aaab” 使用 /a+b//a+?b/ 都会匹配整个字符串

选择、分组和引用

正则表达式的语法还包括指定选择项、子表达式分组和引用前一子表达式的特殊字符

选择

字符 “ ” 用于分隔供选择的字符,例如, /ab|cd|ef/ 可以匹配 “ab”,也可以匹配 “cd” 或者 “ef”。

PS: 选择项的尝试匹配次序是从左到右,直到发现了匹配项。如果左边的选择项匹配,就会忽略右边的匹配。

分组

圆括号 “()” 在正则表达式中的其中一个作用就是把单独的项组合成子表达式,以便把每一个子项当作独立的单元来处理, 例如 /java(script)?/ 可以匹配字符串 “java”,其后可以有 “script” 也可以没有。

圆括号的另一个作用是在完整的模式中定义子模式,当一个正则表达式成功地和目标字符串相匹配时,可以从目标中抽出和圆括号中的子模式相匹配的部分。例如:假如我们需要匹配一个或多个小写字母后面跟随了一位或多位数字,则可以使用 /[a-z]+\d+/,如果我们真正关心的是每个匹配尾部的数字,那么将模式的数字放在括号中 /[a-z]+(\d+)/,就可以检索到的字符串中抽取数字了。

除此之外圆括号的另一个用途是允许在同一正则表达式的后部引用前面的子表达式。这里放两个例子好了:

// 以下表达式中,嵌套的子表达式 ([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*)匹配的文本

指定匹配位置

正则表达式中的多个元素才能匹配字符串的一个字符。例如:

  • \s 匹配的只是一个空白符
  • \b 匹配的是一个单词的边界,即位于 \w 和 \W 之间的边界

像 \b 这样的元素不匹配某个可见的字符,它们指定匹配发生的合法位置,我们称这些元素为正则表达式的锚,最常见的锚元素是^,它用来匹配字符串的开始,锚元素$用来匹配字符串的结束。

// 匹配单词 JavaScript
/^JavaScript$/

// 匹配单词 Java本身(前后都有空格),而不是像在 JavaScript 中作为前缀
/\sJava\s/

// 以上的表达式有两个问题
// 1. 不能匹配单词在开头或者结尾的情况
// 2. 返回的匹配字符串前后都有空格
// 使用边界 \b 来代替真正的空格 \s
/\bJava\b/

// \B 匹配非单词边界的位置
// 以下表达式匹配 “Javascript” 和 “postscript”
// 不匹配 “script” 和 “Scripting”
/\B[Ss]cript/

任意正则表达式都可以作为锚点条件。如果在符号“(?=”和“)”之间加入一个表达式,他就是一个先行断言:

// 该正则表达式匹配时必须带冒号
// 匹配:“JavaScript: The Definitive Guide” 中的 “JavaScript”
// 不能匹配:“Java in a Nutshell” 中的 “Java”
/[Jj]ava([Ss]cript)?(?=\:)/
/\sJava\s/

// 以上的表达式有两个问题
// 1. 不能匹配单词在开头或者结尾的情况
// 2. 返回的匹配字符串前后都有空格
// 使用边界 \b 来代替真正的空格 \s
/\bJava\b/

带有“(?!”的断言是负向先行断言,用以指定接下来的字符都不必匹配(忽略)例如:/Java(?! Script)([A-Z]\w*)/ 可以匹配 “Java” 后跟随一个大写字母和任意多个 ASCII 单词

修饰符

正则表达式的修饰符用以说明高级匹配模式的规则,与之前的表达式语法不同,修饰符不是放在两条斜线之间,而是放在第二条“/”之后。

字符 含义
i 执行不区分大小写的匹配
g 执行一个全局匹配
m 多行匹配(^匹配一行的开头和字符串的开头,$同理)

模式匹配的 String 方法

下面是 String 对象的一些用以执行正则表达式模式匹配和检索替换的操作方法,String 支持4种使用正则表达式的方法。

它的参数是一个正则表达式,返回第一个与之匹配的子串的起始位置,如果匹配不到将返回 -1。

'JavaScript'.search(/script/i); // => 4

如果 search() 的参数不是正则表达式,则首先会通过 RegExp 构造函数将它转换为正则表达式,search() 方法不支持全局检索,因为它忽略正则表达式参数中的修饰符 g

replace()

replace() 方法用以执行检索与替换操作。其中第一个参数是一个正则表达式,第二个参数是要进行替换的字符串。支持修饰符 g

// 将所有不区分大小写的‘javascript’都替换成‘JavaScript’
text.replace(/javascript/gi, 'JavaScript');

replace() 还有更加强大的功能,之前提到的圆括号包含的子表达式带有从左到右的索引变好,而且正则表达式会记忆与每一个子表达式匹配的文本。如果在替换的字符串中出现了 $ 加数字,那么 replace() 将用与指定的子表达式相匹配的文本来替换这两个字符,比如:将一个字符串中的英文引号替换为中文半角引号

// 一段引用文本起始于引号,结束于引号
// 中间的内容区域不能包含引号
var quote = /"([^"]*)"/g;
// 用中文半角引号替换英文引号,同时要保持引号之间的内容(存储在 $1 中)不被修改
text.replace(quote, '“$1”');

PS: replace() 的第二个参数可以是函数,该函数能够动态的脊髓炎替换字符串

match()

match() 方法是最常用的 String 正则表达式方法。它的唯一参数就是一个正则表达式(或通过RegExp()构造函数将其转换为 正则表达式),返回值是一个由匹配结果组成的数组。支持全局匹配(g)

'1 plus 2 equals 3'.match(/\d+/g); // => ["1", "2", "3"]

如果这个正则表达式没有设置修饰符 g,match() 就不会进行全局检索,它只检索第一个匹配。但即使match()执行的不是全局检索,它也返回一个数组,这种情况下,数组的第一个元素就是匹配字符串,余下的元素则是正则表达式中用圆括号括起来的子表达式,因此match()返回一个数组a ,那么a[0]存放的是完整的匹配,a[1]存放的则是与第一个圆括号括起来的表达式相匹配的子串,以此类推…

> var url = /(\w+):\/\/([\w.]+)\/(\S*)/;

var text = “Visit my blog at http://www.example.com/~frank”;
var result = test.match(url);
if (result != null) {
var fullurl = result[0]; // => ‘http://www.example.com/~frank’
var protocal = result[1]; // => ‘http’
var host = result[2]; // => ‘www.example.com’
var path = result[3]; // => ‘~frank’
}

split()

String 对象的最后一个和正则表达式相关的方法是 split()。该方法将字符串按照分割符(分隔符就是该方法的参数)组成一个数组,这个方法强大之处在于它接收的参数也可以是一个正则表达式。

RegExp 对象

正则表达式是通过 RegExp 对象来表示的,除了 RegExp() 构造函数之外,RegExp 对象还支持三个方法和一些属性。使用 RegExp() 创建新的 RegExp 对象,第一个参数包含正则表达式的主体部分,第二个参数是可选的修饰符。

PS: 不论是字符串直接量还是正则表达式,都使用 ‘\’ 字符作为转义字符的前缀,因此必须将 ‘\’ 替换成 ‘\’

// 全局匹配字符串中的 5 个数字
var zipcode = new RegExp('\\d{5}', 'g');

RegExp 的属性

RegExp 对象都包含 5 个属性:

  1. source: 只读的字符串,包含正则表达式的文本
  2. global: 只读的布尔值,说明是否带有修饰符g
  3. ignoreCase: 只读的布尔值,说明是否带有修饰符i
  4. multiline: 只读的布尔值,说明是否带有修饰符m
  5. lastIndex: 可读/可写的整数,如果匹配模式带有 g 修饰符,这个属性存储在整个字符串中下一次检索的开始位置,这个属性会被 exex() 和 test() 方法用到

RegExp 的方法

RegExp 对象定义了两个用于执行模式匹配操作的方法。

exec()

参数是一个字符串,如果没有匹配到结果返回 null,反之返回一个数组。

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}`)
}

PS: 当 exec() 的正则表达式对象具有修饰符 g 时,它将把当前正则表达式对象的 lastIndex 属性设置为紧挨着匹配子串的字符位置,当同一个正则表达式第二次调用 exec() 时,它将从 lastIndex 属性所指示的字符处开始检索

test()

参数是一个字符串,如果没有匹配到结果返回 false,反之返回true。

var pattern = /Java/i;
pattern.test('JavaScript is more fun than Java!'); // => true

结语

本文比较全面的讲了 JavaScript 中的正则,到这就结束了。其实正则的基础知识是比较少的,关键在于如何组合使用并将其运用在开发过程中。正则是个好东西,用的好可以帮我们节省代码、节约时间