Eita!

Na verdade, como eu estava com o tempo apertado, só tive tempo para vasculhar minha biblioteca de snippets.

Dei uma otimizada no exemplo, de acordo com o que foi proposto, e comentei:
var strNumber = '12345678';
while (strNumber.match(/^\d{4}/)){ // enquanto houver 4 dígitos sem separador de milhar
strNumber = strNumber.replace(/(\d)(\d{3}(\.|$))/, '$1.$2'); // separar o dígito de milhar dos dígitos de centena com o ponto, de trás para frente!
}
alert(strNumber);
De trás para frente? Sim.
(\d)(\d{3}(\.|$)) = (\d)(\d{3}\.)|(\d)(\d{3}$)
Como de início não haverá ponto, a ER irá olhar de trás pra frente (
$
). Depois, irá fazer o mesmo procedimento com o ponto, até não haver mais possibilidades.
Ah,
$1.$2
é o dígito de milhar acrescido do ponto e dos dígitos de centena, é a substituição que determina o fim da iteração.

Exemplos:
123 = 123 // não casa o match
1234 = 1.234 // casa o match e o replace, pelo final ($)
1234567 = 1.234.567 // casa o match e o replace, primeiro pelo final ($) e depois pelo ponto (separador de milhar) que fora inserido na primeira iteração do while
Enfim, espero que tenham captado a idéia.

Pra outra ER só haviam duas diferenças. A primeira é a ER do match, que podia ser
\d{4}
apenas. A segunda a vírgula no grupo que casa as centenas. Ela estava ali pois o snippet que eu tinha aqui permitia ponto e vírgula como separadores de milhar.
[]s

Até mais