3

I have the following code where I add an event listener to the document and then remove it.

document.addEventListener("keypress", gameStart);

function gameStart() {
    document.querySelector("h1").innerHTML = "Level 1";
    document.querySelector("h2").style.visibility = "hidden";
    document.removeEventListener("keypress", gameStart);
}

I cannot wrap my head around how I can have a callback to gameStart in the removeEventListener method inside the definition of gameStart() itself. This seems circular to me, but I sense I am misunderstanding something fundamental here. What am I missing?

codingbryan
  • 40
  • 1
  • 10
  • The function doesn't know it is used as a callback. `gameStart` is a reference to the function, and with the reference you can pass functions around in JS. A reference to the function itself is passed automatically to the named functions, and can be got from the outer scope if an anonymous function is assigned to a variable. This reference is actually a value, and in JS, circular references are not a problem, you can fluently do ex. `const arr = []; arr[0] = arr;`. See https://stackoverflow.com/questions/1493453/example-of-a-circular-reference-in-javascript – Teemu Apr 23 '20 at 08:06
  • also note that, if you you´re uncomfortable with that, you can also pass a third argument `options` to addEventListener where you can specify that the listener will only be called once. `document.addEventListener("keypress", gameStart, {once: true});`. Documentation: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener – ItsFlo Apr 29 '20 at 06:41

4 Answers4

6

In the removeEventListener documentation we can see:

target.removeEventListener(type, listener[, options]);
target.removeEventListener(type, listener[, useCapture]);

...

listener
   The EventListener function of the event handler to remove from the event target.

the EventListener function (in your case gameStart) is not called when calling removeEventListener, therefore there is not any circular calling or recursion, it is passed to removeEventListener so this function can be unregistered from that event.

Community
  • 1
  • 1
Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
  • This makes more sense to me now, but what I don't understand is that you say removeEventListener never calls gameStart, but how does addEventListener know to call gameStart when it is the exact same syntax (i.e. in both methods they appear as gameStart)? Is there no way for me to know when it is or it isn't a callback by the syntax? – codingbryan Apr 29 '20 at 20:08
  • @codingbryan `addEventListener` does not call the function either; actually the function will **never** be called if there won't be a *keypress* (*regarding the `keypress` event*). `addEventListener` just *hooks* that function to a specific event and `removeEventListener` *unhooks* that function from that event. – Christos Lytras Apr 29 '20 at 20:09
4

Variables used inside a function are not evaluated until the function is called.

gameStart can, therefore, reference itself because it is created before it can be called.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
4

You can always pass the reference of the function that you're defining because

A function doesn't have to have everything available at the time of definition, rather that's a requirement at the time of calling.

It's also fundamental to recursion. e.g.

function getFactorial(num) {
    if (num <= 2) {
        return num;
    }
    return num * getFactorial(num - 1);
}

Maybe, the following will help you understand more:

function getType() {
    return typeof getType;
}

Above function will always return "function".

How about another one:

function getTypeOfX() {
    return typeof myObj.x;
}

You'll be able to define this function but as soon as you call it using getTypeOfX(), you'll get an error because myObj is not defined in the outer / global scope of the function definition.

If you're trying that out in console. You can do

var myObj = {
    x: ""
}

even after the function definition, and call getTypeOfX() again to see that it now prints "string".

The conclusion here is what @Quentin mentioned:

Variables used inside a function are not evaluated until the function is called.

chiragrtr
  • 902
  • 4
  • 6
4

Everybody answers are pretty complex. A little simpler:

1- You are not calling the gameStart function back on document.removeEventListener("keypress", gameStart); You are actually calling the removeEventListener() function - Now read that again because I know this can be confusing.

2- You are telling the removeEventListener() function to remove your on key press listener from your gameStart() function. gameStart never called itself at the end on that line.

3- Real life example(out of computing) :

Someone calls you to your Phone# xxx-xxx-xxxx then you pick up and that person tells you to do something (you are gameStart()) after you are done you want to hung up because you have nothing else to do or discuss with the other person on the phone, so you tell the other person to hung up for you because other wise they will stay listening forever on the call(the other person was the event listener). You did not hung up, you did not even participate on the action to hung up, you just told them what they needed to do.

I hope that helps!

  • Shouldn't point 2 rather be the removeEventListener() moethod removes the event listener (i.e.gameStart() in my case) from the Document (i.e. EventTarget)? – codingbryan Apr 29 '20 at 19:56
  • I don't know if I understood your comment properly, but the when you addEventListener() to gameStart() the event listener is coming from addEventListener(). When there is an event fired by gameStart() is visible in the entire page and it affects nothing, but thanks to the addEventListener() you are able to capture it and do some action with that event. The moment that you use removeEventListener() you stop capturing the signals from gameStart(), but gameStart() NEVER stops firing events, you just don't capture them so you do not even know that they are happening. – Mixael Contreras Apr 30 '20 at 08:07