Everything You Ever Wanted to Know About JS Functions But Didn’t Have the Time to Ask

Kate Norton
The Startup
Published in
9 min readSep 7, 2020
Photo by Kelly Sikkema on Unsplash

If you are like me, you understand the basics of JS functions, and know how to use them to help you create your programs, but there are complexities to them that you have ignored as you build your skills with fancy frameworks, libraries, languages, and technologies.

But, I’m going to succinctly inform you of what you need to know so that you can feel more empowered around JS Functions.

Let’s get to it.

Expressions, Values, Statements, Declarations, Oh My

In order to easily understand functions, it is good to get a handle on some terms.

Statements are blocks of code that can be executed:

It’s hard to talk about statements without talking about expressions.

Expressions are sections of code that return a value, reduce to a value, become something of value, are generally interpretable, have meaning, use, and can give their value to a variable, or a function, or anything that wants to store or use it.

If JS could interpret something as a statement or an expression, if it is between () then JS interprets what is inside of that as an expression (if possible).

Expressions compile to values used in the program.

Function Declarations

Function declarations are strange double beasts in the statement-expression world. In most cases, functions are expressions, and not statements. The special case is when the function is defined without any supportive structure.

A function declaration is basically a statement-ish function. They exist when the beginning of a statement begins with the word ‘function.’

Function declarations are unique in that they are hoisted with their definitions to the top of the scope they are in and can be used anywhere above or below the scope they are defined in.

Function declarations are hoisted with their definitions.

In this way, they are hoisted like variables are, but they are treated differently. Function declarations are hoisted with their contents and are evaluated at the top of their scope as if they were expressions that were stored in a variable named for it.

But function expressions, the most common type of function type, are not hoisted in this way.

Function Expressions

It is a little unusual for a function to act as a statement. The more logical role for functions is the one that is most common as well. Most forms of functions can be thought of as values — things that can be useful somewhere else. They are a set of instructions to follow when called, and can be stored and retrieved much like a cookbook can be stored on a bookshelf (and whose recipes can be followed later).

Function expressions are not hoisted in the same way as function statements.

Demonstration of function expression not being hoisted.

If you know how JS hoists variables and their assignments generally this should not surprise you: assignments to hoistable declarations never happen until the line they are assigned.

For this reason, the most important idea in understanding JS functions is to understand that unless they are a function declaration, they are treated as expressions, or values that can be used.

Photo by Michael Dziedzic on Unsplash

So…Arrow Functions, Callback Functions, Generator Functions, IIFEs?

No matter what type of function you are using in JS, it is either a function expression or function declaration/statement. Coming to terms with arrow functions and callback functions is easier if you notice that no matter what crazy syntactical hoops they are jumping through, they are all doing more or less the same thing: either it’s a value that is being passed somewhere else, or is a declaration that registers itself and is waiting to be used.

Callback Functions

Callback functions are not a special type of function in JS or in any programming language for that matter. Callback functions are function expressions passed as a parameter into another function.

In the example above, each of the defined functions is a function declaration/statement and is referenced and used as a function expression inside the the myOwnMap function.

But you don’t need to have a separate declaration in order to use a callback function.

doSomethingToNumber takes two parameters: a number, and a function. In the above example, we passed anonymous function expressions into our function call.

To summarize callback functions, they are function expressions that are passed in as arguments into another function. That’s it!

Photo by Max Böttinger on Unsplash

Arrow Function Expressions

Arrow function expressions are a syntactical coloring on what is really just an anonymous function expression. An anonymous function is a function that doesn’t have a name property associated with it, but can still be used based upon what it was assigned to.

Arrow function expressions are supposed to simplify some code, but can be confusing if not understood correctly. Basically the things before the arrow are the parameters of the function (or just parenthesis if not argument), and the things after the arrow is the block of code to run, or the value to return (depending upon whether or not there is a value there).

Here are some rules of thumb:

Before the arrow:

You don’t need () if you have one parameter.

You need () if you have no parameters.

You need () if you have more than one parameter.

After the arrow:

  1. You do not need {} if you are returning the result of a single expression.
  2. You need {} if you function block contains more than one statement.
  3. You need to use the keyword return if you use {} if you want your function to return anything.

There are a lot of options, but, seriously, arrow function expressions are just another way of writing anonymous function expressions.

A Note On this

this in JS is a complex subject better for another article. However, one good rule of thumb on this with regards to functions is this (pun intended): using the function keyword will give you a consistent this. Using arrow functions will give you a this that retains the value of the enclosing context’s this. In short, for now, stay away from arrow functions if you are relying upon the this keyword to fulfill the functions goals, AND you expect it to operate based upon the scope in which you wrote it, not the scope in which it is executed.

