正则表达式从原理到实战全面学习小结
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">正则是啥?</a></li><li><a href="#_label1">简单字符</a></li><li><a href="#_label2">转义字符</a></li><li><a href="#_label3">字符集和</a></li><li><a href="#_label4">量词</a></li><li><a href="#_label5">字符边界</a></li><li><a href="#_label6">选择表达式</a></li><li><a href="#_label7">分组与引用</a></li><li><a href="#_label8">预搜索</a></li><li><a href="#_label9">修饰符</a></li><li><a href="#_label10">图形化工具</a></li><li><a href="#_label11">JavaScript中的正则</a></li><li><a href="#_label12">RegExp#test</a></li><li><a href="#_label13">RegExp#exec</a></li><li><a href="#_label14">String#search</a></li><li><a href="#_label15">String#match</a></li><li><a href="#_label16">String#split</a></li><li><a href="#_label17">String#replace</a></li><li><a href="#_label18">RegExp</a></li><li><a href="#_label19">实例属性</a></li><li><a href="#_label20">实战实例</a></li><li><a href="#_label21">总结</a></li></ul></div><p>正则表达式,名字听上去就没有吸引力,我发现很多前端对正则表达式了解不深,甚至有些惧怕,每次能够运行全凭运气,更有甚者完全靠复制粘贴。</p><p>正则表达式其实并不难,语法就那么多,而且一旦掌握在某些时候能够给解决问题提供捷径,更重要的是面试可能会被问到,要是不会那就尴尬了。</p>
<p>本文全面介绍正则表达式的语法知识,全面介绍JavaScript中正则表达式的API,通过实战,希望能够帮助大家全面学习,并啃下前端的难题。</p>
<p class="maodian"><a name="_label0"></a></p><h2>正则是啥?</h2>
<p>下面是我对正则的理解:</p>
<blockquote><p>正则就是用有限的符号,表达无限的序列,殆已!</p></blockquote>
<p>正则表达式的语法一般如下(js),两条斜线中间是正则主体,这部分可以有很多字符组成;<code>i</code>部分是修饰符,<code>i</code>的意思表示忽略大小写</p>
<div class="jb51code"><pre class="brush:bash;">/^abc/i</pre></div>
<p>正则定义了很多特殊意义的字符,有名词,量词,谓词等,下面逐一介绍</p>
<p class="maodian"><a name="_label1"></a></p><h2>简单字符</h2>
<p>没有特殊意义的字符都是简单字符,简单字符就代表自身,绝大部分字符都是简单字符,举个例子</p>
<div class="jb51code"><pre class="brush:bash;">/abc/ // 匹配 abc
/123/ // 匹配 123
/-_-/ // 匹配 -_-
/海镜/ // 匹配 海镜</pre></div>
<p class="maodian"><a name="_label2"></a></p><h2>转义字符</h2>
<p><code>\</code>是转义字符,其后面的字符会代表不同的意思,转义字符主要有三个作用:</p>
<p>第一种,是为了匹配不方便显示的特殊字符,比如换行,tab符号等</p>
<p>第二种,正则中预先定义了一些代表特殊意义的字符,比如<code>\w</code>等</p>
<p>第三种,在正则中某些字符有特殊含义(比如下面说到的),转义字符可以让其显示自身的含义</p>
<p>下面是常用转义字符列表:</p>
<table><tbody><tr><td>\n</td><td>匹配换行符</td></tr><tr><td>\r</td><td>匹配回车符</td></tr><tr><td>\t</td><td>匹配制表符,也就是tab键</td></tr><tr><td>\v</td><td>匹配垂直制表符</td></tr><tr><td>\x20</td><td>20是2位16进制数字,代表对应的字符</td></tr><tr><td>\u002B</td><td>002B是4位16进制数字,代表对应的字符</td></tr><tr><td>\u002B</td><td>002B是4位16进制数字,代表对应的字符</td></tr><tr><td>\w</td><td>匹配任何一个字母或者数字或者下划线</td></tr><tr><td>\W</td><td>匹配任何一个字母或者数字或者下划线以外的字符</td></tr><tr><td>\s</td><td>匹配空白字符,如空格,tab等</td></tr><tr><td>\S</td><td>匹配非空白字符</td></tr><tr><td>\d</td><td>匹配数字字符,0~9</td></tr><tr><td>\D</td><td>匹配非数字字符</td></tr><tr><td>\b</td><td>匹配单词的边界</td></tr><tr><td>\B</td><td>匹配非单词边界</td></tr><tr><td>\\</td><td>匹配\本身</td></tr></tbody></table>
<p class="maodian"><a name="_label3"></a></p><h2>字符集和</h2>
<p>有时我们需要匹配一类字符,字符集可以实现这个功能,字符集的语法用<code>[``]</code>分隔,下面的代码能够匹配a或b或c</p>
<div class="jb51code"><pre class="brush:bash;"></pre></div>
<p>如果要表示字符很多,可以使用<code>-</code>表示一个范围内的字符,下面两个功能相同</p>
<div class="jb51code"><pre class="brush:bash;">
</pre></div>
<p>在前面添加<code>^</code>,可表示非的意思,下面的代码能够匹配<code>a``b``c</code>之外的任意字符</p>
<div class="jb51code"><pre class="brush:bash;">[^abc]</pre></div>
<p>其实正则还内置了一些字符集,在上面的转义字符有提到,下面给出内置字符集对应的自定义字符集</p>
<ul><li>. 匹配除了换行符(\n)以外的任意一个字符 = [^\n]</li><li>\w = </li><li>\W = [^0-9a-zA-Z_]</li><li>\s = [ \t\n\v]</li><li>\S = [^ \t\n\v]</li><li>\d = </li><li>\D = [^0-9]</li></ul>
<p class="maodian"><a name="_label4"></a></p><h2>量词</h2>
<p>如果我们有三个苹果,我们可以说自己有个3个苹果,也可以说有一个苹果,一个苹果,一个苹果,每种语言都有量词的概念</p>
<p>如果需要匹配多次某个字符,正则也提供了量词的功能,正则中的量词有多个,如<code>?</code>、<code>+</code>、<code>*</code>、<code>{n}</code>、<code>{m,n}</code>、<code>{m,}</code></p>
<p><code>{n}</code>匹配n次,比如<code>a{2}</code>,匹配<code>aa</code></p>
<p><code>{m, n}</code>匹配m-n次,优先匹配n次,比如<code>a{1,3}</code>,可以匹配<code>aaa</code>、<code>aa</code>、<code>a</code></p>
<p><code>{m,}</code>匹配m-∞次,优先匹配∞次,比如<code>a{1,}</code>,可以匹配<code>aaaa...</code></p>
<p><code>?</code>匹配0次或1次,优先匹配1次,相当于<code>{0,1}</code></p>
<p><code>+</code>匹配1-n次,优先匹配n次,相当于<code>{1,}</code></p>
<p><code>*</code>匹配0-n次,优先匹配n次,相当于<code>{0,}</code></p>
<p>正则默认和人心一样是贪婪的,也就是常说的贪婪模式,凡是表示范围的量词,都优先匹配上限而不是下限</p>
<div class="jb51code"><pre class="brush:bash;">a{1, 3} // 匹配字符串'aaa'的话,会匹配aaa而不是a</pre></div>
<p>有时候这不是我们想要的结果,可以在量词后面加上<code>?</code>,就可以开启非贪婪模式</p>
<div class="jb51code"><pre class="brush:bash;">a{1, 3}? // 匹配字符串'aaa'的话,会匹配a而不是aaa</pre></div>
<p class="maodian"><a name="_label5"></a></p><h2>字符边界</h2>
<p>有时我们会有边界的匹配要求,比如以xxx开头,以xxx结尾</p>
<p><code>^</code>在<code>[]</code>外表示匹配开头的意思</p>
<div class="jb51code"><pre class="brush:bash;">^abc // 可以匹配abc,但是不能匹配aabc</pre></div>
<p><code>$</code>表示匹配结尾的意思</p>
<div class="jb51code"><pre class="brush:bash;">abc$ // 可以匹配abc,但是不能匹配abcc</pre></div>
<p>上面提到的<code>\b</code>表示单词的边界</p>
<div class="jb51code"><pre class="brush:bash;">abc\b // 可以匹配 abc ,但是不能匹配 abcc</pre></div>
<p class="maodian"><a name="_label6"></a></p><h2>选择表达式</h2>
<p>有时我们想匹配x或者y,如果x和y是单个字符,可以使用字符集,<code></code>可以匹配<code>a</code>或<code>b</code>或<code>c</code>,如果x和y是多个字符,字符集就无能为力了,此时就要用到分组</p>
<p>正则中用<code>|</code>来表示分组,<code>a|b</code>表示匹配<code>a</code>或者<code>b</code>的意思</p>
<div class="jb51code"><pre class="brush:bash;">123|456|789 // 匹配 123 或 456 或 789</pre></div>
<p class="maodian"><a name="_label7"></a></p><h2>分组与引用</h2>
<p>分组是正则中非常强大的一个功能,可以让上面提到的量词作用于一组字符,而非单个字符,分组的语法是圆括号包裹<code>(xxx)</code></p>
<div class="jb51code"><pre class="brush:bash;">(abc){2} // 匹配abcabc</pre></div>
<p>分组不能放在<code>[]</code>中,分组中还可以使用选择表达式</p>
<div class="jb51code"><pre class="brush:bash;">(123|456){2} // 匹配 123123、456456、123456、456123</pre></div>
<p>和分组相关的概念还有一个捕获分组和非捕获分组,分组默认都是捕获的,在分组的<code>(</code>后面添加<code>?:</code>可以让分组变为非捕获分组,非捕获分组可以提高性能和简化逻辑</p>
<div class="jb51code"><pre class="brush:bash;">'123'.match(/(?:123)/) // 返回 ['123']
'123'.match(/(123)/)// 返回 ['123', '123']</pre></div>
<p>和分组相关的另一个概念是引用,比如在匹配html标签时,通常希望<code><xxx></xxx></code>后面的<code>xxx</code>能够和前面保持一致</p>
<p>引用的语法是<code>\数字</code>,数字代表引用前面第几个捕获分组,注意非捕获分组不能被引用</p>
<div class="jb51code"><pre class="brush:bash;"><(+)><\/\1> // 可以匹配 `<span></span>` 或 `<div></div>`等</pre></div>
<p class="maodian"><a name="_label8"></a></p><h2>预搜索</h2>
<p>如果你想匹配xxx前不能是yyy,或者xxx后不能是yyy,那就要用到预搜索</p>
<p>js只支持正向预搜索,也就是xxx后面必须是yyy,或者xxx后面不能是yyy</p>
<div class="jb51code"><pre class="brush:bash;">1(?=2) // 可以匹配12,不能匹配22
1(?!2) // 可有匹配22,不能匹配12</pre></div>
<p class="maodian"><a name="_label9"></a></p><h2>修饰符</h2>
<p>默认正则是区分大小写,这可能并不是我们想要的,正则提供了修饰符的功能,修复的语法如下</p>
<div class="jb51code"><pre class="brush:bash;">/xxx/gi // 最后面的g和i就是两个修饰符</pre></div>
<p><code>g</code>正则遇到第一个匹配的字符就会结束,加上全局修复符,可以让其匹配到结束</p>
<p><code>i</code>正则默认是区分大小写的,<code>i</code>可以忽略大小写</p>
<p><code>m</code>正则默认情况下,^和只能匹配字符串的开始和结尾,m修饰符可以让和只能匹配字符串的开始和结尾,m修饰符可以让^和只能匹配字符串的开始和结尾,m修饰符可以让和匹配行首和行尾,不理解就看例子</p>
<div class="jb51code"><pre class="brush:bash;">/jing$/ // 能够匹配 'yanhaijing,不能匹配 'yanhaijing\n'
/jing$/m // 能够匹配 'yanhaijing, 能够匹配 'yanhaijing\n'
/^jing/ // 能够匹配 'jing',不能匹配 '\njing'
/^jing/m // 能够匹配 'jing',能够匹配 '\njing'</pre></div>
<p class="maodian"><a name="_label10"></a></p><h2>图形化工具</h2>
<p>有时我们会遇到特别复杂的正则,有时候可能不太直观,下面推荐一个图形化展示的<a href="https://link.juejin.cn?target=https%3A%2F%2Fjex.im%2Fregulex" rel="external nofollow" target="_blank">工具</a>,我们把涉及到的语法罗列一下</p>
<div class="jb51code"><pre class="brush:bash;">/^*[^\d]{1,10}?(aaa|bbb)(?:ccc)$/</pre></div>
<p>可以看到工具能够更快的帮我们理清头绪</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202207/2022719103903592.png?2022619103952" /></p>
<p class="maodian"><a name="_label11"></a></p><h2>JavaScript中的正则</h2>
<p>在js中创建正则有两种办法,字面量和new,和创建其他类型变量一样</p>
<div class="jb51code"><pre class="brush:bash;">var reg = /abc/g // 字面量
var reg = new RegExp('abc', 'g') // new方式,意思和上面一样</pre></div>
<p>js中用到正则的地方有两个入口,正则的api和字符串的api,<code>RegExp#test</code>等于<code>RegExp.prototype.test</code></p>
<ul><li>RegExp#test</li><li>RegExp#exec</li><li>String#search</li><li>String#match</li><li>String#split</li><li>String#replace</li></ul>
<p class="maodian"><a name="_label12"></a></p><h2>RegExp#test</h2>
<p>每个正则实例都有test方法,test的参数是字符串,返回值是布尔值,表示当前正则是否能匹配指定的字符串</p>
<div class="jb51code"><pre class="brush:bash;">/abc/.test('abc') // true
/abc/.test('abd') // false</pre></div>
<p class="maodian"><a name="_label13"></a></p><h2>RegExp#exec</h2>
<p>exec使用方法和test一样,只是返回值并不是布尔值,而是返回匹配的结果</p>
<p>匹配成功返回一个数组,数组第一项是匹配结果,后面一次是捕获的分组</p>
<div class="jb51code"><pre class="brush:bash;">/abc(d)/.exec('abcd') // ["abcd", "d", index: 0, input: "abcd"]</pre></div>
<p>此数组还有另外两个参数,input是输入的字符串,index表示匹配成功的序列在输入字符串中的索引位置</p>
<p>如果有全局参数(g),第二次匹配时将从上次匹配结束时继续</p>
<div class="jb51code"><pre class="brush:bash;">var r1 = /ab/
r1.exec('ababab') // ['ab', index: 0]
r1.exec('ababab') // ['ab', index: 0]
var r2 = /ab/g
r2.exec('ababab') // ['ab', index: 0]
r2.exec('ababab') // ['ab', index: 2]
r2.exec('ababab') // ['ab', index: 4]</pre></div>
<p>这一特性可以被用于循环匹配,比如统计字符串中abc的次数</p>
<div class="jb51code"><pre class="brush:bash;">var reg = /abc/g
var str = 'abcabcabcabcabc'
var num = 0;
var match = null;
while((match = reg.exec(str)) !== null) {
num++
}
console.log(num) // 5</pre></div>
<p>如果匹配失败则返回null</p>
<div class="jb51code"><pre class="brush:bash;">/abc(d)/.exec('abc') // null</pre></div>
<p class="maodian"><a name="_label14"></a></p><h2>String#search</h2>
<p>search方法返回匹配成功位置的索引,参数是字符串或正则,结果是索引</p>
<div class="jb51code"><pre class="brush:bash;">'abc'.search(/abc/) // 0
'abc'.search(/c/) // 2</pre></div>
<p>如果匹配失败则返回-1</p>
<div class="jb51code"><pre class="brush:bash;">'abc'.search(/d/) // -1
'abc'.search(/d/) !== -1 // false 转换为布尔值</pre></div>
<p class="maodian"><a name="_label15"></a></p><h2>String#match</h2>
<p>match方法也会返回匹配的结果,匹配结果和exec类似</p>
<div class="jb51code"><pre class="brush:bash;">'abc'.match(/abc/) // ['abc', index: 0, input: abc]
'abc'.match(/abd/) // null</pre></div>
<p>如果有全局参数(g),match会返回所有的结果,并且没有index和input属性</p>
<div class="jb51code"><pre class="brush:bash;">'abcabcabc'.match(/abc/g) // ['abc', 'abc', 'abc']</pre></div>
<p class="maodian"><a name="_label16"></a></p><h2>String#split</h2>
<p>字符串的split方法,可以用指定符号分隔字符串,并返回数据</p>
<div class="jb51code"><pre class="brush:bash;">'a,b,c'.split(',') // </pre></div>
<p>其参数也可以使一个正则,如果分隔符有多个时,就必须使用正则</p>
<div class="jb51code"><pre class="brush:bash;">'a,b.c'.split(/,|\./) // </pre></div>
<p class="maodian"><a name="_label17"></a></p><h2>String#replace</h2>
<p>字符串的replace方法,可以将字符串的匹配字符,替换成另外的指定字符</p>
<div class="jb51code"><pre class="brush:bash;">'abc'.replace('a', 'b') // 'bbc'</pre></div>
<p>其第一个参数可以是正则表达式,如果想全局替换需添加全局参数</p>
<div class="jb51code"><pre class="brush:bash;">'abc'.replace(//, 'y') // ybc
'abc'.replace(//g, 'y') // yyy 全局替换</pre></div>
<p>在第二个参数中,也可以引用前面匹配的结果</p>
<div class="jb51code"><pre class="brush:bash;">'abc'.replace(/a/, '$&b') // abbc $& 引用前面的匹配字符
'abc'.replace(/(a)b/, '$1a') // aac &n 引用前面匹配字符的分组
'abc'.replace(/b/, '$\'') // aac $` 引用匹配字符前面的字符
'abc'.replace(/b/, "$'") // acc $' 引用匹配字符后面的字符</pre></div>
<p>replace的第二个参数也可以是函数,其第一个参数是匹配内容,后面的参数是匹配的分组</p>
<div class="jb51code"><pre class="brush:bash;">'abc'.replace(/\w/g, function (match, $1, $2) {
return match + '-'
})
// a-b-c-</pre></div>
<p class="maodian"><a name="_label18"></a></p><h2>RegExp</h2>
<p>RegExp是一个全局函数,可以用来创建动态正则,其自身也有一些属性</p>
<ul><li>$_</li><li>$n</li><li>input</li><li>length</li><li>lastMatch</li></ul>
<p>来个例子</p>
<div class="jb51code"><pre class="brush:bash;">/a(b)/.exec('abc') // ["ab", "b", index: 0, input: "abc"]
RegExp.$_ // abc 上一次匹配的字符串
RegExp.$1 // b 上一次匹配的捕获分组
RegExp.input // abc 上一次匹配的字符串
RegExp.lastMatch // ab 上一次匹配成功的字符
RegExp.length // 2 上一次匹配的数组长度</pre></div>
<p class="maodian"><a name="_label19"></a></p><h2>实例属性</h2>
<p>正则表达式的实例上也有一些属性</p>
<ul><li>flags</li><li>ignoreCase</li><li>global</li><li>multiline</li><li>source</li><li>lastIndex</li></ul>
<p>还是看例子</p>
<div class="jb51code"><pre class="brush:bash;">var r = /abc/igm;
r.flags // igm
r.ignoreCase // true
r.global // true
r.multiline // true
r.source // abc</pre></div>
<p>lastIndex比较有意思,表示上次匹配成功的是的索引</p>
<div class="jb51code"><pre class="brush:bash;">var r = /abc/igm;
r.exec('abcabcabc')
r.lastIndex // 3
r.exec('abcabcabc')
r.lastIndex // 6</pre></div>
<p>可以更改lastIndex让其重新开始</p>
<div class="jb51code"><pre class="brush:bash;">var r = /abc/igm;
r.exec('abcabcabc') // ["abc", index: 0]
r.exec('abcabcabc') // ["abc", index: 3]
r.lastIndex = 0
r.exec('abcabcabc') // ["abc", index: 0]</pre></div>
<p class="maodian"><a name="_label20"></a></p><h2>实战实例</h2>
<p>来几个常用的例子</p>
<div class="jb51code"><pre class="brush:bash;">/(?:0\d{2,3}-)?\d{7}/ // 电话号 010-xxx xxx
/^1\d{9}$/ // 手机号 13xxx 17xxx 18xxx
/^+@+\.+$/ // 邮箱</pre></div>
<p>去除字符串前后空白</p>
<div class="jb51code"><pre class="brush:bash;">str = str.replace(/^\s*|\s*$/g, '')</pre></div>
<p class="maodian"><a name="_label21"></a></p><h2>总结</h2>
<p>刻意练习,方能游刃有余,知己知彼,方能百战百胜,正则是前端的一个武器,技多不压身。</p>
<p>到这里你已经学会了正则的语法,并且学会了在js中使用正则的方法,接下来快去实战吧,要想学会正则必须多加练习,正所谓拳不离手曲不离口吗。</p>
頁:
[1]