1. Start with an awkward example

<ul class="wrap">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

<script>
  var addEvent = function (nodes) {
    for (var i = 0; i < nodes.length; i++) {
      nodes[i].onclick = function () {
        console.log(i)
      }
    }
  }

  var wrap = document.querySelectorAll('.wrap > li')
  addEvent(wrap)
</script>
Copy the code
  • As you would expect, click on eachli, will print 3

The purpose of the addEvent function is to pass each event handler a unique I. The reason it doesn’t work as expected is that the event handler binds variable I itself, not the value of variable I at the time the function was constructed.

2 to solve

2.1 The most common solution

It leverages the invariance of object properties.

var addEvent = function (nodes) {
    for (var i = 0; i < nodes.length; i++) {
      nodes[i].index = i
      nodes[i].onclick = function () {
        console.log(this.index)
      }
    }
 }
Copy the code

2.2 es6

Let declared variables are used and are only valid at the block level scope. I is only valid for this cycle. So I is a new variable for each loop. The JavaScript engine internally remembers the value of the previous loop as the basis for initializing the variable I of this round.


With VAR, the declared variable is global, that is, the same variable is used. And finally when you print it out, you’re going to use the global variable I, so when you click, it’s always going to be 3.

let addEvent = function (nodes) {
    for (let i = 0; i < nodes.length; i++) {
      nodes[i].onclick = function () {
        console.log(i)
      }
    }
}
Copy the code

2.3 Closures (Finally…)

In the event handler, as shown below, a helper function is returned.

  • Because of the nature of closures,
    • This helper function has access to the context in which it was created. This is because variables declared in a function or passed to a function are stored in the execution context, which in turn is attached to its scope.
    • This helper function accesses the actual variable in the external function, not the copied value.

So, each time I is passed to the Handlers function it is put in the handlers execution context, and each time the handlers function is called an execution context object is created because there is no interference with each other.

var addEvent = function (nodes) {
    var handlers = function (i) {
      return function () {
        console.log(i)
      }
    }
    for (var i = 0; i < nodes.length; i++) {
      nodes[i].onclick = handlers(i)
    }
}
Copy the code

3 Other Questions

3.1 aboutforCycle, useletandvarThere are differences:

  • letThe part that sets the loop variable is the parent scope; Inside the loop body are separate subscopes

As shown below, three consecutive as are printed. First, let cannot be declared repeatedly, but no error is reported in this example. Because at the end of the first loop, I ++, I is still 0

for (let i = 0; i < 3; i++) {
  let i = 'a'
  console.log(i)
}
// a
// a
// a
Copy the code
  • varBecause throughvarIt’s a global variable, and it can be declared repeatedly, so when I ++, it becomes a++, and then the second time through the loop, I is NaN, we’re done.
for (var i = 0; i < 3; i++) {
  var i = 'a'
  console.log(i)
}
// a
Copy the code

3.2 Problems in creating functions

  • Avoid creating functions in loops.

Going back to where we started, we do this in a loop to bind event handlers to each node. Thus, a new anonymous function is created each time, leading to uninteresting calculations and confusion, as this example shows.

var addEvent = function (nodes) {
    for (var i = 0; i < nodes.length; i++) {
      nodes[i].onclick = function () {
        console.log(i)
      }
    }
}
Copy the code