DEV Community

loading...
Cover image for Back to Basics: Functions, Hoisting, and Scope

Back to Basics: Functions, Hoisting, and Scope

alisabaj profile image Alisa Bajramovic ・7 min read

This series discusses the building blocks of JavaScript. Whether you're new to the language, you're preparing for a technical interview, or you're hoping to brush up on some key JS concepts, this series is for you.

Today's post is about functions:

What is a function?

A function in JavaScript is a set of statements that take some input, do a certain task, and return some output.

When working with functions, you first have to define the function, which involves naming it and saying what actions it does. Then, it actually get those actions to happen, you have to call the function.

There are two main ways to define a function: function declarations and function expressions. (Note: there also is something called the function constructor, though it's less commonly used.)

Function declarations

A function declaration, also known as a function definition or a function statement, is one way to define a function. It's structured like the following:

function name(input) {
  statements;
}
Enter fullscreen mode Exit fullscreen mode

The name of the function is name. The input is the parameters for the function, and it's enclosed in parentheses. Inside of the curly brackets is statements, which do a certain task. statements often return a value, or an output. A function does not have to take in any parameters, so input is optional. The statements themselves are also optional (though that would just mean you'd have an empty function that didn't do anything).

For example, let's say we wanted to define a function using a function declaration that took in a number, and returned the given number times two:

function double(number) {
  return number * 2;
}
Enter fullscreen mode Exit fullscreen mode

In this example, number is passed to the function by value alone; in other words, this function does not change number in the larger, global context. To illustrate what that means, let's insert a few console logs before and after the above function:

// declaring a variable called `count` and setting it equal to 3
let count = 3;
console.log(count); // 3

// declaring a function called `double` which returns an inputted number times 2
function double(number) {
  return number * 2;
}

// declaring a variable called `result` is set equal to calling the function `double` and passing the number `count` as the input
let result = double(count);
console.log(result); // 6

console.log(count); // 3
Enter fullscreen mode Exit fullscreen mode

When we called the function double() and passed in count, we didn't change the value of count itself -- it still equaled 3.

However, this is only true of primitive parameters in JavaScript. If you pass a non-primitive parameter to a function (such as an array or object), and the function alters the object in some way, then the object is changed outside of the function as well. For example:

let fruits = ["apple", "banana", "orange"];

function removeLastElement(array) {
  array.pop();
  return array;
}

removeLastElement(fruits);

console.log(fruits); // ["apple", "banana"]
Enter fullscreen mode Exit fullscreen mode

The above example uses the .pop() method, which removes the last element of an array. By passing in the fruits object as a parameter in removeLastElement(), the last element of fruits was removed, and the updated array was returned. When working with non-primitive values, it's important to keep in mind that passing them into functions may end up changing their value.

Function expressions

Another way to define functions is with a function expression. The main difference between a function expression and function declaration is that with function expressions, the function name is optional. If you don't include a function name, you have an anonymous function. A function expression is structured like the following:

function name(input) {
    statements;
}
Enter fullscreen mode Exit fullscreen mode

Note that this is the exact same structure as function declaration. The following is an example of an anonymous function, meaning that it does not have a name. The function is set equal to a variable called triple:

const triple = function (number) {
  return number * 3;
};
Enter fullscreen mode Exit fullscreen mode

Function expressions are often written as arrow functions. Arrow functions are considered to be compact versions of function expressions, and are often used to "clean up" code. Let's turn the above function into an arrow function:

// Standard function expression
function (number) {
  return number * 3;
};

// Arrow function
number => number * 3;
Enter fullscreen mode Exit fullscreen mode

What changed with the arrow function? The words function and return were removed, there are no parentheses around the parameter number, the curly brackets were replaced by an arrow =>, and everything is on one line.

However, these rules vary depending on the arrow function. If the function has only one parameter, then you don't surround it in parentheses. If it has zero or 2+ parameters, then you do surround it in parentheses. If the function has only one statement, then you don't have the curly brackets or the word return. If the function has more than one statement, then you have both the brackets and the word return. Let's see an example of each of these:

// One parameter, one statement
number => number * 3; // AB

// Zero parameters, one statement (these are often used in callback functions)
() => x * 2;

// Two parameters, one statement
(a, b) => a - b;

// Two parameters, multiple statements:
(a, b) => {
  let tax = 0.05;
  return (a + b) * tax;
};
Enter fullscreen mode Exit fullscreen mode

Arrow functions have a lot of changing syntax, depending on the function. However, it's less important to memorize exactly when to use parentheses around an input, than it is to recognize what an arrow function generally looks like, and where to find more resources about it. Over time and with practice, you'll end up not needing to refer back to the documentation. This is true of a lot of aspects of programming: rather than trying to memorize every little detail of how something is written and the specific ways to use it, it's much better to recognize something and know where to go for more information. Every programmer uses Google and refers to documentation, no matter how long they've been doing it.

Calling functions

Just because you defined a function, that doesn't mean the function has been executed. When you define a function, you say what it's called and what it's supposed to do. When you call a function, it actually gets performed.

To call a function, you refer to the function's name, and pass in arguments that correspond to the parameters. To call the function triple(), which we defined above, we need to refer to its name, and pass in one number as an argument:

triple(5);
Enter fullscreen mode Exit fullscreen mode

Hoisting

Hoisting in JavaScript means that variable declarations and function declarations are brought to the top of the code.

This is tricky concept to grasp at first, so it can help to look at an example. Let's create a function using a function declaration, and call it numberSquared. numberSquared() will take an inputted number, and then console log that value squared. Then, after the function, we can call it, and we'll pass in the number 5.

function numberSquared(num) {
  console.log(num * num);
}

numberSquared(5);
Enter fullscreen mode Exit fullscreen mode

The result of the above code is 25.

Now, what would happen if we called the function before we declared the function?

numberSquared(5);

function numberSquared(num) {
  console.log(num * num);
}
Enter fullscreen mode Exit fullscreen mode

Again, the result of the above code is 25. This is because the function declaration was brought to the top when your code was compiled.

Keep in mind that only function declarations, not function expressions, are hoisted.

Scope and closures

A scope in JavaScript is what is currently "visible" or "accessible". According to MDN documentation, "If a variable or other expression is not 'in the current scope,' then it is unavailable for use."

In terms of functions, the variables that are declared in the function are only accessible within the function. This is called a closure.

To see examples of different scopes, let's look at the following:

const weather = "rainy";

function myNameAndTheWeather() {
  const name = "Alisa";

  console.log(name);
  console.log(weather);
}

myNameAndTheWeather();

console.log(weather);
console.log(name);
Enter fullscreen mode Exit fullscreen mode

What would happen if we ran this program? The output would be the following:

Alisa
rainy
rainy
[ReferenceError: name is not defined]
Enter fullscreen mode Exit fullscreen mode

To understand why these are the results, let's go over what the code says, and what happens when we run it. First, the variable weather is initialized and set equal to "rainy". Then, using a function declaration, the function myNameAndTheWeather() is defined. Inside of myNameAndTheWeather(), the variable name is initialized and set equal to "Alisa", name is console logged, and weather is console logged. Then, outside of the function, myNameAndTheWeather() is called. Then, weather is console logged. Finally, name is console logged.

When we run this program, the first thing that happens is that the function myNameAndTheWeather() is called. name is defined in the function, in the local scope, so the function is able to console log it. weather is defined outside of the function, in the global scope, so the function also has access to it. In other words, the function has access to variables declared in its own local scope (a closure) and in the global scope. Therefore, Alisa and rainy are logged to the console.

After myNameAndTheWeather() is executed, the program goes to the next line, which says to log weather to the console. weather is an accessible variable, so the program console logs its value. Finally, the program tries to console log the variable name. However, name is defined within the function myNameAndTheWeather(). It has a local scope, which means we don't have access to it from outside of the function. Therefore, a reference error is returned.


Let me know in the comments if you have any questions or other ways of thinking about functions, scope, and hoisting in JavaScript.

Resources

Discussion (0)

Forem Open with the Forem app