正则表达式核心概念和实践
正则表达式核心概念和实践
正则表达式核心就是对两个东西的匹配:
- 字符;
- 位置;
对字符的理解一般大家都有理解,毕竟他是看得到摸得着的,接下来我对对位置的匹配这一个抽象的概念进行描述。
对位置的理解
要记住,空格不算位置,
'' !== ' '
, 空串才算位置''
,也就是str.length === 0
。
let str7 = 'Hello World!'
,对于这串字符他的位置都有哪些:
由这个图我们也可以发现中间的空格并不算位置。
既然空串就算做位置的话,那么我可以发现一个特征就是,无限位置的累加并不会对字符有什么影响,这对后面写正则有很多用处。
所以在正则中也是这么理解,let RE = ^^^^|$$$
,多个相同位置的匹配,匹配的也就是同一个地方,他并不会变出来多个开头,或者多个结尾。
对位置的匹配
-
\b, \B
-
\b
代表字符和非字符之间的位置,就是\w
和\W, ^, $
的间隔的位置, 其中\w
代表的是[0-9a-zA-Z_]
,\w
代表的就是\W
取反:寻找
\b
的技巧: 先找到\W, ^, $
, 然后看\W, ^, $
旁边有没有字符\w
, 他们俩之间的位置就是\b
-
\B
代表非字符间隔的位置,就是和\b
取反,除了\b
的位置,其他的都是\B
的位置:
-
-
^, $
-
在没有修饰符
m(multiline)
的情况下,代表的是所有字符(也就是即使有换行,他也把所有字符看作为一行)的首和尾,也就是不管怎么回车换行找到最后面作为$
: -
在有修饰符
m(multiline)
的情况下,代表的是每一行的首尾:
-
-
(?=RE), (?!RE)
-
正向先行断言,负向先行断言都需要写在
()
里,不然不会生效; -
(?=RE)
正向先行断言,代表的含义是这个位置的后面必须满足RE
表达式,或者理解为满足RE
表达式的前面的位置;比如:
(?=([0-9]|abc))
的含义是这个位置的后面要满足([0-9]|abc)
,也就是这个位置的后面要是数字或者(abc
字符串)---
这个abc
是一个整串: -
(?!RE)
负向先行断言,代表的含义是这个位置的后面必须不能满足RE
表达式,或者理解为不能满足RE
表达式的前面的位置;比如:
(?!=([0-9]|abc))
的含义是这个位置的后面必须不能满足([0-9]|abc)
,也就是这个位置的后面不能是数字或者(abc
字符串)---
这个abc
是一个整串:
-
-
(?<=RE),(?<!RE)
-
正向后行断言,负向后行断言都需要写在
()
里,不然不会生效; -
(?<=RE)
正向后行断言,代表的含义是这个位置的前面必须满足RE
表达式,或者理解为满足RE
表达式的后面的位置;比如:
(?<=([0-9]|abc))
的含义是这个位置的前面要满足([0-9]|abc)
,也就是这个位置的前面要是数字或者(abc
字符串)---
这个abc
是一个整串: -
(?<!RE)
负向后行断言,代表的含义是这个位置的前面不能满足RE
表达式,或者理解为不能满足RE
表达式的后面的位置比如:
(?<!([0-9]|abc))
的含义是这个位置的前面不能满足([0-9]|abc)
,也就是这个位置的前面不能是数字或者(abc
字符串)---
这个abc
是一个整串:
-
实践
千分位匹配
这里就是对位置的匹配的应用。
最前面的\B
是防止当 str = '100'
然后将开头也匹配到,变成 ',100'
。
这里也可以写成 (?!^)
,也就是这个位置的后面不能是开头, 这里怎么理解呢?
我们先来看(?=^)
,这个位置的后面必须是^
,前文我们描述过,多个同个位置的叠加还是同一个位置(对同一个位置的不同描述)。
那么什么位置的后面是^
呢,那就是开头的前面的位置,开头的前面的位置还是开头,例如:'' + '' + '100' === '100'
:
既然理解了(?=^)
,那就再来看(?!^)
,这里代表当前位置的后面不能是^
,除了开头的后面是开头之外,其他任意地方的位置的后面都不可能是开头:
这里有没有人在想为什么不能[^^]
,或者 [^(?=^)]
,这是因为[]
作为字符组符号,里面写正则表达式只能匹配字符,如果匹配位置是不会生效的。
校验密码格式
综合应用,对位置的匹配和对字符的匹配。
验证密码问题:
密码长度 6-12
位,由数字、小写字符和大写字母组成,必须至少包括 以上2
种类型的字符。
这里我们分步求解:
-
密码长度
6-12
位,由数字、小写字符和大写字母组成,const RE1 = /[0-9a-zA-Z]{6,12}/
: -
密码中必须包含数字:
-
匹配一个位置,这个位置的后面必须包含数字,且这个位置要在开头,因为我们需要这个位置的后面包含所有的字符串:
-
首先怎么写一个匹配开头位置的正则,前文我们知道,字符串的开头是用
^
表示,然后^
的前面其实也是^
,这里我们假设(REForLocation)
是一个匹配位置的正则。那么
(REForLocation)^
就代表(REForLocation)
匹配的位置是字符串的开头;把位置的匹配写在 ^ 之前的话意味着, 可以匹配后续所有的字符串 (?=RE)^ —> 这个位置的后面必须满足 RE —> 整个字符串必须满足 RE
-
接下来写一个包含数字的正则:
.*\d
; -
然后结合正向先行断言,来让这个位置的后面包含数字,最终得到
const RE2 = /(?=.*\d)^/
,也就是说开头位置的后面必须包含数字;
-
-
写一个密码长度
6-12
位,由数字、小写字符和大写字母组成,且密码必须包含数字的正则(我们只用把前文所说的两个正则合并就行RE1 + RE2
),const containNumRE = /(?=.*\d)^[0-9a-zA-Z]{6,12}$/
;
-
-
那么至少包含两种字符又怎么描述呢?
- 如何表示至少包含数字:
/(?=.*\d)/
这个位置的后面必须包含数字 - 如何表示至少包含 a-z:
/(?=.*[a-z])/
这个位置的后面必须包含[a-z]
- 如何表示至少包含 A-Z:
/(?=.*[A-Z])/
这个位置的后面必须包含[A-Z]
因为位置是可以叠加的, 这也就着可以类似于写 ‘and’ 条件拼接:
- 同时包含数字和小写字母
/(?=.*\d)(?=.*[a-z])/
- 同时包含数字和大写字母
/(?=.*\d)(?=.*[A-Z])/
- 同时包含小写字母和大写字母
/(?=.*[a-z])(?=.*[A-Z])/
- 同时包含数字、小写字母和大写字母, 这个要结合最后一个
[0-9a-zA-Z]
来看, 没有[0-9a-zA-Z]
的话, 同时包含两种, 那就意味着只要有这两种, 然后加其他的任意都可以 - 以上的 4 种情况是或的关系(实际上,可以不用第 4 条)
- 如何表示至少包含数字:
-
最终得到正确答案,密码长度 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符:
注意
[]是不能匹配位置
[]
算作字符组,里面写匹配位置的正则不生效,比如[\b], [$], [\B]
等,既然[]
这个名字叫做字符组的话,那里面肯定写的就是对字符的匹配的正则表达式了:
| 分支符写在哪
示例,判断一个字符串是否包含name
或者password
关键字:
-
错误示范
const containNameOrPwdRE = (?=.*name)|(?=.*password)^.*$
这里被分支符给分成两个部分,
(?=.*name)
,(?=.*password)^.*$
他这里会先去匹配
(?=.*name)
这个正则,也就是先去找后面是name
整串的位置。这个找完之后如果不满足,就会去走下一个分支。
也就是匹配下一个正则
(?=.*password)^.*$
,开头位置后面包含password
整串的这个位置,结尾是任意字符的这个整串,也就是说这个字符串必须包含password
,这里显然和我们预期不符: -
正确示范
const containNameOrPwdRE = ((?=.*name)|(?=.*password))^.*$
这里被分支符给分成两个部分,前面一个分支不满足,就会匹配后面一个分支,
(?=.*name)^.*$
,(?=.*password)^.*$