正则学习笔记 主要是C#或Javascript --不错

来自:http://blog.csdn.net/youbl/article/details/8985086

概念相关笔记

这是俺学习正则时的一些正则学习笔记
可能理解会有些不对,谁看到谁提哈,嘿嘿

1、容易混淆的单行模式和多行模式:
单行模式只影响.(小数点)的匹配,关闭单行模式,.匹配换行以外的任意字符;开启单行模式,.匹配任意字符
多行模式只影响^和$的匹配,关闭多行模式,^只能匹配字符串开头,$只能匹配字符串结尾;
开启多行模式,^匹配字符串开头或行的开头,$匹配字符串结尾或行的结尾
因为正则发展的历史原因,造成这2个概念好像是相反的概念,实际这2个概念是没有任何关系的2个概念

2、全局模式(C#没有,js有):关闭时,只匹配一次;开启时,匹配全部字符串,
在js中,关闭全局模式,等效于C#中的Match方法;开启全局模式,等效于C#中的Matches方法

3、贪婪模式与懒惰模式:举例说明:有字符串:0<div>a1</div>b1<div>c1</div><div>d1</div>9
贪婪模式的正则:<div>.*</div>,只有一个匹配结果:<div>a1</div>b1<div>c1</div><div>d1</div>
懒惰模式的正则:<div>.*?</div>,有3个匹配结果,分别是:<div>a1</div> <div>c1</div> <div>d1</div>
注:贪婪模式的原理是匹配优先,而懒惰模式的原理是忽略优先,比如:
字符串 abd
贪婪模式正则:ab?c 在匹配时,会先尝试进行ab匹配,再比对c,不匹配了,进行回溯,进行ac的匹配
懒惰模式正则:ab??c 在匹配时,会先尝试进行ac匹配,不匹配了,进行回溯,进行abc的匹配

4、非回溯匹配(也叫固化分组):(?>):举例说明:字符串:张三是中国人,李四是中国人,王五是韩国人
正则:(.*)中国人,因为正则引擎的贪婪特性,.*第一次扫描时会匹配全部字符串,发现后面没有字符了,不能匹配正则里的“中国人”,于是把.*的匹配往前递推一个,发现“国”也不能匹配正则里的“中国人”,于是再把.*的匹配往前递推,一直推到“张三是中国人,李四是”,此时匹配到了“中国人”,于是.*匹配的结果就是:张三是中国人,李四是这里说的往前递推就是回溯
把正则改为:(?>.*)中国人 匹配就会失败,因为正则式里指定了.*不允许回溯,所以.*第一次扫描时会匹配全部字符串,再往后扫描时匹配不到,就直接返回了,而不会往前递推。注意:非回溯组也是非捕获组,就是这个括号里的值不会被捕获
之所以有这个非回溯,是因为在正则表达式引擎时,回溯是很耗资源和时间的,要尽量避免回溯,比如:
字符串:<a href="http://www.beinet.cn">这是我的网站</a>,要用正则匹配里面的url和文本,可以用下面2个正则,都可以实现:
<a href="(.+?)">(.+?)</a>
<a href="([^"]+)">([^<]+)</a>
但是第一个正则,在匹配时会有回溯,比如href是懒惰匹配,这个.+?会先匹配h,然后看后面是不是",不是,再递推下一个字符t,一直递推19次
而第二个正则,直接就匹配到"前面,不存在回溯,所以在写正则时,要尽量使用没有回溯,或者回溯少的正则

其它笔记

1、\b:表示单词的起始或结束
\B:表示非单词边界(不在单词的开始或结束)
^:表示字符串的起始位置,指定多行模式时,表示行的起始位置
$:表示字符串的结束位置,指定多行模式时,表示行的结束位置

2、反向引用:\1这样的转义数字,代表前面捕获的内容,如果我们想匹配重复的单词,就可以用这种转义数字
举例:this isa a this this a a file list file filea,我们要找出其中重复的单词,可以用正则:
\b([a-z]+)\b \1\b 来匹配,\1表示第一个括号里的内容,\2表示第2个,如此类推

