Jakobimatrix
Jakobimatrix

Reputation: 47

Javascript undesired shares variables between different fuctions

First of all I am not too familiar with javascript. Actually I try to avoid it if possible but the following behaviour bugs me:

The following is a minimal example:

I have a 2 dimensional Array (an array of objects) and I decided to split the decomposition of it into two functions, for better readability. The first function (foo(), foo2()) seperates the different objects, and the second function (bar()) goes through an individual object (in this case just prints the components).

To loop through the array I use a for loop. Now I expected (coming from c++) no difference between useing foo() and foo2(). Since bar() is a seperate function with a seperate scope (or is it??). BUT useing foo() I get the output: 123 Useing foo2() I get the desired: 123456

Now the only difference is the name of the index within the loop. foo() uses the same index as bar() foo2() uses a different index as bar()

So what am I missing here?

Now I understand that giving an object / array to a function will result in a call by reference. But why does this affect the index of the loop?

I have the suspicion that javascript passes both "a" and "i" rather than passing the object behind "a[i]" to bar(a[i]).

If so, why? And how can I avoid this?

    // decompose the different objects
    function foo(a){
      for(i = 0; i < a.length; i++){
        bar(a[i]);
      }
    }

    // decompose the different objects
    function foo2(a){
      for(j = 0; j < a.length; j++){
        bar(a[j]);
      }
    }

    // decompose a single object
    function bar(b){
      for(i = 0; i < b.length; i++){
        document.write(b[i]);
      }
    }

    function start(){
      //2-d array
      var a = [
      [1, 2, 3],
      [4, 5, 6]
      ];
      foo(a);//decompose the array -> unexpected behaviour
      document.write("</br>");
      foo2(a);//decompose the array -> expected behaviour
    }
    //run
    start();

calling foo() I get the output: 123 expecting 123456

calling foo2() I get the desired output: 123456


edit: See for short answer @MrGeek, and for detailed Answer @Akrion Thank you both!

Lesson learned:

Upvotes: 1

Views: 70

Answers (2)

Akrion
Akrion

Reputation: 18525

In a nut shell the issue you are experiencing comes from the fact that JS handles varaibles which are not specifically declared in the execution scope with let/var as global.

function foo(a){
  for(i = 0; i < a.length; i++){
    bar(a[i]);
  }
}

In the above function the variable i is set to 0. JS would fist look in the current scope and try to find a declaration of i. If it can not it would go to the scope above it and if it is not defined there AND the use strict is not set it would define globally that variable. So now you have i already defined globally by JS. Then:

function bar(b){
  for(i = 0; i < b.length; i++){
    document.write(b[i]);
  }
}

In the bar function we get i again. This time however JS knows about it since it global defined it already. So it uses the i defined already and you get the picture form there.

The obvious fix for this is to block scope the i with var i=0 or let i=0. In your scecific case var would do just fine. Using var would limit the scope of your i just to that for loop and JS would define i in the foo function and then it would define a new i for the bar function.

let in this case would do the same and overall it can be used as a replacement for var (although many would argue about that and point out that both are still useful and let is not a panacea and a total replacement of var)

now let is different from var in a scenario like this:

var arr = []
for (var i = 1; i <= 3; i++) {
  var obj = { print: () => console.log(i) }
  arr.push(obj);
}
arr[0].print()
arr[1].print()
arr[2].print()

As you can see all of the print functions print the same exact value 4 instead of what you would expect. A simple change from var to let would give you the desired result:

var arr = []
for (let i = 1; i <= 3; i++) {
  var obj = { print: () => console.log(i) }
  arr.push(obj);
}
arr[0].print()
arr[1].print()
arr[2].print()

This is due to the fact that let preserves its lexical block scope and thus when passed into the print function it keeps its current value where var would be overwritten with the last actual value which in this case is 4.

So with if we simply change your code to use var (and print to the console):

// decompose the different objects
function foo(a) {
  for (var i = 0; i < a.length; i++) {
    bar(a[i]);
  }
}

// decompose the different objects
function foo2(a) {
  for (var j = 0; j < a.length; j++) {
    bar(a[j]);
  }
}

// decompose a single object
function bar(b) {
  for (var i = 0; i < b.length; i++) {
    console.log(b[i]);
  }
}

function start() {
  //2-d array
  var a = [
    [1, 2, 3],
    [4, 5, 6]
  ];
  foo(a); //decompose the array -> expected behaviour  
  foo2(a); //decompose the array -> expected behaviour
}
//run
start();

You will see that we get the expected result. let would do the same :)

Upvotes: 1

Djaouad
Djaouad

Reputation: 22776

Your indices are global variables, declare them with let or var to make them local to their corresponding functions and avoid this problem:

// decompose the different objects
function foo(a) {
  for (let i = 0; i < a.length; i++) {
    bar(a[i]);
  }
}

// decompose the different objects
function foo2(a) {
  for (let j = 0; j < a.length; j++) {
    bar(a[j]);
  }
}

// decompose a single object
function bar(b) {
  for (let i = 0; i < b.length; i++) {
    document.write(b[i]);
  }
}

function start() {
  //2-d array
  var a = [
    [1, 2, 3],
    [4, 5, 6]
  ];
  foo(a); //decompose the array -> expected behaviour
  document.write("</br>");
  foo2(a); //decompose the array -> expected behaviour
}
//run
start();

Upvotes: 3

Related Questions