To better understand let's apply the three expressions plus a capturing group and analyse each behaviour.
()
capturing group - the regex inside the parenthesis must be matched and the match create a capturing group
(?:)
non-capturing group - the regex inside the parenthesis must be matched but does not create the capturing group
(?=)
positive lookahead - asserts that the regex must be matched
(?!)
negative lookahead - asserts that it is impossible to match the regex
Let's apply q(u)i
to quit.
q
matches q and the capturing group u
matches u.
The match inside the capturing group is taken and a capturing group is created.
So the engine continues with i
.
And i
will match i.
This last match attempt is successful.
qui is matched and a capturing group with u is created.
Let's apply q(?:u)i
to quit.
Again, q
matches q and the non-capturing group u
matches u.
The match from the non-capturing group is taken, but the capturing group is not created.
So the engine continues with i
.
And i
will match i.
This last match attempt is successful.
qui is matched.
Let's apply q(?=u)i
to quit.
The lookahead is positive and is followed by another token.
Again, q
matches q and u
matches u.
But the match from the lookahead must be discarded, so the engine steps back from i
in the string to u.
Given that the lookahead was successful the engine continues with i
.
But i
cannot match u.
So this match attempt fails.
Let's apply q(?=u)u
to quit.
The lookahead is positive and is followed by another token.
Again, q
matches q and u
matches u.
But the match from the lookahead must be discarded, so the engine steps back from u
in the string to u.
Given that the lookahead was successful the engine continues with u
.
And u
will match u. So this match attempt is successful.
qu is matched.
Let's apply q(?!i)u
to quit.
Even in this case lookahead is positive (because i
does not match) and is followed by another token.
Again, q
matches q and i
doesn't match u.
The match from the lookahead must be discarded, so the engine steps back from u
in the string to u.
Given that the lookahead was successful the engine continues with u
.
And u
will match u.
So this match attempt is successful.
qu is matched.
So, in conclusion, the real difference between lookahead and non-capturing groups is all about if you want just to test the existence or test and save the match.
But capturing groups are expensive so use it judiciously.