Questo è un buon compromesso. La ricorsione può far girare la testa. Il motivo per cui non è definito è perché non tutte le iterazioni restituiscono un valore, e per quelle che non vengono definite indefinite - lo stesso come se si impostasse una variabile su qualsiasi funzione che non restituisce un valore.
Si confonde però con la ricorsione, perché il valore di ritorno che si sta vedendo in questo caso è dalla prima chiamata e dall'ultima iterazione completata. A differenza di una normale chiamata di metodo in cui return interrompe l'esecuzione del metodo - rimandandolo da dovunque sia arrivato, la ricorsione ha ancora la sua strada attraverso lo stack delle chiamate, restituendo tutti i valori che deve restituire, incluso undefined, nel retro ordine in cui sono stati chiamati. Quindi in realtà sta dando alla tua console.log call quattro valori di ritorno: 15, indefiniti, indefiniti, indefiniti.
Poiché sincrono, console.log non può essere eseguito fino a quando non viene eseguito il metodo chiamato. Ciò che emette è l'ultimo valore che ottiene, o indefinito. Se si attiva un ritorno dopo la chiamata al metodo nel blocco else, vedrai che ottieni 5 o il valore di acc dopo la prima iterazione della funzione.
var multiplyT = function(a, b, acc) {
if (b == 0) {
console.log("BASE CASE: ", acc);
return acc;
} else {
b--;
acc = acc + a;
console.log("NOT THE BASE CASE: ", a,b,acc);
multiplyT(a, b, acc);
return acc;
}
}
console.log(multiplyT(5,3,0));