kembali ke pelajaran

Pasukan-pasukan fungsi

Kode berikut membuat array dari shooters.

Setiap fungsi diinginkan untuk mengeluarkan angkanya sendiri. Tetapi ada yang salah???

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let shooter = function() { // fungsi shooter
      alert( i ); // seharusnya mengeluarkan angkanya sendiri
    };
    shooters.push(shooter); // and add it to the array
    i++;
  }

  // ...and return the array of shooters
  return shooters;
}

let army = makeArmy();

// semua penembak menunjukkan 10 bukannya angka mereka 0, 1, 2, 3...
army[0](); // 10 dari nomor penembak 0
army[1](); // 10 dari penembak nomor 1
army[2](); // 10 ...dan seterusnya.

Mengapa semua penembak menunjukkan nilai yang sama?

Perbaiki kode agar berfungsi sebagaimana mestinya.

Buka sandbox dengan tes.

Let???s examine what exactly happens inside makeArmy, and the solution will become obvious.

  1. It creates an empty array shooters:

    let shooters = [];
  2. Fills it with functions via shooters.push(function) in the loop.

    Every element is a function, so the resulting array looks like this:

    shooters = [
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); },
      function () { alert(i); }
    ];
  3. The array is returned from the function.

    Then, later, the call to any member, e.g. army[5]() will get the element army[5] from the array (which is a function) and calls it.

    Now why do all such functions show the same value, 10?

    That???s because there???s no local variable i inside shooter functions. When such a function is called, it takes i from its outer lexical environment.

    Then, what will be the value of i?

    If we look at the source:

    function makeArmy() {
      ...
      let i = 0;
      while (i < 10) {
        let shooter = function() { // shooter function
          alert( i ); // should show its number
        };
        shooters.push(shooter); // add function to the array
        i++;
      }
      ...
    }

    We can see that all shooter functions are created in the lexical environment of makeArmy() function. But when army[5]() is called, makeArmy has already finished its job, and the final value of i is 10 (while stops at i=10).

    As the result, all shooter functions get the same value from the outer lexical environment and that is, the last value, i=10.

    As you can see above, on each iteration of a while {...} block, a new lexical environment is created. So, to fix this, we can copy the value of i into a variable within the while {...} block, like this:

    function makeArmy() {
      let shooters = [];
    
      let i = 0;
      while (i < 10) {
          let j = i;
          let shooter = function() { // shooter function
            alert( j ); // should show its number
          };
        shooters.push(shooter);
        i++;
      }
    
      return shooters;
    }
    
    let army = makeArmy();
    
    // Now the code works correctly
    army[0](); // 0
    army[5](); // 5

    Here let j = i declares an ???iteration-local??? variable j and copies i into it. Primitives are copied ???by value???, so we actually get an independent copy of i, belonging to the current loop iteration.

    The shooters work correctly, because the value of i now lives a little bit closer. Not in makeArmy() Lexical Environment, but in the Lexical Environment that corresponds to the current loop iteration:

    Such a problem could also be avoided if we used for in the beginning, like this:

    function makeArmy() {
    
      let shooters = [];
    
      for(let i = 0; i < 10; i++) {
        let shooter = function() { // shooter function
          alert( i ); // should show its number
        };
        shooters.push(shooter);
      }
    
      return shooters;
    }
    
    let army = makeArmy();
    
    army[0](); // 0
    army[5](); // 5

    That???s essentially the same, because for on each iteration generates a new lexical environment, with its own variable i. So shooter generated in every iteration references its own i, from that very iteration.

Now, as you???ve put so much effort into reading this, and the final recipe is so simple ??? just use for, you may wonder ??? was it worth that?

Well, if you could easily answer the question, you wouldn???t read the solution. So, hopefully this task must have helped you to understand things a bit better.

Besides, there are indeed cases when one prefers while to for, and other scenarios, where such problems are real.

Buka solusi dengan tes di sandbox.