0

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.

HPierce
  • 7,249
  • 7
  • 33
  • 49
  • Nested foo() is executed on array before you log anything. Try logging before the nested `foo()` – charlietfl Jun 21 '16 at 01:53
  • 3
    No, there's nothing special about recursion, and this has nothing to do with scope. Every call creates its own `ar` variable. It's just that they all reference the same array object, and `pop` mutates it. – Bergi Jun 21 '16 at 01:57
  • By they time you start logging 'Function recursion ends...' what "is" ar given what the console tells you? – JonSG Jun 21 '16 at 01:58
  • 1
    The question that this is said to duplicate does not answer his question. A better answer is found here: http://stackoverflow.com/a/3638034/1455010 – Nate Jun 21 '16 at 02:00
  • 1
    @4castle OK I've reopened so you can post your answer. Please don't disappoint me, though :-) – Bergi Jun 21 '16 at 02:01
  • @Bergi, FWIW the duplicate you marked is likely the solution I will end up using. But 4castle's answer cleared up my confusion - close away! – HPierce Jun 21 '16 at 02:16

2 Answers2

1

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]);
4castle
  • 32,613
  • 11
  • 69
  • 106
  • 2
    You haven't addressed the OP's expectation that "variables passed to a function can only modify them locally". The answer should explictly mention that each invocation's `ar` argument references *the same* array. As was commented directly under the question, this really isn't about recursion or scope, it's about array references not creating copies of the array. – nnnnnn Jun 21 '16 at 02:08
  • "you are perhaps expecting that the operations within `foo` can't modify the parameters you give it." Boom. Right there. That said, removing the last element of the array was an intended part of how the function behaves. So copying the array as the question was marked as a dupe was a better solution. – HPierce Jun 21 '16 at 02:13
  • @nnnnnn I alluded to it, but now I've put that explicitly in the answer. I'm glad it answered the question sufficiently anyway though. – 4castle Jun 21 '16 at 02:33
0

As @4castle suggests, if you want to have a more durable view of the array at each iteration, you can clone ar with slice(). Alternatively, you might also be able to rework your function foo() to allow you more room to work with the "current" ar and ele.

var foo = function(ar) {
  console.log('Given Array: ' + ar.toString());
  
  if(ar.length === 0){
    console.log('recursion ended');
    return;
  }
  
  var ele = ar.pop();
  console.log('Array Now: ' + ar.toString() + ' After Popping: ' + ele);
  console.log(" ");

  foo(ar);
}

foo([1,2,3]);
JonSG
  • 10,542
  • 2
  • 25
  • 36