一、背景
工作上遇到一个这样的需求:
用正则表达式将一个字符串中的span标签替换为img标签,并将原span标签的内容放到img标签的src中,问题详细描述:点我
看到这个需求,我知道应该可以用正则表达式,可是由于之前没怎么用,一想到正则表达式就头大,一堆各种各样的特殊符号,似乎没有规律可循,有点难以理解。不过知道自己不能逃避,于是自己就去尝试怎么写这个正则表达式来解决我的需求,上述中提到的问题详细描述,大概就是我思考的过程,问题提出后立马有人解答,看完他们的答案后,惭愧,感觉到自己知识的欠缺,再不学习就老了(┬_┬)
二、正则表达式基础
2.1 元字符介绍
-
"":会匹配行或者字符串的起始位置,有时还会匹配整个文档的起始位置。
-
"$":$会匹配行或字符串的结尾。
-
"":不会消耗任何字符只匹配一个位置,常用于匹配单词边界 如:我想从字符串中"This is Regex"匹配单独的单词 "is" 正则就要写成:
"This is Regex".match(/is/);
"" 不会匹配is 两边的字符,但它会识别is 两边是否为单词的边界。 -
"d":匹配数字。
-
"w":匹配字母,数字,下划线。等价于'[A-Za-z0-9_]'。
-
"s":匹配空格。
-
".":匹配除了换行符以外的任何字符。
-
"[a-zA-Z]":字符组 匹配包含括号内元素的字符。
-
几种反义:改成大写,意思就与原来的相反。
如:
"W":匹配任何非单词字符。等价于'[^A-Za-z0-9_]'。
"[^abc]":匹配除了abc以外的任意字符。 -
字符转义:在正则表达式中元字符是有特殊的含义的,当我们要匹配元字符本身时,就需要用到字符转义,如:
/./.test("."); // true
2.2 量词
2.2.1 常用量词
- "*"(贪婪)重复零次或更多,贪婪量词会首先匹配整个字符串,尝试匹配时,它会选定尽可能多的内容,如果 失败则回退一个字符,然后再次尝试回退的过程就叫做回溯,它会每次回退一个字符,直到找到匹配的内容或者没有字符可以回退。如:
"aaaaaa".match(/a*/) // ["aaaaaa"]
- "?"(懒惰)重复零次或一次,懒惰量词使用另一种方式匹配,它从目标的起始位置开始尝试匹配,每次检查一个字符,并寻找它要匹配的内容,如此循环直到字符结尾处。如:
"aaaaaa".match(/a?/) // ["a"]
- "+"(占有)重复零次或更多次,占有量词会覆盖事个目标字符串,然后尝试寻找匹配内容 ,但它只尝试一次,不会回溯。如:
"aaaaaa".match(/a+/) // ["aaaaaa"]
- "{n}"重复n次;如:
"aaaaaa".match(/a{3}/) // ["aaa"]
- "{n,m}"重复n到m次;如:
"aaaaaa".match(/a{3,4}/) // ["aaaa"]
- "{n,}" 重复n次或更多次;如:
"aaaaaa".match(/a{3,}/) // ["aaaaaa"]
2.2.1 懒惰限定符
- "*?" 重复任意次,但尽可能少重复;如:
"aabab".match(/a.*?b/) // ["aab"]
为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权。 - "+?" 重复1次或更多次,但尽可能少重复,与上面一样,只是至少要重复1次。如:
"aabab".match(/a.+?b/) // ["aab"]
- "??" 重复0次或1次,但尽可能少重复。如:
"aabab".match(/a.??b/) // ["aab"]
- "{n,m}?" 重复n到m次,但尽可能少重复。如:
"aaa".match(/a{1,3}?/) // ["a"]
- "{n,}?" 重复n次以上,但尽可能少重复。如:
"aaa".match(/a{1,}?/) // ["a"]
2.2.2 处理选项
- javascript中正则表达式支持的正则表达式有三个,g、i、m,分别代表全局匹配、忽略大小写、多行模式。三种属性可以自由组合共存。
- 在默认的模式下,元字符 ^ 和 $ 分别匹配字符串的开头和结尾处,模式 m 改变了这俩元字符的定义,让他们匹配一行的开头和结尾。
三、正则进阶
3.1 捕获分组
正则表达式一个最重要的特性就是将匹配成功的模式的某部分进行存储供以后使用这一能力。对一个正则表达式模式或部分模式两边添加圆括号将导致这部分表达式存储到一个临时缓冲区中。(可以使用非捕获元字符 '?:', '?=', 或 '?!' 来忽略对这部分正则表达式的保存。)
所捕获的每个子匹配都按照在正则表达式模式中从左至右所遇到的内容存储。存储子匹配的缓冲区编号从 1 开始,连续编号直至最大 99 个子表达式。每个缓冲区都可以使用 ' ' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
后向引用一个最简单,最有用的应用是提供了确定文字中连续出现两个相同单词的位置的能力。
举个例子:
/([a-zA-Z]+)s+1/.exec(" asd sf hello hello asd"); //["hello hello", "hello"]
解释这个例子:
1、([a-zA-Z]+) 是一个捕获分组,它捕获所有的单词,
" asd sf hello hello asd".match(/([a-zA-Z]+)/g) // ["asd", "sf", "hello", "hello", "asd"]
注:加上/g这个处理选项是便于我理解,没有这个选项的时候,只输出第一个单词asd。
2、s加了一个空格限制条件,所以最后一个单词被排除,
" asd sf hello hello asd".match(/([a-zA-Z]+)s/g) \ ["asd ", "sf ", "hello ", "hello "]
3、"1"后向引用,
" asd sf hello hello asd".match(/([a-zA-Z]+)s+1/g) \ ["hello hello"]
说实话,这个例子花了我很长时间去理解,有一点点想通,感觉这个概念看起来容易,写起来并不容易啊。
3.2 捕获分组常有的用法(断言)
- "(exp)" 匹配exp,并捕获文本到自动命名的组里;如:
/(hello)sworld/.exec("asdadasd hello world asdasd") // ["hello world", "hello"]
- "(?:exp)" 匹配exp,不捕获匹配的文本,也不给此分组分配组号;如:
/(?:hello)sworld/.exec("asdadasd hello world asdasd") // ["hello world"]
- "(?=exp)" 用来捕获exp前面的字符,分组中的内容不会被捕获,也不分配组号;如:
/hellos(?=world)/.exec("asdadasd hello world asdasd") // ["hello "]
- "(?!exp)" 捕获后面不是exp的字符,同样不捕获分组的内容,也不分配组号;如:
/hellos(?!world)/.exec("asdadasd hello world asdasd") //null
world改变一下:
/hellos(?!world)/.exec("asdadasd hello wosrlds asdasd") //["hello "]
- "(?<!exp)" 匹配前面不是exp的位置;如:
/(?!<d)123/.exec("abc123 ") // ["123"]
四、Javascript中正则表达式的使用
在JavaScript中定义一个正则表达式语法为:
var reg=/hello/ 或者 var reg=new RegExp("hello")
接着列举一下JavaScript中可以使用正则表达式的函数,并简单介绍一下这些函数的作用。
4.1 String.prototype.search方法
用来找出原字符串中某个子字符串首次出现的索引index,没有则返回-1。可以在官方文档中了解更多。
"abchello".search(/hello/); // 3
4.2 String.prototype.replace方法
用来替换字符串中的子串。简单例子:
"abchello".replace(/hello/,"hi"); // "abchi"
在官方文档中有提到:
如果第一个参数是 RegExp对象,那么替换字符串可以插入特殊变量名$n,n是个小于100的非负整数,表示插入第 n 个括号匹配的字符串。
所以我在文中一开始提到的需求就可以用
str.replace(/<span>(.*?)</span>/g, '<img src="$1"/>')
[$1表示/(.?)</span>/g中的“(.?)”)匹配的字符串]
答案来解答。
4.3 String.prototype.split方法
用来分割字符串
"abchelloasdasdhelloasd".split(/hello/); //["abc", "asdasd", "asd"]
4.4 String.prototype.match方法
用来捕获字符串中的子字符串到一个数组中。默认情况下只捕获一个结果到数组中,正则表达式有”全局捕获“的属性时(定义正则表达式的时候添加参数g),会捕获所有结果到数组中。
"abchelloasdasdhelloasd".match(/hello/); //["hello"]
"abchelloasdasdhelloasd".match(/hello/g); //["hello","hello"]
4.5 RegExp.prototype.exec方法
和字符串的match方法类似,这个方法也是从字符串中捕获满足条件的字符串到数组中,但是也有两个区别。
1、exec方法一次只能捕获一份子字符串到数组中,无论正则表达式是否有全局属性
/hello/g.exec("abchelloasdasdhelloasd"); // ["hello"]
2、正则表达式对象(也就是JavaScript中的RegExp对象)有一个lastIndex属性,用来表示下一次从哪个位置开始捕获,每一次执行exec方法后,lastIndex就会往后推,直到找不到匹配的字符返回null,然后又从头开始捕获。 这个属性可以用来遍历捕获字符串中的子串。
var reg=/hello/g;
reg.lastIndex; //0
reg.exec("abchelloasdasdhelloasd"); // ["hello"]
reg.lastIndex; //8
reg.exec("abchelloasdasdhelloasd"); // ["hello"]
reg.lastIndex; //19
reg.exec("abchelloasdasdhelloasd"); // null
reg.lastIndex; //0
4.6 RegExp.prototype.test方法
用来测试字符串中是否含有子字符串
/hello/.test("abchello"); // true
五、总结
总算是对正则表达式了解了一些,要熟练掌握还需后面多多实践_
参考资料:
1.http://www.cnblogs.com/zery/p/3438845.html
2.http://www.cnblogs.com/tzyy/p/4927476.html
3.http://www.codeyyy.com/regex/index.html