From MDN:

An arrow function expression is … without its own bindings to the this, arguments, super, or new.target keywords. Arrow function expressions are ill suited as methods, and they cannot be used as constructors.

Basically, if you have any problems, ask yourself if your arrow function expression is being asked to do more than it was made for, especially when it comes to this.

Generator Functions

Generator Functions are unique. Writing a generator function is very similar to how you write function declarations and expressions (of the non-arrow variety) except you use * and yield to make them do their magic.

How generator functions work is very specific. First you create a generator function. Then you call that generator function, and it returns an iterator object whose .next() method is used to un-suspend the generator and return a simple object with two properties, the value that was yielded when it last stopped, and a Boolean named done, which is false if the iterator has more to it, or true if there is no more to do.

Here’s a simple example:

Simple generator function example.

So, what’s happening here? The generator function is defined on line 1. It is called on line 9, and returns the iterator object that is used subsequently. On line 10, when myGenerator.next() is called, it starts the generator up. The generator runs all the code it finds until it finds a yield. When it finds the first yield, it pauses the function, and returns the first object, which contains the value that was yielded, and the Boolean that says the function is not completed.

Let’s look at some of the options we have when using generator functions:

Generator function possibilities simplified.

Let’s walk through the things that are different here. In this case allTheBells is called on line 1, before we’ve defined it, which shows that generator function declarations follow the same hoisting rules we defined earlier for function declarations. This generator function also takes a parameter called startingValue. Here it is 10. Like a regular function, this variable is created in the scope of the function and is usable in the generator function.

Notice on line 4, 5 and 7 that you can yield any expression. Notice that generator functions can run code between yields. You can think of the yield keyword kind of like a stop sign. When yield is reached, it will stop the function and return an object whose value is what was the argument of the yield. When the next next() is called, the function first returns to the function in the place of that previous yield the value it evaluates to. If the yield is not used as an expression in another statement it won’t do much, but whatever is passed into the next function is what the previous yield statement evaluates to. Walking through lines 17 and 18 (and 9 and 10) can help explain this.

When myGenerator.next() is called on line 17, it is resuming the allTheBells function from line 9. Nothing is passed into the .next() call, and there is nothing in the function to receive it anyway, so it continues the function until it gets to the next yield on line 10. It returns the argument after the yield making the object with the value of 100. The when myGenerator.next() is called on line 18, 5 is passed into it. That 5 that is passed into the .next() call is what yield 100 evaluates to, and since that value is now fully returned, the console.log of it can complete, and 5 is logged to the console. The .next() call also returns an object that tells us the value of the next yield. But there is no next yield so it returns an object whose value is undefined, and whose done is set to true.

I found walking through the second example on this page to be a good exercise to help me understand these functions. For now, let’s leave this topic, and move onto the last topic we’ll cover in this article: IIFEs.

IIFEs

IIFE stands for Immediately Invoked Function Expression. We’ve seen what a function expression is, and can likely understand what invoking a function immediately is. The obscure acronym is actually describing something fairly straightforward.

You might wonder what the point of using an IIFE is. One great use of them is to manage scope. The first function — engineOn — is an IIFE, and is modest in its functionality. It doesn’t do much, and so it might seem that putting all the syntax around it is pointless. Yet, we can see something interesting if we look at line 8. We get a ReferenceError when we run it. Why? The IIFE creates a function scope, and the variables created in that scope are not available in the scopes outside of it.

With ES6, we don’t need to use an IIFE to protect our scopes, as lines 14 through 18 show. We can get the same level of scope containment using a block and let keyword.

So what is an IIFE useful for? Well…let that bring us to our last topic.

Readability

With so many options for creating functions, we might find ourselves slightly confused about which to choose. Well, I wrote this partially because I’ve seen every single one of these in code, and I’ve often felt confused myself about why there are so many ways of writing it, and wondering why some ways were chosen over others.

The best answer I can come up with for how to choose one version over another is that it is best to choose the syntactical function option that makes your code more readable to others. Behind the scenes, the engines that handle your code don’t care which syntax option you choose. But other programmers who read your code for the first time will care.

If an anonymous arrow function is best because it eases a reader into understanding the purpose of your code, then that’s the best choice. If a named function expression is going to clarify for everyone what your unique and complex function is meant to do, then name that expression. Ultimately, it is up to you, and it is a good idea to get out of your head that some form of function syntax is inherently more advanced or better than others.

Photo by Hybrid on Unsplash

Congratulations

You did it. You took time to touch on all the JS function possibilities. Good luck with your projects and applications! Remember: make it readable!

--

--