The key point here is that the function calls itself with different parameters in the else case. Suppose we make this first call
var ans = rangeOfNumbers(2, 4);
Inside this function, the if test fails so the else part is executed, making a recursive call
rangeOfNUmbers(2, 3)
Inside this function, again the test fails, so there’s another recursive call
rangeOfNumbers(2,2)
Now, the recursion stops because the if test succeeds and returns [2]. Where does this function return the value to? To its caller, of course. Who called rangeOfNumbers(2,2)? It’s called from inside the function rangeOfNumbers(2,3). The returned value is assigned to numbers and then endNum is added. What is endNum here? 3. So we’re returning [2,3] to the caller, which is from rangeOfNumbers(2,4). The same process is repeated, causing [2, 3, 4] is returned to the caller.
Here’s a simple illustration of the concept.