理解正则中的(?=p)、(?!p)、(?<=p)、(?<!p)

6,672 阅读3分钟

前言

一直不是很理解正则中的(?=p)、(?!p)、(?<=p)、(?<!p),按照官方的介绍晦涩难懂

比如:(?:pattern):匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用

刚看到这句话的我..

这也太难懂了吧。

另一种解释

// 前瞻:
exp1(?=exp2) 查找exp2前面的exp1
// 后顾:
(?<=exp2)exp1 查找exp2后面的exp1
// 负前瞻:
exp1(?!exp2) 查找后面不是exp2exp1
// 负后顾:
(?<!exp2)exp1 查找前面不是exp2exp1

//举例
"中国人".replace(/(?<=中国)人/, "rr") // 匹配中国人中的人,将其替换为rr,结果为 中国rr
"法国人".replace(/(?<=中国)人/, "rr") // 结果为 法国人,因为人前面不是中国,所以无法匹配到

这种理解方式对于简单的例子还能理解,但是如果遇到 /(?!\b)(?=(\d{3})+\b)/g 这种表达式呢, (?!exp2)前面没有表达式怎么破?

正文

要理解(?=p)跟(?!p),要知道正则里面有个 匹配位置 这一说。

正则的匹配模式分为两种

  1. 匹配字符
  2. 匹配位置

匹配字符

匹配字符我相信都能理解,直接匹配字符串中的字符

var regex = /hello/;
console.log( regex.test("hello") );
// => true

匹配字符还分为纵向匹配,以及横向匹配

//横向匹配,一个正则可匹配的字符串的长度不是固定的
var regex = /ab{2,5}c/g;
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log( string.match(regex) );
// => ["abbc", "abbbc", "abbbbc", "abbbbbc"]

//纵向匹配,一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种可能
var regex = /a[123]b/g;
var string = "a0b a1b a2b a3b a4b";
console.log( string.match(regex) );
// => ["a1b", "a2b", "a3b"]

好了,匹配字符就介绍到这里,这不是本文的重点,重点是匹配位置

匹配位置

什么是位置

位置是相邻字符之间的位置,可以理解成空字符 ""。比如,下图中箭头所指的地方

"hello" == "" + "h" + "" + "e" + "" + "l" + "" + "l" + "" + "o" + ""

(?=p)、(?!p)

(?=p)匹配的是什么呢,匹配的是 p 前面的位置,或者说,该位置后面的字符要匹配 p

比如 (?=h),表示 "h" 字符前面的位置,例如:

var result = "hello".replace(/(?=h)/g, '#');
console.log(result);
// => "#hello"

而 (?!p) 就是 (?=p) 的反面意思,比如:

var result = "hello".replace(/(?!h)/g, '#');
console.log(result);
// => "h#e#l#l#o#"          除了h前面没有加#,其他的位置都加上了#

正则

现在再回过头来看下那个正则:var regex = /(?!^)(?=(\d{3})+$)/g

它是用来将数字用千位分隔符表示,比如把 "12345678",变成 "12,345,678

var regex = /(?!^)(?=(\d{3})+$)/g;
var result = "12345678".replace(regex, ',')
console.log(result);
// => "12,345,678"
result = "123456789".replace(regex, ',');
console.log(result);
// => "123,456,789"

分析

正则 /(?!^)(?=(\d{3})+$) 分为 (?!^) 以及 (?=(\d{3})+$)

(?=(\d{3})+$)(?=\d{3}$) 匹配 \d{3}$ 前面的位置。而 \d{3}$ 匹配的是目标字符串最后那 3 位数字

var result = "12345678".replace(/(?=\d{3}$)/g, ',')
console.log(result);
// => "123456,789"

因为逗号出现的位置,要求后面 3 个数字一组,也就是 \d{3} 至少出现一次。此时可以使用量词 +:

var result = "12345678".replace(/(?=(\d{3})+$)/g, ',')
console.log(result);
// => ",123,456,789"

(?!^) :匹配前面不是开头的位置,如果不加上的话,会 "123456789",变成 ",123,456,789",开头会有一个逗号

(?<=p)、(?<!p)

说到这里,(?<=p)跟(?<!p)也就不难理解了

//(?<=p)表示p表达式后面的位置
var result = "hello".replace(/(?<=h)/g, '#');
// => "h#ello"

而 (?!p) 就是 (?=p) 的反面意思,比如:
//(?<!p)是(?<=p)的反面意思,匹配不是p表达式后面的位置
var result = "hello".replace(/(?<!h)/g, '#');
// => "#he#l#l#o#"

正则速查表

p1-jj.byteimg.com/tos-cn-i-t2…