作者:link
我们先来看看,我们干哈要学正则表达式这玩意儿:
JavaScript、JAVA、Perl、PHP、C#...
。Linux/Unix、Mac、Windows PowerScript
。在我们常用的开发工具中,如Fiddler Willow、WebStorm、Vim,正则表达式也能帮助我们方便的进行Find&Replace的工作。由于正则表达式的流派很多,这篇文章主要是描述JavaScript中的正则表达式。
这是《精通正则表达式》对于它的定义,反正我看了这句话还是不知道正则表达式是干嘛用的,不过没关系,下面我们先来看一下JavaScript的正则表达式中一些常用的语法。
在JavaScript中,我们可以通过RegExp()构造函数或者RegExp直接量两种方式去创建正则表达式。
var pattern1 = /s$/;
var pattern2 = new RegExp('s$');
上面代码中的pattern1和pattern2是等价的,都是用来匹配所有以字母s
结尾的字符串。
多说两句:
在创建变量时,对于布尔、数值、字符串、null和undefined这个五个原始值类型来说,原始类型优于封装对象,原因如下。
1、不同于字符串直接量,new出来的String是一个真正的对象,这意味着你不能使用内置操作符来比较两个截然不同的String对象的内容。
var s = new String('hello');
typeof 'hello'; // string
typeof s; // object
var s1 = new String('hello');
var s2 = new String('hello');
s1 === s2; // false
这是因为每个String对象对是一个单独的对象,其总是只等于其自身。使用不严格相等运算符也是一样。
s1 == s2; // false
2、在ES5规范中,就像[],{}这样的对象直接量一样,程序运行时每次碰到RegExp直接量都会创建新对象。比如,如果在循环体中写var pattern = /s$/
,则每次遍历都会创建一个新的正则表达式对象。然而在ES3规范中一个正则表达式直接量会在执行到它时转换为一个RegExp对象,同一段代码的正则表达式直接量的每次运算都返回同一个对象。而ES5做了相反的规定。用下面这段代码做比较。
function getRE() {
var re = /[a-z]/;
re.foo = 'bar';
return re;
}
var reg = getRE();
re2 = getRE();
console.log(reg === re2); // 在ES3中返回true,在ES5中返回false
reg.foo = 'baz';
console.log(re2.foo); // 在ES3中返回'baz',在ES5中返回'bar'
显然ES5的规范更符合开发者的期望。
字符 | 匹配 |
---|---|
字母和数字字符 | 自身 |
\o | NUL字符 |
\t | 制表符(\u0009) |
\n | 换行符(\u000A) |
\v | 垂直制表符(\u000B) |
\f | 换页符(\u000C) |
\r | 回车符(\u000D) |
\xnn | 由十六进制数nn指定的拉丁字符 |
\uxxx | 由十六进制数xxxx指定的Unicode字符 |
\cX | 控制字符^X |
注:
由十六进制数nn指定的拉丁字符,例如:\x0A等价于\n 由十六进制数xxxx指定的Unicode字符:\u0009等价于\t 控制字符^X:\cJ等价于换行符\n 如果不记得哪些标点符号需要反斜杆转义,可以在每个标点符号前都加上反斜杆。
字符 | 匹配 |
---|---|
[...] | 方括号内任意字符 |
[^...] | 不在方括号内的任意字符 |
. | 除换行符和Unicode行终止符外的任意字符 |
\w | 任何ASCⅡ字符组成的单词,等价于[a-zA-Z0-9_] |
\W | 任何不是ASCⅡ字符组成的单词,等价于[^a-zA-Z0-9_] |
\s | 任何Unicode空白符 |
\S | 任何非Unicode空白符,注意\w和\S的不同 |
\d | 任何ASCⅡ数字,等价于[0-9] |
\D | 除了ASCⅡ数字之外的任何字符,等价于[^0-9] |
[\b] | 退格直接量 |
字符 | 匹配 |
---|---|
{n,m} | 匹配前一项至少n次,但不能超过m次 |
{n,} | 匹配前一项n次或多次 |
{n} | 匹配前一项n次 |
? | 匹配前一项0次或1次,也就是说前一项是可选的,等价于{0,1} |
+ | 匹配前一项1次或多次,等价于{1,} |
* | 匹配前一项0次或多次,等价于{0,} |
字符 | 匹配 |
---|---|
"竖线" | 选择,匹配的是该符号左边的子表达式或右边的子表达式 |
(...) | 组合,将几个项组合为一个单元,这个单元可通过“*”、“+”、“?”和"竖线"等符号修饰,而且可以记住和这个相匹配的字符串以供伺候的引用使用 |
(?:...) | 只组合,把项组合到一个单元,但不记忆与该组相匹配的字符 |
\n | 和第n个分组第一次匹配的字符相匹配,组是圆括号中的子表达式(也有可能是嵌套),组索引是从左到右的左括号数,“(?:”形式的分组不编码 |
字符 | 匹配 |
---|---|
^ | 匹配字符串的开头,在多行检索中,匹配一行的开头 |
$ | 匹配字符串的结尾,在多行检索中,匹配一行的结尾 |
\b | 匹配一个单词的边界,简而言之,就是位于字符\w和字符\W之间的位置,或位于字符\w和字符串的开头或结尾之间的位置(但需要注意的是在字符组内[\b]匹配的是退格符) |
\B | 匹配非单词边界的位置 |
(?=p) | 零宽正向先行断言,要求接下来的字符都与p匹配,但不能包括匹配p的那些字符 |
(?!p) | 零宽负向先行断言,要求接下来的字符不与p匹配 |
字符 | 匹配 |
---|---|
i | 执行不区分大小写的匹配 |
g | 执行一个全局匹配,简而言之,即找到所有的匹配,而不是在找到第一个之后就停止 |
m | 多行匹配模式,^匹配一行的开头和字符串的开头,$匹配行的结束和字符串的结束 |
方法 | 意义 |
---|---|
String.search() | 参数:一个正则表达式。返回:第一个与参数匹配的子串的起始位置,如果找不到,返回-1。不支持全局搜索,如果参数是字符串,会先通过RegExp构造函数转换成正则表达式。 |
String.replace() | 检索和替换。第一个参数:正则表达式,第二个参数:要进行替换的字符串,也可以是函数。设置了g修饰符,则替换所有匹配的子串,否则只替换第一个子串。通过在替换字符串中使用“$n”,可以使用子表达式相匹配的文本来替换字符。 |
String.match() | 参数:一个正则表达式。返回:一个由匹配结果组成的数组。设置g则返回所有匹配结果,否则数组的第一个元素是匹配的字符串,剩下的是圆括号中的子表达式,即a[n]中存放的是$n的内容。 |
String.split() | 参数:正则表达式或字符串。返回:子串组成的数组。 |
var pattern = new RegExp(arg1, arg2);
arg1: 正则表达式中两条斜杆之间的文本
arg2: 可选,指定修饰符:g,m,i
作用:动态创建正则表达式,例如待检索的字符串是由用户输入的。
属性 | 意义 |
---|---|
source | 只读字符串,包含正则表达式的文本。 |
global | 只读布尔值,是否带修饰符g |
ignoreCase | 只读布尔值,是否带修饰符i |
multiline | 只读布尔值,是否带修饰符m |
lastIndex | 可读写整数,如果带g修饰符,这个属性储存在整个字符串中下一次检索开始的位置,这个属性会被exec()和test()方法用到。 |
方法 | 意义 |
---|---|
exec() | 参数:字符串。在一个字符串中执行匹配检索,与String.macth()非全局检索类似,返回一个数组或null。 |
test() | 参数:字符串。返回true or false |
toString() | 转换成字符串形式 |
关于RegExp对象的属性和方法多说两句:
RegExp对象的属性index包含了发生匹配的字符位置,属性input引用的是正在检索的字符串。 当调用exec()或test()的正则表达式具有修饰符g时,它将把当前正则表达式对象的lastIndex属性设置为紧挨着匹配子串的字符位置。如果没发现任何匹配结果,lastIndex将重置为0。可以通过此特性反复调用exec()或test()来遍历字符串。
ES5中,正则表达式直接量的每次计算都会创建一个新的RegExp对象,每个新的RegExp对象具有各自的lastIndex属性,这势必会大大减少“残留”lastIndex属性对程序造成的意外影响。
常见的URL:http://hostname/path.html
当然,.htm或.shtml的结尾也很常见,或者干脆没有path部分,还包括http或https的协议头。
[-a-z0-9_.]
来匹配,再加上可能存在的端口号,所以再加上:
, 就成了[-a-z0-9_.:]
。[-a-z0-9_:@&?=+,.!/~*%$]
来匹配。注意,连字符必须放在字符组的开头,保证它是一个普通字符,而不是用来表示范围。var patternURL = /https?:\/\/[a-z0-9_.:]+\/[-a-z0-9_:@&?=+,.!/~*%$]*(\.(html|htm|shtml))?/
接下来,我们一步步地对URL进行分析。
我们可以将URL分为三个部分:
var patternURL = /^https?:\/\/([^/]+)(/.*)?$/
(:(\d)+)?
var patternURL = /^https?:\/\/([^/:]+)(:(\d)+)?(/.*)?$/
var patternHostname = /[a-z0-9]|[a-z0-9][-a-z0-9]*[a-z0-9]/i
var patternHostname =/^([a-z0-9]\.|[a-z0-9][-a-z0-9]{0,61}[a-z0-9]\.)(com|edu|gov|int|mil|net|org|biz|info|name|museum|coop|aero|[a-z][a-z])$/i
匹配HTML标签嘛,感觉很简单的样子,我们的第一反应可能是:var pattern = /<[^>]+>/
不过这样匹配可能存在的问题是:如果tag中含有>
,上面的正则就不能正常匹配了。如:
<input name=123 value=">" >
虽然上面这种HTML的写法很少(sha)见(bi),但确实合法的。因此,简单的<[^>]+>
就不能用了,需要想个聪明点的办法。
我们先来看一下HTML Tag中有什么规则:<...>
中能够出现
>
和引号之外的任何字符)因此我们可以使用/("[^"]*"|'[^']*')/
来匹配。
>
和引号之外的任意字符可以使用/[^'">]/
来匹配
现在可以得出匹配HTML Tag的正则表达式最终版!
var pattern = /<("[^"]*"|'[^']*'|[^'">])*>/
给这个正则表达式来点注释:
< # 开始的尖括号"<"
( # 任意数量的...
"[^"]*" # 双引号字符串
| # 或者是...
'[^']*' # 单引号字符串
| # 或者是...
[^'">] # "其他文本"
)* #
> # 结束的尖括号">"
需要注意的是,我们不用"+"
来修饰[^'">]
的原因是([^'">]+)*
可能会带来灾难性的后果。匹配次数呈指数级增长。比如:对于简单的目标字符串helloworld,是星号会迭代10次,每一次迭代中[^'">]+
匹配一个字符?还是星号迭代3次,内部的[^'">]+
分别匹配5、2、3个字符?或者2、3、1、4个字符?还是其他情况?这样会把正则引擎搞疯掉的啦!
其实匹配引号内字符串的最简单办法是用这个表达式:/"[^"]*"/
。
不过我们要容许其中包含转义的引号,例如:"we have a \"awesome\" world!"
。
下面进行任务分解:
不过由于转义之后的引号也能够出现的正文中,所以处理起来比较棘手哈。
我们还是以"we have a \"awesome\" world!"
为例子。如果JavaScript中有逆序环视(lookaround)可用,我们可以这样写:var pattern = /"([^"]|(?<=\\)")*"/
。
但是这个正则表达式无法匹配下面这两个无聊的例子:"/-|-\\" or "[^-^]"
我本来想匹配"/-|-\\"
,结果匹配的确是"/-|-\\" or "
。
匹配正文的思路:1、不是引号:由[^"]
匹配。2、是一个引号,而它左边又有一个反斜杆,那么这个引号也属于正文。使用逆序环视:/"([^"]|(?<=\\)")*"/
鉴于上面的例子,我们需要对var pattern = /"([^"]|(?<=\\)")*"/
进行修改!
第一个表达式的问题在于,我们把反斜杆认为只是用来转义引号的,其实反斜杆在字符串中可以用来转义任何字符。因此,我们要匹配的文本其实是开始引号和结束引号之间,包括转义字符和非引号的任何字符。得到:/"(\\.|[^"])*"/
不过!
上面的表达式还是会错误的匹配:"You need a new\"world\" haha.
中的"You need a new\"world\"
即使这并不是一个字符串。
因为,这个表达式一开始匹配到了引号之后的文本,如果找不到结束的引号,它就会回溯。而[^"]
匹配到了world\里的反斜杆后,之后的那个引号会被表达式认为是一个结束的引号。。。
继续改改改!
所以我们需要保证,字符串里的反斜杆不能以[^"]
方式匹配。要将[^"]
改为[^\\"]
上面的正则表达式使用了JavaScript正则表达式并不兹瓷的逆序环视,这里给出JavaScript支持的版本。
/(["'])(((\\.|[^\1\\])*)+)\1/
或者 /^(['"])(((\\['"])?([^\1])*)+)\1/
好了,由于本人笔力有限,关于JavaScript的正则表达式只能介绍到这里,感兴趣的同学可以去阅读犀牛书的第十章以及《精通正则表达式》这本书
相关推荐
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。