6

I'm trying to understand the difference when adding a function to an event listener and what implications it has.

var buttons = document.getElementsByTagName('button');
for (i = 0, len = 3; i < len; i++) {
    var log = function(e) {
        console.log(i);
    }
    buttons[0].addEventListener("click", log);
}

for (i = 0, len = 3; i < len; i++) {
    function log(e) {
        console.log(i);
    }
    buttons[1].addEventListener("click", log);
}

http://jsfiddle.net/paptd/

The first button fires the console.log 3 times while the second only fires it once.

Why and what should you use when adding a function to an event listener in a normal situation?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
user1767586
  • 438
  • 1
  • 8
  • 19
  • For me, both buttons are firing three times as expected in your jsfiddle. – Fallexe May 31 '13 at 09:18
  • 1
    A couple things to note here. 1. You're not declaring `i` nor `len`. 2. Function declarations within blocks [are bad practice](http://stackoverflow.com/questions/4071292/may-function-declarations-appear-inside-statements-in-javascript), use a function expression instead, that's the problem right there. – elclanrs May 31 '13 at 09:20
  • In case of interest: here is your fiddle of how javascript rearrange your code due to hoisting: http://jsfiddle.net/paptd/4/ – basilikum May 31 '13 at 09:30
  • @Fallexe, what browser?(and it's a bad one because it doesn't follow the W3C specs quoted by Row W.) – gdoron May 31 '13 at 09:33
  • @basilikum Do you mind explaining your change and the benefits of it? – user1767586 May 31 '13 at 09:53
  • 1
    @user1767586 I didn't make the changes to improve your code. In face, the code runs the same as it did before. I just wanted to show you how javascript internally rearranges variable declarations and function definitions. So, the code that you posted is actually interpreted as if it were written in the way I posted. – basilikum May 31 '13 at 10:13
  • @gdoron Firefox 21.0 but basiliukm's fiddle fires 1x and 3x. – Fallexe May 31 '13 at 21:04
  • @Fallexe You are right, I just tried it in Firefox and experienced the same. Cannot explain it though. – basilikum May 31 '13 at 22:40
  • @Fellexe it seems to be really a unique firefox behaviour, which also seems to be not that false. Obviously ECMAScript isn't designed to have function declarations inside of blocks. So browsers may behave differently on this issue. Most browsers are hoisting functions inside of blocks up to the top of the scope, firefox doesn't. Look [here](http://statichtml.com/2011/spidermonkey-function-hoisting.html) for further reading. – basilikum May 31 '13 at 23:00

1 Answers1

5

Well, couple of notes:

  • The first one creates a new log function in each iteration, so every time you add another event listener it adds a new function.
  • The second one creates a global(read about hoisting) log function, If multiple identical EventListeners are registered on the same EventTarget with the same parameters, the duplicate instances are discarded. They do not cause the EventListener to be called twice.

Specs:

Invoking addEventListener (or removeEventListener) repeatedly on the same EventTarget with the same values for the parameters type, listener, and useCapture has no effect. Doing so does not cause the event listener to be registered more than once and does not cause a change in the triggering order.

source thanks to Rob W.

so the second and third iterations do nothing.

  • You also have a problem of closure, the last iteration sets i to 3 and this is what shown in the console.

Fixed version with closure:

var buttons = document.getElementsByTagName('button');
for (i = 0, len = 3; i < len; i++) {
    var log = (function closure(number) {
        return function () {
            console.log(number);
        }
    })(i);

    buttons[0].addEventListener("click", log);
}

DEMO

gdoron
  • 147,333
  • 58
  • 291
  • 367
  • You should use an explicit reference to the specification ("no need to add the same function multiple times" is not a strong argument in this case). Relevant quote: "Invoking `addEventListener` (or `removeEventListener`) repeatedly on the same `EventTarget` with the same values for the parameters `type`, `listener`, and `useCapture` has no effect. Doing so does not cause the event listener to be registered more than once and does not cause a change in the triggering order." - http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#interface-EventTarget – Rob W May 31 '13 at 09:22
  • The second form (the function declaration within the loop block) is actually invalid and only works because browsers add non-standard behaviour to deal with it. Also in the second case, `log` will only be global if the containing code is global, but function declarations within blocks should be avoided anyway. – Tim Down May 31 '13 at 09:53
  • @TimDown, is just like: `var foo =2; var foo =2; var foo =2;` which I don't know about the spec, but it runs fine...` – gdoron May 31 '13 at 10:30
  • Yes, it does run in all browsers, but as mentioned it requires a non-standard extension in the browser's JS parser, and I think there are differences between browser implementations. Also, since other ECMAScript environments are not obliged to support function declarations within blocks, the code is less portable. It can be trivially avoided, so it's best to do so. More background: http://kangax.github.io/nfe/#function-statements – Tim Down May 31 '13 at 11:09
  • @TimDown, Hell I didn't encourage it, I just explained how does it work... (:, don't worry, my code looks a bit different. – gdoron May 31 '13 at 11:16
  • I know, I know. I was just being too lazy to write my own answer and trying to piggyback on yours :) – Tim Down May 31 '13 at 11:22