3、捕获的顺序是按左括号的出现顺序,从1开始顺序递增
注:捕获就是把括号里的内容压入堆栈
例如:([+-])?(\d+(\.\d+)?)(.*)
([+-])为捕获的第一个内容,通常为$1,
C#中可以用Match.Groups[0].Value来得到捕获的值(在正则中可以用\1反向引用,以下类推)
也可以用Match.Result("$1")来得到捕获的值
(\d+(\.\d+)?)为捕获的第二个内容,通常为$2
而$2中的(\.\d+)为捕获的第三个内容,通常为$3
最后的(.*)为捕获的第四个内容,通常为$4
注意:如果补获组进行了命名,则未命名的第1个左括号为$1,未命名的第2个左括号为$2,以此类推,直到没有未命名的补获为止,再开始按顺序推算有命名的补获组

4、如果对某个括号里的内容不想进行捕获,可以使用?:
例如:例3修改为:([+-])?(\d+(?:\.\d+)?)(.*)
例3里的$3就变成了(.*),而$4就不存在了
技巧:如果不想加?:,可以在匹配时增加选项:RegexOptions.ExplicitCapture,这个选项只会捕获用(?<name>...)的组,但是如果指定了反射引用时,必须对引用的的捕获显式命名,比如正则:<([^\s]+)></\1>,如果指定RegexOptions.ExplicitCapture时会报错

5、替换时保留匹配内容,例如字符串:http://beinet.cn/
要替换成超链接形式<a href=‘http://beinet.cn/’>beinet.cn</a>,可以用C#语句:
Regex.Replace(@"http://beinet.cn/",@"(http://(.*)/)",@"<a href='$1'>$2</a>");

Regex.Replace(@"http://beinet.cn/",@"(?<url>http://(?<host>.*)/)",@"<a href='${url}'>${host}</a>");

6、\s匹配空白字符,包括:空格、Tab、换行、回车,等价于 [\t\r\n ]
\S匹配上述4个字符以外的其它所有字符

所以:[\s\S] 就可以匹配任意字符了

7、\w :匹配包括下划线的任何单词字符,等价于 [A-Za-z0-9_]
\W :匹配任何非单词字符,等价于 [^A-Z a-z 0-9_]

所以:[\w\W] 也可以匹配任意字符了

8、\d :匹配所有数字,一般等价于 [0-9] 注:在C#里,默认情况下也匹配全角的0-9
\D :匹配任何非数字
所以:[\d\D] 也可以匹配任意字符了

9、.(句点字符。): 匹配除 \n 以外的任何字符。
注意1:[.\n]并不能匹配任意字符,因为在[ ]里,.只是代表自己,不匹配其它字符
所以要匹配任意字符,请参考:5,6,7
注意2:如果指定正则选项为Singleline,则此时.匹配任意字符了

10、\nnn:匹配一个3位的8进制Ascii字符,如\103匹配大写C字符
\xnn:匹配一个2位的16进制Ascii字符,如\x43匹配大写C字符
\unnnn:匹配一个4位的16进制Unicode字符
\cV:匹配一个控制字符,如\cV匹配Ctrl-V
注意1:为了跟反向引用区分开,表示8进制字符时,比如\43,请写成\043

11、零宽度断言:有的地方称之为环视,或者预搜索,或声明,就是根据表达式匹配一个位置,而不是匹配字符,举例:
有字符串为:abcdefghijklmnopqrstuvwxyz
正声明?=
(?=opq):匹配n与o中间的位置,此时:mn(?=opq),就可以匹配到mn,而m(?=opq)匹配不到东西
(?=opq)op,就可以匹配到op,而(?=opq)p匹配不到东西
举例:Languages have: Java C#.Net C++ Javascript VB.Net JScript.Net Pascal
正则:\S+(?=\.Net) 将得到结果:C# VB JScript

