Direct vs Indirect Function Execution
JavaScript functions can run upon events or because you call them directly. This affects how you execute the functions!
Executing JavaScript Functions
There are two ways functions can be "scheduled for execution" in JavaScript:
You directly execute a function:
someFunction()
You schedule a function for future execution:
el.addEventListener('click', someFunction)
Do you notice the difference? Only in case 1 you execute a function immediately!
This might sound obvious to you - if it does, you can skip this article. But I noticed that a lot of newcomers to JavaScript are indeed struggling with this!
So let's take a closer look.
A Normal Function, Executing Normally
Consider this code snippet:
function init() {// Do initialization workconst myElement = document.createElement('li');myList.append(myElement);}init();
What happens in the above snippet?
A function (init
) gets defined, inside of that function we execute code to create a <li>
element and append it to another element (myList
which is probably selected somewhere else in the script).
Of course, the code inside of init
does not execute immediately when the script is loaded and parsed. Instead, it only executes once the function is called - which happens right below the definition in the above snippet (init()
).
Nothing too fancy here, this should all be well-known.
The Confusing Case
I often notice that newcomers to programming (but also some people who already did some development) struggle with this kind of code:
function greet() {alert('Hi there!');}someButton.addEventListener('click', greet);
What's the confusing part?
It's this line:
someButton.addEventListener('click', greet);
What's greet
? Aren't the parantheses (()
) missing here? Shouldn't it be:
someButton.addEventListener('click', greet());
?
No, it absolutely should NOT be that!
Why?
Because if you add parentheses (greet()
) you execute the function. This means, the code inside of greet
run as soon as JavaScript execution reaches this line:
someButton.addEventListener('click', greet());
But that's typically not what you want here. You don't want to execute greet
when JavaScript reaches this line. You want it to execute once a click on the button (someButton
) occurs.
That's a different thing!
References vs Function Results
In the "standard case" (I like to call it direct function execution), we (= the developer) simply instruct JavaScript to execute the code inside of a function.
That's what we did in the very first example of this article:
function init() {// Do initialization workconst myElement = document.createElement('li');myList.append(myElement);}init();
In the second case (with the button and the event listener), we don't want to execute a function directlly. We want to execute it "indirectly" you could say.
We want to "tell" JavaScript (or actually the browser in the end) that it should execute a function (greet
) for us when something happens (in the above example: a click on someButton
).
Hence we must not add ()
after greet
. Because that would invoke the function immediately.
By just using
someButton.addEventListener('click', greet);
we instead just "point" at the function. We pass a reference (a pointer) to the function as a second argument to addEventListener
. And JavaScript/ the browser then uses the reference to eventually call that function for us.
If you used this code instead
someButton.addEventListener('click', greet());
you would not pass a reference to greet
to addEventListener
but instead the result of invoking greet()
.
What is that result?
Well, it's whatever this function returns. In this example, it's undefined
because the function does not return anything. If it had a return
statement, it would be whatever comes after return
.
References & Function Parameters
That's all nice but what if greet
looked like this?
function greet(name) {alert('Hi ' + name);}
Now it becomes a bit more tricky. greet
wants a parameter (name
) and we're currently "connecting" greet
to the button like this:
someButton.addEventListener('click', greet);
How does the browser/ JavaScript (which executes the function for us, as we learned) know which value should be fed in for name
?
The answer is: It doesn't.
Currently, if you clicked on the button, you would just get Hi undefined
as an output. Clearly not what we want here!
There are two easy solutions to this problem though:
Solution 1
someButton.addEventListener('click', function() {greet('Max'); // yields 'Hi Max'});
Solution 2
someButton.addEventListener('click', greet.bind(null, 'Max')); // also yields 'Hi Max'
What's going on there?
Let's start with solution 1:
There, we create an anonymous function "on the fly" in the place of the second argument of addEventListener
. This anonymous function is then passed (i.e. the reference to that function is passed) as a second argument.
It's not getting executed immediately when JavaScript reaches the line because we haven't added parentheses after the anonymous function definition.
Inside of that function, we then call greet('Max')
but keep in mind that this code only executes once the button is clicked (because it's wrapped by the anonymous function).
So that was solution 1, let's now focus on solution 2:
There, we use the bind()
method which is "built into" JavaScript you could say. To be precise, it's available on function objects - for that, it's important to keep in mind that functions are also objects in JavaScript. You learn basics like this in my "JavaScript - The Complete Guide" course by the way.
What does the bind()
method do though?
It "prepares" the function on which it's called for future execution.
It allows you to "preconfigure" which arguments that function should receive when it's eventually getting called. In addition, you can also define what the this
keyword should refer to inside of that function.
To be precise, when we use bind
like this
someButton.addEventListener('click', greet.bind(null, 'Max')); // also yields 'Hi Max'
we "tell" JavaScript that this
should be null
(or, to be precise, not have any different value that it would have otherwise) and that the first argument passed to greet
, when it's getting called, should be Max
.
You can simply keep in mind that the first parameter of bind
is always the this
keyword reference, all other parameters thereafter are the arguments fed into the function that will be invoked (i.e. on which you called bind
).
So the first parameter of the "to-be-called" function will then get the second argument passed to bind
. The second parameter of the "to-be-called" function will be defined via the third argument passed to bind
. And so on.
And by using this code, we ensure that greet
will receive Max
as a value when it's being called in the future (by the brower/ JavaScript).
It's up to you which approach (anonymous function vs bind) you prefer, the most important part is that you understand why you might need these approaches.
You can learn way more about JavaScript, functions, function execution, bind
and all other core things that you need to know as a JavaScript developer in my "JavaScript - The Complete Guide" course!