Introduction
Functions in JavaScript are incredibly powerful and useful. Unlike some other languages, functions in JS are first-class citizens. That means that we can actually pass them around as values which allows us to do some pretty cool stuff. Let's dig in.
Function Declaration vs Function Expression & Arrow Functions
There are a few different ways to declare functions in JS. Functions can actually behave differently based on how you declare them. The biggest difference is between them is the difference between a function declaration and a function expression (function keyword or arrow). Let's examine both and how they differ.
Function Declaration
Function declarations can be really useful for organization because they are hoisted to the top of your JS files when they're executed. This means we can keep them in any order and anywhere in our code and use them whether they've been declared yet or not. Hoisting is a concept that makes JS really nice for keeping code organized. Let's look at how we would write a function declaration:
greet();
greet("Donny");
function greet(name = "being") {
console.log(`Hello, ${name}.`);
}
This will print the following to the console
$ Hello, being.
$ Hello, Donny.
Note how we are able to call the greet
function before we even write that function. This is because of how JS hoists functions (only declarations) and variables to the top of the scope before executing the code. So at runtime, JS sees your code like this (even though you aren't writing it this way):
function greet(name = "being") {
console.log(`Hello, ${name}.`);
}
greet();
greet("Donny");
Function Expressions
There are two ways to make a function expression. The first is with the function keyword, like so:
const myFunc = function() {
// ...
};
The other is with an arrow function:
const arrowFunc = () => {
// ...
};
The major difference between these two are how they treat the this
keyword. Let's take a look with an example.
const myObj = {
myFunc: function() {
console.log(this);
},
arrowFunc: () => {
console.log(this);
}
};
myObj.myFunc();
myObj.arrowFunc();
You will see different outcomes depending on whether you run in the browser or through Node. in Node you get the following output:
$ { myFunc: [Function: myFunc], arrowFunc: [Function: arrowFunc] }
$ {}
Callbacks
We mentioned at the beginning that functions are first class citizens but what exactly does that mean. Well, in essence, it means we can use them the same way we would use a String, Number, Boolean, or any other data type. The most common use of this kind of thing is what is known as a callback. This is where we pass a function as a value to another function and then do something with it. Let's take a look at what this looks like:
function callSomethingElseAndSayHi(name, callback) {
callback();
console.log(`Hello again, ${name}.`);
}
callSomethingElseAndSayHi("Ben", () => {
console.log("Hey! I am a callback function!");
});
which results in the following:
Hey! I am a callback function!
Hello again, Ben.
Anonymous Functions
In the previous example, we were able to just type out an arrow function as a parameter of the function we created. That is what is known as an anonymous function and it simply means that the function has no identifier. So once it runs, we can never reference that function again. This is useful for things that we just want to execute once. This is why most callback functions are actually anonymous functions. They enclose some very specific functionality that gets executed once and then goes away. This is how JS uses a functional paradigm to simplify code. That isn't something that you would see in Object Oriented Programming (OOP).
Closures & Scope
The concept of closures and scope in JS is one that can seem really complicated at first, but there really isn't much to it.
A closure is the combination of a function and the lexical environment within which that function was declared. - Mozilla Developer Network
Let's see what this looks like:
function alertTime() {
const time = new Date().getTime();
function applyTime() {
alert(time);
}
applyTime();
}
Now we have an enclosed function that has reference to the scope or environment of its enclosing function.
Closures get really cool when you actually return new functions. These sorts of things are called factories. Here's a really basic example:
function greetingFactory(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
const sayHiTo = greetingFactory("Hi");
const sayWhatsUpTo = greetingFactory("What's up");
console.log(sayHiTo("Lacey"));
console.log(sayWhatsUpTo("Lauren"));
You'll often also see factories returning objects as well.
Conclusion
Functions are a huge asset to us when we're writing code. They give us the means to have encapsulation and abstraction in our JS code. Get used to how they work and what they can do for you!