逆向正声明?<=
(?<=opq):匹配q与r中间的位置,此时:pq(?<=opq),就可以匹配到pq,而p(?<=opq)匹配不到东西
(?<=opq)rst,就可以匹配到rst,而(?<=opq)st匹配不到东西
注:Javascript不支持逆向正声明
举例:
名单:张三 李四 张建四 王五
正则:(?<=张)\S+ 将得到结果:三 建四
负声明?|
(?!opq):匹配所有位置,除了n与o中间的位置,此时:[a-z](?!opq),就可以匹配到除n以外的所有字符,而n(?!opq)匹配不到东西
(?!opq)[a-z],就可以匹配到除o以外的所有字符,而(?!opq)o匹配不到东西
举例:123A 456c 789 111C
正则:\d{3}(?![A-Z]) 将得到结果:456 789
逆向负声明?<!
(?<!opq):匹配所有位置,除了q与r中间的位置,此时:[a-z](?<!opq),就可以匹配到除q以外的所有字符,而q(?<!opq)匹配不到东西
(?<!opq)[a-z],就可以匹配到除r以外的所有字符,而(?<!opq)r匹配不到东西
注:Javascript不支持逆向负声明
举例:
123A 456C 789 111C
正则:(?<!1)\d{2}[A-Z] 将得到结果:56C
综合应用举例:
\b\w+(?=o)o\b:匹配所有以o结尾的单词
Regex.Replace("I have 1234567Yuan",@"(?<=\d)(?=(?:\d{3})+(?!\d))",","):替换字符串里的数字为科学计数法(即3位数字一个逗号)
上面的是C#,Javascript因为不支持逆向环视,所以要用:'123456'.replace(/(\d)(?=(?:\d{3})+(?!\d))/g,"$1,")

12、决策(也叫平衡组)是正则里的3目运算表达式,形如:(?(exp)yes|no),如果exp成立,就匹配yes,否则匹配no
(?!)表示返回匹配失败,如:(\d)(?(1).|(?!))可以匹配以数字开头的任意2个字符
举例1:
字符串:1a cb 3a 5c 3b 正则:(?(\d)\da|b) 可以匹配到结果:1a b 3a b
需要注意的是yes表达式是\da,如果是正则:(?(\d)a|b) 将只会匹配到:b b,
因为expression成立时,是匹配到这个expression的位置,后面的yes也必须要包含这个expression
当然:(?(\d)\wa|b)的匹配结果也是可以的,因为\w包含了\d
对于字符串a1234,正则(?(?<!a)\d\d|\d)可以匹配到1 和 23
举例2:引用前面的条件
字符串:10-12 z0-az 11-sd 正则:(\d)?(0)-(?(1)\d\d|[a-z][a-z]) 后面的?(1),表示前面的第1 个捕获如果匹配时,用yes匹配,否则用no匹配
这个正则可以匹配到:10-12 0-az
参考:http://www.jb51.cc/article/p-swvxogiu-ry.html
http://www.cnblogs.com/luckcs/articles/2212996.html

13、正则表达式选项,(?i:)指定括号内的匹配忽略大小写,比如正则:(?i:a) 表示匹配a或A,而不管是否指定了RegexOptions.IgnoreCase选项
(?n:):指定只有显式命名或编号的组才进行捕获,类似于RegexOptions.ExplicitCapture
(?x:):消除模式中的非转义空白并启用由 # 标记的注释,类似于RegexOptions.IgnorePatternWhitespace
(?m:):指定使用多行模式,类似于RegexOptions.Multiline
(?s:):指定使用单行模式,类似于RegexOptions.Singleline
注:选项可以叠加,比如:(?is:) 表示单行模式,忽略大小写

14、应用 | 时要注意,正则引擎总是选择第一个选项进行匹配,无法匹配时才考虑第2个选项,然后第3个,比如:
字符串:abcd 正则:a|ab 只匹配到 a 而正则:ab|a 则匹配到 ab

15、Javascript提取匹配中的内容举例,下面是在Html里循环提取超链接的Href和链接文本
var a = /<a\s+[^>]*href="([^"\s]*)"[^>]*>([\s\S]*?)<\/a>/ig;
while(a.test(html)){// 第二个test会从第一个test的lastIndex+1处开始匹配
alert(RegExp.$1);
alert(RegExp.$2);
}

正则举例

