标题挺有意思吧,一个来自正则,一个来自 CSS。前者是正则断言匹配,后者是 CSS 选择器匹配。接下来看看相似性分析。
既然都是用来匹配的,那么,如果二者在一些地方有什么相似之处,应不足为奇。我发现 (?<=p) 与 :nth-child() 就有很强的相似性。这里容我慢慢道来。本文假设读者对 CSS 选择器更熟悉些,所以下面的例子都是先 CSS,后正则。
假设页面上有 9 个 li,让所有元素的字都变成红色。此处不会使用 :nth-child 的,直接用标签选择器就行了。
li{
color: red;
}
同样的,正则这边也不需要使用 (?<=p)。
'123456789'.replace(/./g, '*')
// "*********"
匹配首元素,在CSS 可以用 :first-child 选择器:
li:first-child{
color:red;
}
而正则可以使用 ^ 位置匹配符:
'123456789'.replace(/^./g, '*')
// "*23456789"
而我们知道 :first-child 其实是 :nth-child 的特例:
li:nth-child(1){
color:red;
}
相应的,正则其实也可以用 (?<=p):
'123456789'.replace(/(?<=^)./g, '*')
// "*23456789"
(?<=^) 断言其实匹配的是一个位置,^ 之后的位置,当然还是开头。可以参考《JS正则迷你书》对位置的讲解。
CSS 里要匹配第 3 个元素,:nth-child(3) 即可
li:nth-child(3){
color:red;
}
而正则这边呢?这里需要转个弯,要匹配第 3 个,其实是说该字符前面还有 2 个:
'123456789'.replace(/(?<=^.{2})./g, '*')
// "12*456789"
我们知道 :nth-child 选择器厉害之处是在于它支持 an+b 表达式,比如匹配前 3 个:
li:nth-child(-n+3){
color:red;
}
正则这边,要匹配的字符前面有 0 到 2 个字符,
'123456789'.replace(/(?<=^.{0,2})./g, '*')
// "***456789"
CSS 这边使用 2n+1:
li:nth-child(2n+1){
color:red;
}
正则这边,要匹配的字符前面有 0、2、4...个字符,
'123456789'.replace(/(?<=^(.{2})*)./g, '*')
// "*2*4*6*8*"
类似的匹配偶数位,即要匹配的字符前面有 1、3、5...个字符:
'123456789'.replace(/(?<=^(.)(.{2})*)./g, '*')
// "1*3*5*7*9"
比如 CSS 这边使用 4n+3
li:nth-child(4n+3){
color:red;
}
正则这边变成了:
'123456789'.replace(/(?<=^(.{4})*.{2})./g, '*')
// "12*456*89"
即:要匹配的字符前面还有 4n+2 个字符
我们知道 :nth-child 还有对应的 :nth-last-child。它的意思是,与 :nth-child 相反,不是从前往后数,而是从后面向前数,比如要匹配后 3 个 li:
li:nth-last-child(-n+3){
color:red;
}
正则这边呢?(?<=p) 表示 p 后面的位置,与之相对的是 (?=p),表示 p 前面的位置。因此要匹配后 3 个字符:
'123456789'.replace(/.(?=.{0,2}$)/g, '*')
// "123456***"
更多的,与前几条类似,这里就不写了。
在 CSS 中,要匹配除了第 3 个元素之外的所有元素,可以配合使用 :not选择器来实现“补集”。
li:not(:nth-child(3)){
color:red;
}
(?<=p) 表示 p 后面的位置。而 (?<!p) 有点绕,它表示所有位置中,不是 p 后面的那个位置,或者说当下位置的前面不是 p。
'123456789'.replace(/(?<!^.{2})./g, '*')
// "**3******"
:nth-child 除了取补,还可以取交集,比如匹配第 3-7 个元素
li:nth-child(n+3):nth-child(-n+7){
color:red;
}
(?<=p) 也可以支持交集的
'123456789'.replace(/(?<=^.{2,})(?<=^.{0,6})./g, '*')
// "12*****89"
交并补,还有并集,CSS 很简单:
li:nth-child(3),
li:nth-child(7){
color:red;
}
正则呢,有 | 就是来做这个:
'123456789'.replace(/(?<=^(.{2}|.{6}))./g, '*')
// "12*456*89"
自此,这么一条条看下来,发现了二者确实有多相似之处。这种跨界比较,我觉得很有趣!

