HPierce HPierce - 5 months ago 14
Javascript Question

Do recursive functions have special scoping rules?

I'm working on a problem that requires a recursive function and have noticed that nested executions seem to be modifying the parent's parameters:

var foo = function(ar) {
console.log('Calling function - Array: ' + ar.toString());
if(ar.length > 1){
var ele = ar.pop();
foo(ar);
console.log('Function recursion ends - Array: ' + ar.toString() + ' Popped: ' + ele);
return;
} else {
console.log('Function ends - Array: ' + ar.toString());
return;
}
}

foo([1,2,3]);


outputs (indentation mine):


/*
Calling function - Array: 1,2,3
Calling function - Array: 1,2
Calling function - Array: 1
Function ends - Array: 1
Function recursion ends - Array: 1 Popped: 2
Function recursion ends - Array: 1 Popped: 3 <-- What happened to 2?
*/



This seems odd - because I've invoked the function with
[1,2,3]
and I'd expect the first iteration of the function to still maintain all of the elements provided to it between both
ar
and
ele
- but instead when the function concludes, only
1
remains in the provided array - what happened to
2
? Did the nested execution
pop
it out of the first execution's variable?

My understanding of function scope in JavaScript would say that variables passed to a function can only modify them locally and do not export them back to the global/parent's scope as seen here:

var bar = 'bar';

function doBar(bar){
bar = 'foo';
}

doBar(bar);
console.log(bar); //Outputs 'bar';


But the output from the recursive function seems to challenge that understanding.

How can I prevent these nested executions from modifying the parent's parameter to bring back the missing
2
? Is my understanding of scoping in JavaScript wrong?





In my pathetic attempt to grasp at straws before opening this question, I've tried executing the function within a closure:

var foo = function(ar) {
console.log('Calling function - Array: ' + ar.toString());
if(ar.length > 1){
var ele = ar.pop();
(function(foo, ar){
foo(ar);
})(foo, ar)

console.log('Function recursion ends - Array: ' + ar.toString() + ' Popped: ' + ele);
return;
} else {
console.log('Function ends - Array: ' + ar.toString());
return;
}
}


But I got the same results as without using the closure - I suspect because I explictly passed in
ar
and
foo
making it no different than without the closure.

Answer

Array.prototype.pop() is a mutable operation. When you call foo recursively, that mutable operation is still in effect in the calling scope. There aren't any strange things going on with scope, it's just you are perhaps expecting that the operations within foo can't modify the internals of the parameters you give it. Arrays are passed by reference, which means that the parameters will reference the same array that the calling scope does.

Instead, you can call arr.slice(0, -1). This will return a shallow copy of the array instead of modifying the existing array. It will change how you get the last index of the array.

var foo = function(ar) {
  console.log('Calling function - Array: ' + ar.toString());
  if(ar.length > 1){
    var ar2 = ar.slice(0, -1);
    foo(ar2);
    console.log('Function recursion ends - Array: ' + ar2.toString() + ' Popped: ' + ar.slice(-1));
    return;
  } else {
    console.log('Function ends - Array: ' + ar.toString());
    return;
  }
}

foo([1,2,3]);