1、需求:如果小数在2位以内,就保持不变,如果有第3位小数,且第3位小数不是0,那也保留,如果是0就不保留,第3位以后的数字全部替换掉
比如:string str = 1.23=》1.23、1.234=1.234、1.230=》1.23、1.2345678=》1.234
此时,Regex.Replace(str,@"(\.\d\d[1-9]?)\d*","$1")就可以实现,但是对于1.23和1.234,替换操作浪费了一点时间,因为结果相当于用23替换成23
有效率一点的做法是把正则改成:(\.\d\d(?>[1-9]?))\d+
注意里面的固化分组,如果不使用固化分组的正则:(\.\d\d[1-9]?)\d+ 在匹配1.625时,因为\d+至少要匹配一个数字,而[1-9]?可以不匹配,所以导致回溯,\d+匹配了5,导致替换结果成了1.62,与需求不符,所以这里要用固化分组,避免回溯

2、需求:匹配出字符串里的日期,比如:string str = January 31 我们要得到后面的日期31
一般我们会用正则:(0?[1-9]|[12]\d|3[01]) 这个多选组合,0?[1-9]匹配01-09或1-9;[12]\d匹配10-29;3[01]匹配30-31,应该是没错的但是匹配的第一个
结果是3,而不是31 这是因为正则引擎按顺序测试组合,0?[1-9]可以匹配3,所以错误结果出现了
正确的正则应该是把能匹配最短数字的0?[1-9]放到最后,变成:([12]\d|3[01]|0?[1-9]) 就OK了
或者使用下列正则之一:
(31|[123]0|[012]?[1-9]) (0[1-9]|[12]\d?|3[01]?|[4-9])

3、需求:匹配双引号和里面的内容,内容里允许出现\" 和\\这样的转义
如果内容不包含引号,那正则就是:"[^"]*"
如果加上允许转义的双引号时,我们先用逆序环视,正则变成:"([^"]|(?<=\\)")*",这个表示式可以匹配 "aa\"bb" 这样的文本,但是对于"aa\\" and "bb",它的匹配结果是错误的,因为它把转义的\\后面的这个斜杠去环视了,所以这个正则不能用
再改用:"(\\.|[^"])*",就是匹配\和一个字符,或非双引号,对于上面的这回可以匹配了,不过,对于没有结束双引号的字符串:"aa\"bb,它又匹配到了"aa\",因为正则引擎的回溯到\"时,[^"]能匹配前面的\,于是就返回了匹配成功
所以最终的正则应该是:"([^"\\]|\\.)*" 或者使用固化分组:"(?>([^"]|\\.)*)"
注:这个正则顺序交换一下,变成:"(\\.|[^"\\])*" 也是可以的,但是这个正则回溯比不交换前多,参见下图

4、需求:替换字符串前后的空白(C#的Trim方法已经可以实现,但是js没有这个功能)
在网上最常见的作法是用正则: (^\s*)|(\s*$) 可以搜索 Javascript trim,得到一大堆的类似结果如下:
String.prototype.trim= function(){return this.replace(/(^\s*)|(\s*$)/g,"");}
这个当然没有问题,但是这个正则是可以改进的,首先,用\s*,这样也可以匹配空,没有空白的字符串也会进行2次替换,
而且正则里有2个捕获,而实际上捕获没有使用到,
所以比较好的作法是把*改成+,并去掉括号,用:String.prototype.trim= function(){return this.replace(/^\s+|\s+$/g,"");}
注:虽然这点改进很小,但是如果不是替换成"",那结果就出错了,并且在做任何工作时,都想到这么一点点,那总的提升效率还是很多的

5、需求:匹配HTML标签,允许标签中的属性值包含<或>,例如:<input value="a>bc" type="text">
如果没有后面那个要求,那么匹配HTML标签,就是简单的:<[^>]+>
根据要求,我们可以知道,属性值是包含在单引号或双引号里的,所以可以得到下面正则:
<("[^"]*"|'[^']*'|[^'">])*>

6、需求:匹配嵌套div标签的最内层,例如:<div><div>2<div>3</div></div>1<div>2<div>3<a>3</a>3</div></div></div>,取出3的div
首先自然是两端的正则:<div[^>]*>.*?</div>,这个得到结果:<div><div>2<div>3</div>
那么要求在中间不能出现<div字样,用(?:(?!<div).)匹配前面不等于<div的任意一个字符,得到最终正则如下:
<div[^>]*>(?:(?!<div).)*?</div>

