from https://github.com/qdlaoyao/js-regex-mini-book
正则要么匹配字符,要么匹配位置
位置匹配
什么是位置?
位置 (锚)就是每个字符之间的位置 _h_e_l_l_o_
比如这个字符中的 _
代表的就是一个个的位置
ES5
中一共有 6 个 锚
^
$
\b
\B
(?=p)
(?!p)
,把它们弄成一个正则
/^$\b\B(?=p)(?!p)/
^
$
^
匹配开头,在多行里面匹配多个行开头
$
匹配结尾,在多行里面匹配多个行结尾
比如我们把字符串开头结尾替换成 #
,这里也可以看到位置是可以替换成字符的
1
2
3
4
5
6
7
`hello`.replace(/^|$/g, "#");
// "#hello#"
"I\nlove\njavascript".replace(/^|$/gm, "#");
// "#I#
// #love#
// #javascript#"
\b
\B
\b
代表的是单词的边界 (其实就是 \w
和 $
^
\W
的边界),简单来说就是单词和单词之间(包括开始和结束)的位置
我们考察下下面的情况
1
2
"[JS] Lesson_01.mp4".replace(/\b/g, "#");
// [#JS#] #Lesson_01#.#mp4#
\B
是 \b
的反面,所以上面的例子其他剩下的位置都是 \B
,就是 \w
和 \w
, \W
和 \W
,\W
和 $
^
简单来说就是单词和单词,非单词和非单词(包括开始和结束)之间的位置
1
2
"[JS] Lesson_01.mp4".replace(/\B/g, "#");
// #[J#S]# L#e#s#s#o#n#_#0#1.m#p#4
(?=p)
(?!p)
(?=p)
代表的是 p
模式前面的 位置,或者说 该位置 后面需要匹配 p
,正向先行断言
比如 (?=l)
表示 l
字符前面的位置
1
2
"hello".replace(/(?=l)/g, "#");
// he#l#lo
而 (?!p)
代表的是非 p
模式前面的位置,或者说这个位置后面不是 p
,负向先行断言
1
2
"hello".replace(/(?!l)/g, "#");
// #h#ell#o#
位置的特性
对于位置我们可以理解成空字符
比如 hello
等价于 "hello" == "" + "h" + "" + "e" + "" + "l" + "" + "l" + "" + "o" + "";
也等价于 "hello" == "" + "" + "hello"
所以我们把 /^hello$/
写成了 /^^^hello$$$/
完全没有问题
设置写成了 /(?=he)^^he(?=\w)llo$\b\b$/
也是没有问题
案例分析
不匹配任何东西的正则
/.^/
,匹配某个在开始前的字符,不存在
数字的千位分隔符表示法
先匹配一个,使用 /(?=\d{3}$)/
,代表查找某个位置,它后面是 \d{3}$
三个数组跟着结束符
1
2
"12345678".replace(/(?=\d{3}$)/, ",");
// "12345,678"
接下来匹配所有的位置,要求 \d{3}
至少出现一次,所以使用 +
这里表示的是 可以是某个位置,它后面跟着 3 个数字和结束符,6 个数字和结束符,9 个数字和结束符 等等的情况 (一开始自己理解错了)
1
2
"12345678".replace(/(?=(\d{3})+$)/g, ",");
// "12,345,678"
不过发现下面的出错情况,所以继续优化
1
2
3
4
5
"123456789".replace(/(?=(\d{3})+$)/g, ",");
// ",123,456,789"
"123456789 123456789".replace(/(?=(\d{3})+$)/g, ",");
// 123456789 ,123,456,789"
下面是优化后的,表示要替换成 ,
的位置,这个位置前面的位置不能是 非单词之间的边界(因为 ^
和 \w
之间是 \b
,这个位置是不能放 ,
替换的,所以我们用 \B
)。最后也替换成了 \b
代表从 单词边界 的位置倒叙算起
1
2
3
4
5
"123456789".replace(/\B(?=(\d{3})+\b)/g, ",");
// ",123,456,789"
"123456789 123456789".replace(/\B(?=(\d{3})+\b)/g, ",");
// "123,456,789 123,456,789"
格式化
千分符表示法一个常见的应用就是货币格式化
1888
格式化成:$ 1888.00
1
2
3
4
5
6
7
8
9
function format(num) {
return num
.toFixed(2)
.replace(/\B(?=(\d{3})+\b)/g, ",")
.replace(/^/, "$$ ");
}
format(1888);
// "$ 1,888.00"
验证密码问题
密码长度 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符
简化
不考虑至少两个字符,我们简单写出来
/^[0-9a-zA-Z]{6,12}$/
如果要求必须有数字,我们可以写出下面的正则
/(?=.*[0-9])/
所以整个正则变成了 /(?=.*[0-9])^[0-9a-zA-Z]{6,12}$/
那要求同时包含数字或者小写字母,那就变成了
/(?=.*[0-9])(?=.*[a-z])^[0-9a-zA-Z]{6,12}$/
解答
- 同时包含数字和小写
- 同时包含数字和大写
- 同时包含小写和大写
最终的解答就是
/((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[A-Z])(?=.*[a-z]))^[0-9a-zA-Z]{6,12}$/
另外一种方法
必须有两个字符,那就是后面不能全是一种字符,所以我们可以写出另外一种解法
首先不能全是数字的写法是
/(?![0-9]{6,12}$)^[0-9a-zA-Z]{6,12}$/
那三个都写上的话就是
/(?![0-9]{6,12}$)(?![a-z]{6,12}$)(?![A-Z]{6,12}$)^[0-9a-zA-Z]{6,12}$/