内容简介:此内容由学习《JavaScript正则表达式迷你书(1.1版)》整理而来(于2020年3月30日看完)。此外还参考了MDN上关于Regex和String的相关内容,还有ECMAScript 6中关于正则的扩展内容,但不多。在文章末尾,会放上所有的链接。迷你书共七章,我都做了相应的标号。不过我将【7】7种方法放在了前面,讨论了具体情境下怎么正确使用函数的问题(其实是我自己一直被这个问题困扰,书上的例子为什么用这个方法,为什么这个方法这里返回这样,那里却不是这样,把我搞崩溃了),也建议大家先搞懂这个吧。语法:/
前言
此内容由学习《JavaScript正则表达式迷你书(1.1版)》整理而来(于2020年3月30日看完)。此外还参考了MDN上关于Regex和String的相关内容,还有ECMAScript 6中关于正则的扩展内容,但不多。在文章末尾,会放上所有的链接。
迷你书共七章,我都做了相应的标号。不过我将【7】7种方法放在了前面,讨论了具体情境下怎么正确使用函数的问题(其实是我自己一直被这个问题困扰,书上的例子为什么用这个方法,为什么这个方法这里返回这样,那里却不是这样,把我搞崩溃了),也建议大家先搞懂这个吧。
本文重点
-
正则的 2 种创建
-
正则的 6 个修饰符(i、g、m、u、y、s)
-
【7】.用到正则的 7 种方法(RegExp(exec、test),String(search、match、matchAll、replace、split))
-
正则表达式中的6种结构(字符字面量、字符组、量词、锚、分组、分支)
-
【1】.字符匹配:字面量、字符组、量词。重点还有贪婪匹配与惰性匹配
-
【2】.位置匹配:^ 、$ 、 \b 、\B 、(?=abc) 、 (?!abc) 的使用
-
【3】.括号的作用:分组和分支结构。知识点有:分组引用($1的使用)、反向引用(\1的使用)、括号嵌套、括号结合量词
-
-
【4】
.回溯原理(本文大多介绍的是实践中用的基础知识,后期对此可能会单独写篇,先占个坑) -
【5】.正则的拆分:重点是操作符的优先级
-
【6】.
正则表达式的构建(本章写了些提高正则准确性和效率的内容,也先占个坑) -
简单实用的正则测试器
2种创建
语法:/pattern/flags
var regexp = /\w+/g;
var regexp = new RegExp('\\w+','g');
6个修饰符
重点介绍全局与非全局:
全局就是说在字符串中查找所有与正则式匹配的内容,因此会有>=1个结果。
而为了得到这个所谓的所有匹配内容的过程, 后面介绍的函数还有一次执行和多次执行之别。
如果正则表达式中存在分组,该模式下各个函数返回的结果也不一样。
非全局就简单了,它表示匹配到了一个就不会再继续往后匹配了,因此都是一次就得结果。
7种方法
以下图在我学习的过程中,修改了很多遍,才得出了这个最简洁明了的版本。
【补充1】全局模式对 exex 和 test 的影响
正则实例有个 lastIndex 属性,表示尝试匹配时,从字符串的 lastIndex 位开始去匹配。
全局匹配下,字符串的四个方法,每次匹配时,都是从 0 开始的,即 lastIndex 属性始终不变。
而正则实例的两个方法 exec、test,当正则是全局匹配时,每一次匹配完成后,都会修改 lastIndex。(详见下面示例)
如果是非全局匹配,自然都是从字符串第 0 个字符处开始尝试匹配:
exec()
在全局状态下,需要一次次执行直到末尾才能得到所有匹配项,这里只是手动模拟下。当然最好是用循环实现, while ((match = regexp.exec(str)) !== null) {//输出}
var regexp = /a/g; console.log( regexp.exec("a"), regexp.lastIndex );// [ 'a', index: 0, input: 'a', groups: undefined ] 1 console.log( regexp.exec("aba"), regexp.lastIndex );// [ 'a', index: 2, input: 'aba', groups: undefined ] 3 console.log( regexp.exec("ababc"), regexp.lastIndex );// null 0
注意:该部分将的所有正则表达式不知道的都先不要急,慢慢来。
【补充2】全局下match、exec、matchAll示例
match()和exec()示例:
//全局模式下,匹配所有能匹配到的 var regexp1 = /t(e)(st(\d?))/g; var str1 = 'test1test2'; console.log(str1.match(regexp1)); //match返回所有匹配项组成的数组[ 'test1', 'test2' ] console.log(regexp1.exec(str1))// exec第一次执行返回第一个匹配项和它的分组['test1', 'e','st1','1',index:0,input:'test1test2',groups:undefined] console.log(regexp1.exec(str1))//exec第二次执行返回第二个匹配项和它的分组['test2', 'e','st2','2',index:5,input:'test1test2',groups:undefined] console.log(regexp1.exec(str1))//exec第二次执行已经到末尾了,因此第三次结果为null //非全局模式下,只匹配到第一项就停止 var regexp2 = /t(e)(st(\d?))/; var str2 = 'test1test2'; console.log(str2.match(regexp2)); //match['test1', 'e','st1','1',index:0,input:'test1test2',groups:undefined] console.log(regexp2.exec(str2)) //exec['test1', 'e','st1','1',index:0,input:'test1test2',groups:undefined]
matchAll()示例:
matchAll()
是es6的用法,记住它返回的就是一个迭代器。可以用 for...of
循环取出,也可以用 ...迭代器
运算符或者 Array.from(迭代器)
将它转为数组。
var array1 = [...str1.matchAll(regexp1)]; console.log(array1) //['test1','e','st1','1',index: 0,input: 'test1test2',groups: undefined] //['test2','e','st2','2',index: 5,input: 'test1test2',groups: undefined] var array2 = [...str2.matchAll(regexp2)]; console.log(array2) //['test1','e','st1','1',index: 0,input: 'test1test2',groups: undefined]
到目前为止,我们应该积攒了很多问号了。我学的过程中有以下两个问题:
1. /t(e)(st(\d?))/g
和 /t(e)(st(\d?))/
的区别我知道了,但 t(e)(st(\d?))
这是什么意思呢?
2.上文所谓的 “与正则表达式匹配的内容” 和 “匹配项中分组捕获的内容”怎么理解?
那就带着问题看后面的内容吧。
7种结构 -字符匹配
字面量
字符组
需要强调的是,虽叫字符组(字符类),但只是其中一个字符。
如果字符组里的字符特别多,可用连字符 -
来省略和简写(见表格示例)。
示例:全局匹配,使用 match()
方法返回字符串中与表达式匹配的所有内容。 [123]
表示这个位置的字符可以是 1
、 2
、 3
中的任意一个
量词
有了一个字符,那我我们就会考虑到需要它出现几次,那么量词来了。
示例:全局匹配,使用 match()
方法返回字符串中与表达式匹配的所有内容。 b{2,5}
表示字符 b
出现2到5次。
贪婪匹配与惰性匹配
贪婪匹配 /\d{2,5}/
表示数字连续出现 2 到 5 次,会尽可能多的匹配。你如果有 6 个连续的数字,那我就要我的上限 5 个;你如果只有 3 个连续数字,那我就要3个。想要我只取 2 个,除非你只有两个。
惰性匹配 /\d{2,5}?/
表示虽然 2 到 5 次都行,当 2 个就够的时候,我也不贪,我就取两个。
对惰性匹配的记忆方式是:量词后面加个问号,问一问你知足了吗,你很贪婪吗?注意是 量词后面 量词后面 量词后面,重要的事说三遍。还是来个例子吧,?与??:
'testtest1test2'.match(/t(e)(st(\d?))/g)
的结果就是
[ 'test', 'test1', 'test2' ]
'testtest1test2'.match(/t(e)(st(\d??))/g)
的结果就是 [ 'test', 'test', 'test' ]
7种结构 -位置匹配
位置理解
位置特性
1.对于位置的理解,我们可以理解成空字符 ""。
因此,把 /^hello$/
写成 /^^hello$$$/
,是没有任何问题的。甚至 /(?=he)^^he(?=\w)llo$\b\b$/
;
2.整体匹配时,自然就需要使用 ^
和 $
7种结构 - 括号的作用
分组和分支结构
到目前为止,我们对于 match(/t(e)(st(\d?))/g)
应该完全理解了(包括 match()
的使用,全局修饰符 g
,表示字符组的 \d
和表示量词的 ?
,且也知道了这是贪婪匹配)。那么,里面的括号又表示什么意思呢?这就涉及到分组捕获了。
以日期为例,假设要匹配的格式是 yyyy-mm-dd
的:
对比这两个可视化图片,我们发现,与前者相比,后者多了分组编号,如 Group #1, 这样正则就能在匹配表达式的同时,还能得到我们想要从匹配项中捕获的分组内容。即用 ()
括起来的内容 。到此为止, t(e)(st(\d?))
涉及到的分组捕获知识点也就结束了。
再介绍下分支结构,由于操作符 |
的优先级最低,因此需要将其放于括号中:
分支结构形如 (regex1|regex2)
,字面意思,即这里可以匹配 regex1
或者 regex2
之一,捕获分组的时候也是二者之一 ,例子:
引用分组
使用相应 API 来引用分组。
提取数据
替换replace
当第二个参数是字符串时,如下的字符有特殊的含义。其中,第二个是 $&
:
反向引用分组
除了使用相应 API 来引用分组,也可以在正则本身里引用分组。但只能引用之前出现的分组,即反向引用。
以前面的日期为例,假设我们想要求分割符前后一致怎么办?即杜绝 2016-06/12
这样中间分割符不一致的情况,此时需要使用反向引用:
注意里面的 \1,表示的引用之前的那个分组 (-|\/|\.)
。不管它匹配到什么(比如 -),\1 都匹配那个同样的具体某个字符。
我们知道了 \1 的含义后,那么 \2 和 \3 的概念也就理解了,即分别指代第二个和第三个分组。需要注意:
-
\10
表示第十个分组,如果真要匹配\1
和0
的话,使用(?:\1)0
或者\1(?:0)
。 -
反向引用引用不存在的分组时候,匹配
反向引用的字符本身。例如
\2
,就匹配"\2"
。
分组括号嵌套
针对 "1231231233".match(/^((\d)(\d(\d)))\1\2\3\4$/)
的可视化形式如下,一目了然:
分组括号结合量词
分组后面有量词的话,分组最终捕获到的数据是最后一次的匹配。分组引用与反向引用都是这样 例子:
非捕获括号
如果只想要括号最原始的功能,但不会引用它,即,既不在 API 里引用,也不在正则里反向引用。
可以使用非捕获括号 (?:p)
和 (?:p1|p2|p3)
,如下代码,执行 matchAll()
,虽然有括号,但不会得到捕获的分组内容。
正则的拆分
不仅要求自己能解决问题,还要看懂别人的解决方案。代码是这样,正则表达式也是这样。如何能正确地把一大串正则拆分成一块一块的,成为了破解“天书”的关键。
这里,我们来分析一个正则 /ab?(c|de*)+|fg/
:
思考下: /^abc|bcd$/
和 /^(abc|bcd)$/
。
/^[abc]{3}+$/
和 /^([abc]{3})+$/
操作符优先级
操作符转义
所有操作符都需要转义
^、$、.、*、+、?、|、\、/、(、)、[、]、{、}、=、!、:、- ,
到这里,对正则表达式也算是入了个小门了,即对正则的一些基本操作也有了了解,也能看懂别人的正则表达式,就算是copy也能灵活的改动了。
简单实用的正则测试器
效果图:
源代码: git
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Masterminds of Programming
Federico Biancuzzi、Chromatic / O'Reilly Media / 2009-03-27 / USD 39.99
Description Masterminds of Programming features exclusive interviews with the creators of several historic and highly influential programming languages. Think along with Adin D. Falkoff (APL), Jame......一起来看看 《Masterminds of Programming》 这本书的介绍吧!