Chalk it up to excessive cleverness on the part of the JavaScript designers. They decided []
means nothing (a null construct, no effect on the match), and [^]
means not nothing--in other words, anything including newlines. Most other flavors have a singleline/DOTALL mode that allows .
to match newlines, but JavaScript doesn't. Instead it offers [^]
as a sort of super-dot.
That didn't catch on, which is just as well. As you've observed, it's thoroughly incompatible with other flavors. Everyone else took the attitude that a closing bracket right after an opening bracket should be treated as a literal character. And, since character classes can't be nested (traditionally), the opening bracket never has special meaning inside one. Thus, [][]
is simply a compact way to match a square bracket.
Taking it further, if you want to match any character except ]
, [
or ^
, in most flavors you can write it exactly like that: [^][^]
. The closing bracket immediately after the negating ^
is treated as a literal, the opening bracket isn't special, and the second ^
is also treated as a literal. But in JavaScript, [^][^]
is two separate atoms, each matching any character (including newlines). To get the same meaning as the other flavors, you have to escape the first closing bracket: [^\][^]
.
The pond gets even muddier when Java jumps in. It introduced a set intersection feature, so you can use, for example, [a-z&&[^aeiou]]
to match consonants (the set of characters in the range a
to z
, intersected with the set of all characters that are not a
, e
, i
, o
or u
). However, the [
doesn't have to be right after &&
to have special meaning; [[a-z]&&[^aeiou]]
is the same as the previous regex.
That means, in Java you always have to escape an opening bracket with a backslash inside a character class, but you can still escape a closing bracket by placing it first. So the most compact way to match a square bracket in Java is []\[]
. I find that confusing and ugly, so I often escape both brackets, at least in Java and JavaScript.
.NET has a similar feature called set subtraction that's much simpler and uses a tighter syntax: [a-z--[aeiou]]
. The only place a nested class can appear is after --
, and the whole construct must be at the end of the enclosing character class. You can still match a square bracket using [][]
in .NET.