7、需求:匹配嵌套div标签的最外层
 div>1<div>2<div>3<div>4</div>5 匹配到<div>4</div>
div>1<div>2<div>3<div>4</div>5</div> 匹配到<div>3<div>4</div>5</div>
div>1<div>2<div>3<div>4</div>5</div>6</div>7</div>8</div> 匹配到<div>2<div>3<div>4</div>5</div>6</div>
首先自然是两端的正则:<div> 和 </div>,
然后(?<o><div>)|(?<-o></div>)表示匹配div和相应的结束div,<o>表示把捕获压入堆栈,<-o>表示取出堆栈,堆栈没有数据就表示匹配失败,第三部分(?:(?!</?div)[\s\S])表示不包含div的任意字符,最后还有一块(?(o)(?!))表示如果堆栈中还有div,匹配失败
最终正则如下:
<div>((?<o><div>)|(?<-o></div>)|(?:(?!</?div)[\s\S]))*(?(o)(?!))</div>

8、需求:匹配有效的物理路径,如 c:\ d:/abc/ddd.txt e:\\\\abc////\\kk.exe(注:连续的\或/都被Windows认为是一个\,所以有效)
物理路径自然要以盘符开头,所以正则开始是:^[a-zA-Z]:[\\/]+
接着是后面的子目录,目录或文件名按Windows规定,不允许出现<>/\|:"*? 以及 回车换行共11个字符,所以匹配子目录的正则是:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+,因为可能有多级子目录,也可能没有子目录,所以匹配全部子目录的正则就是:(?:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+)*,最后完整的正则就是:
^[a-zA-Z]:[\\/]+(?:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+)*[^\<\>\/\\\|\:""\*\?\r\n]*$

9、需求:输入6~20位的密码,要求必须是大写字母、小写字母和数字的组合
完整的正则是:
^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])[a-zA-Z0-9]{6,20}$
另外一个使用平衡组的正则:
^(?:([0-9])|([a-z])|([A-Z])){6,20}(?(1)|(?!))(?(2)|(?!))(?(3)|(?!))$

