0

I am having some trouble with a fairly simple piece of vanilla Javascript. All it does, is get all p elements by their tag name, and attach an event handler to each one, which calls a function that changes the colour to red.

Outside the callback function el refers to the correct element, 1-6. But inside the loop, it always refers to the last one, despite javascript's lexical variable scoping nature. Why is this, and what can be done to implement indended behaviour?

Fiddle: https://jsfiddle.net/7j4bg68g/

<p>1 This is a test.</p>
<p>2 This is a test.</p>
<p>3 This is a test.</p>
<p>4 This is a test.</p>
<p>5 This is a test.</p>
<p>6 This is a test.</p>

...

function takeAction() {
  this.style.color = 'red';
}

var els = document.getElementsByTagName('p');
for (var i = 0; i < els.length; i++) {
  var el = els[i];

  // logs els 1 to 6 as expected.
  console.log(el.innerHTML);

  el.addEventListener('click', function() {

    // logs "6 This is a test.", and makes the last <p> tag red,
    // regardless of which <p> element you click on.
    console.log(el.innerHTML);
    takeAction.call(el);

  }, false);
}
kohloth
  • 742
  • 1
  • 7
  • 21
  • Though not addressing the cause of the problem in your question, you could simplify the code and prevent the problem by simply changing the loop body to `els[i].addEventListener("click", takeAction)` –  Apr 04 '17 at 16:22

1 Answers1

1

This is because of closure, in your code you are referencing to "el". All the event handlers are having a reference to the same variable el which has changed to the last value it was given in the loop. Try this,

function takeAction() {
  this.style.color = 'red';
}

var els = document.getElementsByTagName('p');
for (var i = 0; i < els.length; i++) {
  var el = els[i];

  // logs els 1 to 6 as expected.
  console.log(el.innerHTML);
(function(elem){
  elem.addEventListener('click', function() {

    // logs "6 This is a test.", and makes the last <p> tag red, regardless of which <p> element you click on.
    console.log(elem.innerHTML); // logs "6 This is a test."
    takeAction.call(elem);

  }, false);
})(el);
}
Saurabh Verma
  • 1,534
  • 1
  • 14
  • 28
  • 1
    You'll need to fix your syntax. It'll error right now. And while the corrected code would be a possible solution to the problem as presented, it's an unnecessarily verbose solution since the OP could simply use `this` inside the event handler. –  Apr 04 '17 at 16:19
  • This works: `el.addEventListener('click', function() { console.log(this.innerHTML); takeAction.call(this); }, false);` – kohloth Apr 04 '17 at 16:25
  • IceBox: You edited the question, but there's still a syntax error. Did you test your code? –  Apr 04 '17 at 16:27
  • @squint, sorry it was supposed to be a self executing function missed the round brackets – Saurabh Verma Apr 04 '17 at 16:31