У вас один процесс/поток (с опереденными допущениям) для выполнения всего этого. В один момент времени может делаться что-то одно (только webworkers могут работать паралельно с основным кодом). Движок JS мультиплексирует выполнение кода.
setTimeout размещает своего рода заявку на отложенный вызов, по сути он ждет ивента что время нужное наступило и только тогда запускает функцию. Пока этот ивент не произошел, выполняется весь нижестоящий код. Если нижестоящий код блокирует выполнение нашего события, то может случиться ситуация при которой событие наступает позже ожидаемого времени. Именно по этому следует избегать подобного кода и все тежелые вычисления ложить в setTimeout/webworkers. Так что вот такой код:
var start = Date.now();
setTimeout(function () {
console.log('~0 ms should pass, %d ms really pass', Date.now() - start);
}, 0);
while(Date.now() - start < 500) {
// loop for 0.5 sec
}
console.log('%d ms pass', Date.now() - start);
выведет:
500 ms pass
~0 ms should pass, 501 ms really pass
Для более глубокого понимания стоит почитать про
Event Loop.