In short: it doesn’t.
js> var re1 = new RegExp("", "g");
js> var str1 = "a";
js> str1.match(re1)
["", ""]
js> var re2 = new RegExp("", "g");
js> var str2 = "a";
js> re2.exec(str2)
[""]
js> re2.exec(str2)
[""]
Each of these [""]s is a result, as exec is meant to be called in a loop and retrieve both all matches and all groups, as in:
var pairs = /(.).*?\1/g;
var input = "._.  ^.^ -__-";
var match;
while (match = pairs.exec(input)) { // exec() returns null on /g regexes when there are no more matches
    console.log(match);
}
A more interesting question might be why re2.lastIndex doesn’t change after matching, so you can get the same match forever and it’ll never be null. Well, that’s just because it advances by the length of the match for each match, and here the length is zero.