10、需求:匹配HTML的a标签里的href,要求支持单引号、双引号或无引号的可能 可能的数据有:<a href='xxx' target='_self'> <a href=xxx target='_self'> <a href="xxx" target='_self'><a href="javascript:alert('1')"><a href='javascript:alert("1")'> 首先自然是匹配到href的正则:<a\s[^>]*href=,接着匹配引号,因为可能没引号,因此是:(['"])?,后面要判断出现了引号没有,用一个三目运算符:(?(1)yes|no) 如果前面出现了引号,用正则:(?:(?!\1).)*\1,表示不等于前面引号的多个字符+前面的引号(\1表示引用) 完整的正则是(当然这个正则没有考虑更复杂的情况,比如<a href="javascript:alert(\"1\")">,这个等你来扩展吧): <a\s[^>]*href=(['"])?(?(1)((?:(?!\1).)*)\1|([^\s>]*))

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


jquery.validate使用攻略(表单校验) 目录 jquery.validate使用攻略1 第一章&#160;jquery.validate使用攻略1 第二章&#160;jQuery.validate.js API7 Custom selectors7 Utilities8 Validato
/\s+/g和/\s/g的区别 正则表达式/\s+/g和/\s/g,目的均是找出目标字符串中的所有空白字符,但两者到底有什么区别呢? 我们先来看下面一个例子: let name = &#39;ye wen jun&#39;;let ans = name.replace(/\s/g, &#39;&#3
自整理几个jquery.Validate验证正则: 1. 只能输入数字和字母 /^[0-9a-zA-Z]*$/g jQuery.validator.addMethod(&quot;letters&quot;, function (value, element) { return this.optio
this.optional(element)的用法 this.optional(element)是jquery.validator.js表单验证框架中的一个函数,用于表单控件的值不为空时才触发验证。 简单来说,就是当表单控件值为空的时候不会进行表单校验,此函数会返回true,表示校验通过,当表单控件
jQuery.validate 表单动态验证 实际上jQuery.validate提供了动态校验的方法。而动态拼JSON串的方式是不支持动态校验的。牺牲jQuery.validate的性能优化可以实现(jQuery.validate的性能优化见图1.2 jQuery.validate源码 )。 也可
自定义验证之这能输入数字(包括小数 负数 ) &lt;script type=&quot;text/javascript&quot;&gt; function onlyNumber(obj){ //得到第一个字符是否为负号 var t = obj.value.charAt(0); //先把非数字的都
// 引入了外部的验证规则 import { validateAccountNumber } from &quot;@/utils/validate&quot;; validator.js /*是否合法IP地址*/ export function validateIP(rule, value,cal
VUE开发--表单验证(六十三) 一、常用验证方式 vue 中表单字段验证的写法和方式有多种,常用的验证方式有3种: data 中验证 表单内容: &lt;!-- 表单 --&gt; &lt;el-form ref=&quot;rulesForm&quot; :rules=&quot;formRul
正则表达式 座机的: 例子: 座机有效写法: 0316-8418331 (010)-67433539 (010)67433539 010-67433539 (0316)-8418331 (0316)8418331 正则表达式写法 0\d{2,3}-\d{7,8}|\(?0\d{2,3}[)-]?\d
var reg = /^0\.[1-9]{0,2}$/;var linka = 0.1;console.log (reg.test (linka)); 0到1两位小数正则 ^(0\.(0[1-9]|[1-9]{1,2}|[1-9]0)$)|^1$ 不含0、0.0、0.00 // 验证是否是[1-10
input最大长度限制问题 &lt;input type=&quot;text&quot; maxlength=&quot;5&quot; /&gt; //可以 &lt;input type=&quot;number&quot; maxlength=&quot;5&quot; /&gt; //没有效
js输入验证是否为空、是否为null、是否都是空格 目录 1.截头去尾 trim 2.截头去尾 会去掉开始和结束的空格,类似于trim 3.会去掉所有的空格,包括开始,结束,中间 1.截头去尾 trim str=str.trim(); // 强烈推荐 最常用、最实用 or $.trim(str);
正则表达式语法大全 字符串.match(正则):返回符合的字符串,若不满足返回null 字符串.search(正则):返回搜索到的位置,若非一个字符,则返回第一个字母的下标,若不匹配则返回-1 字符串.replace(正则,新的字符串):找到符合正则的内容并替换 正则.test(字符串):在字符串中
正整数正则表达式正数的正则表达式(包括0,小数保留两位): ^((0{1}.\d{1,2})|([1-9]\d.{1}\d{1,2})|([1-9]+\d)|0)$正数的正则表达式(不包括0,小数保留两位): ^((0{1}.\d{1,2})|([1-9]\d.{1}\d{1,2})|([1-9]+
JS 正则验证 test() /*用途:检查输入手机号码是否正确输入:s:字符串返回:如果通过验证返回true,否则返回false /function checkMobile(s){var regu =/[1][3][0-9]{9}$/;var re = new RegExp(regu);if (r
请输入保留两位小数的销售价的正则: /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/ 1.只能输入英文 &lt;input type=&quot;text&quot; onkeyup=&quot;value
判断价格的正则表达式 价格的正则表达式 /(^[1-9]\d*(\.\d{1,2})?$)|(^0(\.\d{1,2})?$)/; 1 解析:价格符合两种格式 ^ [1-9]\d*(.\d{1,2})?$ : 1-9 开头,后跟是 0-9,可以跟小数点,但小数点后要带上 1-2 位小数,类似 2,2
文章浏览阅读106次。这篇文章主要介绍了最实用的正则表达式整理,比如校验邮箱的正则,号码相关,数字相关等等,本文给大家列举的比较多,需要的朋友可以参考下。_/^(?:[1-9]d*)$/ 手机号
文章浏览阅读1.2k次。4、匹配中的==、an==、== an9、i9 == "9i"和99p==请注意下面这部分的作用,它在匹配中间内容的时候排除了说明:当html字符串如下时,可以匹配到两处,表示匹配的字符串不包含and且不包含空白字符。说明:在上面的正则表达式中,_gvim正则表达式匹配不包含某个字符串
文章浏览阅读897次。【代码】正则表达式匹配a标签的href。_auto.js 正则匹配herf