Academind Logo

Direct vs Indirect Function Execution

JavaScript functions can run upon events or because you call them directly. This affects how you execute the functions!

Created by Maximilian Schwarzmüller
#

Executing JavaScript Functions

There are two ways functions can be "scheduled for execution" in JavaScript:

  1. You directly execute a function: someFunction()

  2. 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 work
const 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 work
const 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!

Recommended Courses