正则基本入门

iridium 7年前
   <p>以前看了许许多多的正则教程,收货并不多,往往都是蜻蜓点水,一点就过。事实上,正则用处真的超级大,比如匹配innerHTML的内容,以及表单验证,也是非他莫属。这里,我结合js,对正则进行一个简单的介绍吧。 如有纰漏欢迎指出,希望大家多多包涵。</p>    <h2>js与正则的关系</h2>    <p>在js中定义一个正则有两种方法,一个是实例化,一个是字面量。 分别看一下:</p>    <pre>  <code class="language-python">//字面量  var re = /\w+/;   //这两者等价  //实例化  var re = new RegExp('\\w+');</code></pre>    <p>如果想添加一些flags也是没有问题的。 比较常用的flag有。/i,/g,/ig,/m.</p>    <pre>  <code class="language-python">/i (忽略大小写,ignore)  /g (全文查找出现的所有匹配字符,global)  /m (多行查找,multiLine)  /ig(全文查找、忽略大小写,ignore+global)</code></pre>    <p>所以, 使用flag之后可以这样写.</p>    <pre>  <code class="language-python">var reg = ^\d{5,12}\i$  ;//表示忽略大小写,匹配;  //或者  var reg = new RegExp(^\d{5,12}\i$);</code></pre>    <h3>正式入门正则</h3>    <p>正则其实就是用来匹配字符串的。他用一个简洁表达了,完成了你需要写很多代码的事,就和md(markdown)语法是一个道理。 用的人多了,自然成标准,这也是规则吧。</p>    <h3>正则预定字符</h3>    <p>预定字符,就是用程序比较难表达的一些字符,比如回车键,tab键(通过空格来区分达到的效果). 常用的有:</p>    <table>     <thead>      <tr>       <th>字符</th>       <th>效果</th>      </tr>     </thead>     <tbody>      <tr>       <td>\t</td>       <td>制表符,其实就是一个“Tab”键</td>      </tr>      <tr>       <td>\r</td>       <td>回车符,如果你使用过word应该之后,在一个段落后面那个东西吧。 :)</td>      </tr>      <tr>       <td>\n</td>       <td>换行符,他和\r是有故事的,等下说,我们继续</td>      </tr>     </tbody>    </table>    <p>恩,大部分就是这几个了。 上面提到 \r和\n,他们到底有什么却别。 没错,看字面量,感觉return 不就是换行吗? 其实,这样说没错,但是得区分系统,在Unix为扩展的系统,在每行的结尾只有"\n",而在window下则是:"\r\n"(顺序不能换). 所以,为了解决系统的差异,就出现了两种: \r || \n. 所以一般,我们匹配换行需要使用.\r||\n一起使用.</p>    <pre>  <code class="language-python">var reg = /[\r\n]/g;</code></pre>    <p>这样就能保证系统的兼容性.</p>    <h3>字符类</h3>    <p>所谓的字符类同样也是将你平常要花很多时间做出来的,集成为一个简洁表达。(相当于写库)。 常用的字符类有如下几个。</p>    <table>     <thead>      <tr>       <th>字符</th>       <th>效果</th>      </tr>     </thead>     <tbody>      <tr>       <td>.</td>       <td>匹配换行符以外的任意字符</td>      </tr>      <tr>       <td>\d</td>       <td>匹配所有数字</td>      </tr>      <tr>       <td>\D</td>       <td>匹配非数字</td>      </tr>      <tr>       <td>\s</td>       <td>匹配一个空格符</td>      </tr>      <tr>       <td>\S</td>       <td>匹配非空格</td>      </tr>      <tr>       <td>\w</td>       <td>匹配字母数字下划线=>其实就是匹配单词word(简单易懂)</td>      </tr>      <tr>       <td>\W</td>       <td>匹配!字母数字下划线=>就是不匹配单词</td>      </tr>     </tbody>    </table>    <p>来我们看几个例子</p>    <pre>  <code class="language-python">console.log(/\s+/.test("     "));  //true  console.log(/\d+/.test("1234231"));  //true  console.log(/\D+/.test("  "));  //true</code></pre>    <p>其他的如上。</p>    <h3>锚字符</h3>    <p>这个应该算是正则里面,取名最好理解的一个。使用正则就是停船一样,你需要设置你停的位置,我也需要设置我的边界。 常用的有一下几个:</p>    <table>     <thead>      <tr>       <th>锚字符</th>       <th>效果</th>      </tr>     </thead>     <tbody>      <tr>       <td>^</td>       <td>匹配字符串的开头,在多行检索中,匹配一行的开头</td>      </tr>      <tr>       <td>$</td>       <td>匹配字符串的结尾,在多行检索中,匹配一行的结尾</td>      </tr>      <tr>       <td>\b</td>       <td>匹配一个单词的边界</td>      </tr>      <tr>       <td>\B</td>       <td>匹配非单词边界</td>      </tr>     </tbody>    </table>    <p>这几个应该算是我平常用的最多的几个吧。 如果你想匹配整个字符串,就可以组合使用"^ $";</p>    <pre>  <code class="language-python">var reg = /^\d+$/;  //匹配整个字符串为数字</code></pre>    <h3>量词字符</h3>    <p>“望文生义”,这类字符使用来限定某某出现的次数的。 常用的有:</p>    <table>     <thead>      <tr>       <th>代码 / 语法</th>       <th>说明</th>      </tr>     </thead>     <tbody>      <tr>       <td>*</td>       <td>重复零次或更多次</td>      </tr>      <tr>       <td>+</td>       <td>重复一次或更多次</td>      </tr>      <tr>       <td>?</td>       <td>重复零次或一次</td>      </tr>      <tr>       <td>{n}</td>       <td>重复n次</td>      </tr>      <tr>       <td>{n,}</td>       <td>重复n次或更多次</td>      </tr>      <tr>       <td>{n, m}</td>       <td>重复n到m次</td>      </tr>     </tbody>    </table>    <p>这个应该不用多说了。 直接看例子吧</p>    <pre>  <code class="language-python">console.log(/^\d+$/.test("123")); //true</code></pre>    <p>上面说了这么多内置的字符,那我想使用特定字符类怎么办嘞。其实也很简单。使用"“转义字符。 比如我想匹配大括号.”{}".我可以这样用:</p>    <pre>  <code class="language-python">console.log(/\{.+\}/.test("{123}")); //true</code></pre>    <p>但事实上,量词还分为3种,有贪婪量词,惰性量词,支配性量词。 区分的依据是根据引擎的解析不同而形成。 <strong>贪婪量词</strong> 这类量词指的就是上文所说的: *,+,?。 他的匹配方法就是,全文匹配,如果不成功,则,将末尾的最后一个字符减去,再匹配,如果还不成功,则,再减一次。只到为0。 接着,往中间移动一位,再进行匹配,同样的匹配模式。</p>    <pre>  <code class="language-python">console.log(/.+/.test("abcd"));  //true</code></pre>    <p>惰性量词使用方法: 基本量词 ? 该量词和贪婪量词就像,一个是消极怠工,一个是积极工作。 惰性量词一开始只会匹配一个字符,如果不成功,则在进行匹配。</p>    <pre>  <code class="language-python">console.log(/\d+?/.test("1fjkdf"));  //true</code></pre>    <p>这里阐述一些惰性和贪婪匹配的区别。 我们也通常把惰性称为最少重复匹配。 举个例子: 我们现在需要匹配blablablabla. 中的b~a间的词。 使用贪婪匹配:</p>    <pre>  <code class="language-python">var str = "blablablabla";  console.log(str.match(/(b.*a)/g));  //["blablablabla"]</code></pre>    <p>我们最少重复匹配(惰性匹配)</p>    <pre>  <code class="language-python">console.log(str.match(/(b.*?a)/g));  //["bla", "bla", "bla", "bla"]</code></pre>    <p>支配性量词使用方法: 基本量词 +; 该量词就是只匹配一次,如果不符合则不匹配。 但是由于js不支持,所以,这里也不做过多的介绍。</p>    <pre>  <code class="language-python">正则: /\d*+/;</code></pre>    <p>其实上面只要留个印象就可以,只有当你真正使用的时候,你才会有感触。 OK!!!基本内容说完了,现在轮到真正的进阶,big boom~</p>    <h3>中括号的用法</h3>    <p>我们从小学学过来,老师告诉我们,我们使用括号有3种,一个是( ),一个是[],一个是{}. 而在正则里面,大括号已经被量词字符给强占了,只剩下[]和(). 这里我们来说一下,中括号. [],在正则里面代表的是一个单元字符,或者我宁愿叫他"或"括号. 因为他起到的主要作用就是,你可以匹配这个或者匹配那个或者… 吃个栗子:</p>    <pre>  <code class="language-python">var reg = /[abc]/;  console.log(reg.test("a"));  //true</code></pre>    <p>可以看出,reg可以匹配 a|b|c. 平常使用的时候,可以直接向一个字符使用就可以了。 <strong>异或表达</strong> 这里会出现一个问题,比如,我不想匹配a,b,c中的任意一个该怎么办呢? 其实,只需要在"[]“里面加上”^"即可。</p>    <pre>  <code class="language-python">console.log(/[^abc]/.test("c"));  //false</code></pre>    <p>范围字符范围字符,就是可以省略一些周所周知的。 比如匹配26英文字母可以直接使用:a-z. 因为我们已经都知道了这个的意义。 其实,上面所说的字符类完全就可以使用中括号来代替。</p>    <pre>  <code class="language-python">\d => [0-9]  \w => [0-9a-zA-Z_]  \S => [^\t\n\x0B\f\r]  (\f标识分页符)  ...</code></pre>    <p>另外这个范围字符还有一个好处,就是匹配中文。(电脑都是外国人发明的呀。)</p>    <pre>  <code class="language-python">console.log(/[\u4e00-\u9fa5]{1}/.test("艹")); //true</code></pre>    <p>这就是中括号的常用用法。</p>    <h3>小括号使用</h3>    <p>小括号的主要作用其实就是分组。平常是用来提取匹配到的字符串。 <strong>分组使用</strong> 使用()对内容进行区分。</p>    <pre>  <code class="language-python">console.log(/(jimmy)+/.test("jimmy"));  //true</code></pre>    <p>而且,配合使用match方法,可以获得匹配到的内容.(这里不加括号也是可以的).</p>    <pre>  <code class="language-python">var name = "My name is Jimmy";  console.log(name.match(/(Jimmy)/g));  //["Jimmy"]</code></pre>    <p>需要注意在括号里面写正则和没有括号的时候,是没有区别的。我们可以在()内嵌套你想加的。(如果你想嵌套()的话,Sorry,这样并没有什么卵用).</p>    <pre>  <code class="language-python">var name = "My name is Jimmy Jimy";  console.log(name.match(/(Jimm?y)/g));  //["Jimmy", "Jimy"]</code></pre>    <p>候选(或)这个就相当于将括号加上一个或的功能. 即,在()里面使用"|"进行分隔。</p>    <pre>  <code class="language-python">var name = "My name is Jimmy sam";  var reg = /(jimmy|sam)+?/ig;  console.log(name.match(reg)); //["jimmy","sam"]</code></pre>    <p>反向引用这个名字我真心不理解,什么"反向"… 我宁愿叫做,给分组加上标识符。这个的主要功能,就是给匹配到的小括号加上数字,来表明他是第几个匹配到的。如果不加,则默认从左到右的顺序为1,2,3…</p>    <pre>  <code class="language-python">var reg = /(100)\1/;  var reg2 = /(100)(99)(101)\1\2\3/; //1=>100,2=>99,3=>101</code></pre>    <p>在js中,通常是和replace搭配,才有威力。</p>    <pre>  <code class="language-python">var reg = /(100) (99)/;  var str = "100 99";  console.log(str.replace(reg,"$2 $1")); //99 100</code></pre>    <p>总而言之, 小括号就是让你使用分组的匹配. 说回来,分组有什么用呢? 实际上就是让你的正则看起来更短而已. 看个demo你就懂分组的意义了:</p>    <pre>  <code class="language-python">var str = "name jimmy";  console.log(str.match(/\b(\w+)\b\s+\1\b/));  // 这里的\1 实际上就是前面的(\w+)  //得到的结果为 null. 因为name 不能匹配到jimmy所以为null  var str = "jimmy jimmy";  console.log(str.match(/\b(\w+)\b\s+\1\b/));  //得到的结果为 jimmy。 因为/w匹配到的为jimmy,所以为jimmy</code></pre>    <p>上面那种方法叫做后向引用. 另外, 我们还可以显示的使用命名. 即: \b(?<fetchWord>\w+)\b\s+\b\kfetchWord\b 这样,就可以达到, 内部正则的复用. 不过, 对不起, 在js中,只支持数组分组, 即, 按顺序分配序号。和上面demo一样. 不过在perl 系列的正则中是使用(?P< xx>) 和 \g</p>    <h3>非捕获分组</h3>    <p>我们直接使用 "(…)"进行的匹配是捕获分组。 我们来说一下什么叫 <em>捕获</em> . 上文中我们使用match进行正则匹配,而返回的数组中的元素就是通过正则捕获的内容。 这就叫捕获。 那这里的非捕获,是什么意思呢? 其实很简单,就是通过match不会匹配到内容。但还是可以起到分组的效果。 格式为: (?:xxx) 它最常用的地方就是匹配html.</p>    <pre>  <code class="language-python">var str=` <div class="pin">              <div class="box">                  <img src="https://simg.open-open.com/show/d104cb79b036edf9ccaadc1d8de1766d.jpg" />              </div>          </div>`;  var reg = /<div(?:.|\r|\n)*div>/gi;  console.log(str.match(reg));</code></pre>    <p>大家可以去试一试,说到正则匹配,我还有一个想说的,就是上文所说的惰性匹配(最少重复)和贪婪匹配。 可以看到 “/< div(?:.|\r|\n)*div>/gi” 我这里使用的是贪婪匹配。他的结果是,尽量匹配到最外层的< /div>标签。 即上面的结果为:</p>    <pre>  <code class="language-python"><div class="pin">              <div class="box">                  <img src="https://simg.open-open.com/show/d104cb79b036edf9ccaadc1d8de1766d.jpg" />              </div>          </div>  </code></pre>    <p>可以看出,贪婪匹配,对于两个重复的/div 他会匹配到最外一层。 那我们使用惰性匹配试一试。 /< div(?:.|\r|\n)*?div>/gi 得到的结果为:</p>    <pre>  <code class="language-python"><div class="pin">              <div class="box">                  <img src="https://simg.open-open.com/show/d104cb79b036edf9ccaadc1d8de1766d.jpg" />              </div>  </code></pre>    <p>可以看出少了一个< /div>,原因就是,惰性匹配尽量只会匹配到第一个重复的< /div>上面的。 所以,总结一下,在使用正则匹配的时候需要搞清楚到底什么时候用惰性,什么时候用贪婪,这点很重要。 贪婪会匹配最外层,惰性会匹配最里层。</p>    <h3>前瞻(零宽断言)</h3>    <p>前瞻分为正向前瞻和反向前瞻。(由于js只支持前瞻,所以后瞻只会提一下)。 他的作用就是,在匹配的字符后面,断言说后面一定符合我的正则。 (好饶~~) 算了,先说一下基本格式吧。</p>    <table>     <thead>      <tr>       <th>正则</th>       <th>名称</th>       <th>作用</th>      </tr>     </thead>     <tbody>      <tr>       <td>(?=exp)</td>       <td>正向前瞻</td>       <td>匹配exp前面的位置</td>      </tr>      <tr>       <td>(?!exp)</td>       <td>反向前瞻</td>       <td>匹配后面不是exp的位置</td>      </tr>      <tr>       <td>(?<=exp)</td>       <td>正向后瞻</td>       <td>匹配exp后面的位置</td>      </tr>      <tr>       <td>(?< !exp)</td>       <td>反向后瞻</td>       <td>匹配后面不是exp的位置</td>      </tr>     </tbody>    </table>    <p>看不懂了吧,我们来看一下详细的内容。 for instances:</p>    <pre>  <code class="language-python">var str = "happied boring";  var reg1 = /happ(?=ied)/g;  var reg2 = /bor(?!ied)/;  console.log(str.match(reg1)); //["happ"]  console.log(str.match(reg2)); //["bor"]</code></pre>    <p>从这个例子可以很容易看出前瞻后瞻到底是什么了。 回到上面的匹配html的例子。 这里我们有个需求,即只留下img标签,那么就可以使用前瞻.</p>    <pre>  <code class="language-python">var str=` <div class="pin">              <div class="box">                  <img src="https://simg.open-open.com/show/d104cb79b036edf9ccaadc1d8de1766d.jpg" />              </div>          </div>`;  var reg = /<(?!img)(?:.|\r|\n)*?>/gi;  console.log(str.replace(reg,""));  //得到的结果为:  <img src="https://simg.open-open.com/show/d104cb79b036edf9ccaadc1d8de1766d.jpg" /></code></pre>    <p>另外,零宽断言还有另外一个作用,即匹配以xxx为结尾的单词。 这时候,你的leader对你有个要求,即,jimmy呀,你把ed结尾的单词找出来哦。(好呀~) 这时候就可以使用前瞻了。</p>    <pre>  <code class="language-python">var str = "he is an interested person";  var reg = /\b\w+(?=ed\b)/ig;  console.log(str.match(reg)); //["interest"]</code></pre>    <h2>结束语</h2>    <p>关于正则的内容大概就是这些了。 其实正则的学习,不是只用看就能学会的,实践才是硬道理。 通过,理论的学习,在加上踩过的坑,自然会对正则有着莫名的好感。 不过,大神就是大神,取名字就是这么别扭。 什么 零宽断言,前瞻,后瞻,反向引用 blablabla… 在理解的同时可以根据自己的理解给这些名词冠上自己的idea.我这里只是 正则的冰山一角,正则在任意一门语言内,用处都是超级大的。这里安利一个 总结的比较好的正则库。 <a href="/misc/goto?guid=4959660985307253845" rel="nofollow,noindex">正则库</a> . 还有一个在线的regExp测试工具. <a href="/misc/goto?guid=4958856039043877294" rel="nofollow,noindex">Debuggex</a></p>    <p> </p>    <p>来自:https://www.villainhr.com/page/2017/10/17/正则基本入门</p>    <p> </p>