Core JavaScript

2.1 Functions and Scope

# JavaScript Functions

What is a Function?

Put simply, a function is a series of commands that get bundled together so that they can be run, in that order, once or multiple times.

Functions can be passed information when they are called on to run. They can call other functions to make them run. They can also return some type of resulting value, when needed.

Functions nearly all have a name, just like variables, so you can reference them when you need them to run.

Here are a few examples of what functions can look like.

function hello() {
  //a basic function declaration
}
function goodbye(name, message) {
  //a basic function declaration with two arguments
}
let open = function () {
  //an ANONYMOUS function assigned to a variable
  //known as a function expression
};
function close(num) {
  //function declaration with one argument
  num = num + 1;
  //and a return value
  return num;
}
let next = function (fn) {
  //function expression that receives one argument
  //if the argument that was passed in is another function,
  //then we can call it
  fn();
};

//and this is how we could call each of those functions
hello();
goodbye('Steve', 'See ya later'); // pass in two strings to the `name` and `message` arguments
open();
close(17); //pass in a number to the `num` argument
next(hello); //pass in the name of another function to the `fn` argument
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

You can make a function run by writing its name plus a set of parentheses.

# Function Declarations and Expressions

There are two ways to define your function, with a declaration or an expression.

function f1(){
  //function declaration
}
const f2 = (){
  //function expression
}
1
2
3
4
5
6

Both are equally valid ways to define your function. The only important difference is that a declaration can be called from anywhere in your code. An expression can only be called from below where you defined it. This has to do with scope and hoisting, which will explain shortly.

# IIFE

As already mentioned, to call a function and make it run you just have to add a set of parentheses after the name of the function.

An expression is any single operation that you have in your code. 1 + 3 is an expression. The act of declaring a function is also an expression. Just like in High School math, you can wrap expressions inside parentheses to group terms and control the sequence of operations for multiple expressions.

Expressions cannot go on the left hand side of an equal sign.

This leads to a cool side effect. If we wrap our function declaration in a set of parentheses, it becomes an expression that the JavaScript engine wants to complete before moving on.

function f1() {
  //a function declaration
}
f1(); //calling f1 to make it run.

(function f1() {
  //a function declaration wrapped in parentheses
})();
//putting the set of parentheses after the expression makes it try to run your newly declared function
1
2
3
4
5
6
7
8
9

By adding a set of parentheses after our function declaration that is wrapped in parentheses we are telling JavaScript to try and run (invoke) the result of the expression.

In other words, we have an Immediately Invoked Function Expression, or IIFE (pronounced if-ee) for short.

If your function expression returns something then that can be assigned to a variable like this:

let result = (function f1() {
  //IIFE that returns a String which will be assigned to `result`
  return 'the answer';
})();
1
2
3
4

# Return Statements

If a function does not contain the return keyword then, by default, it returns undefined.

If a function does have a return keyword then as soon as it is encountered, the function stops running and it returns whatever single value you put after return.

You are allowed to use multiple return statements in any function. They can all be returning different things. However, only the FIRST return statement that is encountered will actually be executed and the function will stop running at that point.

function f1() {
  //a simple return statement that returns a string
  return 'the result';
}

function f2(num) {
  if (num > 10) {
    return true; //returns true if the value of num is greater than 10
  } else {
    return false; //returns false
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# Function Arguments

An argument is a value that is passed to a function. The goal for writing functions is to make them pure. This partly means that they should always return the same result if you give them the same input values. Those input values are the arguments.

By having arguments for our functions, we gain flexibility and reuse. Reuse means writing less code. Less code means completing your work faster and with fewer errors. Imagine you have to write a script that has functions capable of adding together any two numbers between 1 and 10. The inflexible approach would be to write a function for each possible combination. Eg:

function onePlusOne() {
  return 1 + 1;
}
function onePlusTwo() {
  return 1 + 2;
}
function onePlusThree() {
  return 1 + 3;
}
//  ... and so on until function tenPlusTen()
1
2
3
4
5
6
7
8
9
10

So, that would be hundreds of lines of code that take you a long time to write.

Instead we can write ONE function that accepts two arguments that represent the values to be added.

function add(a, b) {
  return a + b;
}
1
2
3

This function is only 3 lines long and can work for ANY combination of numbers, even less than one or greater than 10.

The arguments that we put inside the parentheses are actually variable declarations. We have declared a and b just like this would...

function add() {
  let a; //declared but not assigned a value
  let b; //declared but not assigned a value
}
1
2
3
4

...but with the added benefit that they will be assigned any values that are passed to the function.

add(3, 5); //calls the add function and assigns 3 to `a` and 5 to `b`
1

# Default Argument Values

Another cool thing about JavaScript function arguments is how flexible they are. They get declared when you write their names inside the parentheses and just like ALL variables in JavaScript they are automatically given the value undefined to start. If the function is called then the values passed in are assigned to the available variables.

Using our add function as an example:

add(3, 4); //3 assigned to `a`, 4 assigned to `b`
add(1, 2, 3); //1 assigned to `a`, 2 assigned to `b`, 3 is ignored (sort of)
add(5); //5 assigned to `a`, `b` remains undefined
1
2
3

Function declarations have a special keyword called arguments which is a list of all the values that are passed to the function. If you pass more values to a function that there are available variables, then the values are still available through the arguments array.

The third line in the code sample above presents our function with a problem. We only sent one value to the function. The variable b will be left with the default undefined value. When our function tries to add 5 plus undefined there will be an error.

Default argument values are how to prevent this type of error. Below is a new version of our add function declaration that has default values for the arguments.

function add(a = 0, b = 0) {
  return a + b;
}
1
2
3

Now, we are declaring a and b as well as assigning zero as the default value instead of undefined. If the function gets called with only zero or one argument then no error will occur.

add(); //returns zero
add(4); //returns 4
1
2

# Arrow Functions

Like most things in programming, there is often a shorter way of writing something. For functions, we have arrow functions. In this simple example you can see a traditional function and then the arrow function equivalent. We drop the keyword function and add an = and > to create the arrow.

const f1 = function (num) {
  //a traditional function expression
  return num + 2;
};

const f1 = (num) => {
  //the arrow function equivalent
  return num + 2;
};
1
2
3
4
5
6
7
8
9

So, this might not seem like much difference but it can be shortened even more.

//if the function is on one line you can remove the { } and the `return` keyword
const f1 = (num) => num + 2;

//if there is only one argument then we can remove the ( )
const f1 = (num) => num + 2;

//If there is no argument you can write () or _
const f2 = () => 1 + 2;
const f2 = (_) => 1 + 2; //or this without the ( )
1
2
3
4
5
6
7
8
9

Arrow functions don't get named. They are written as function expressions and assigned to a variable or passed as a function expression to another function's argument. Much of the time an arrow function will be used when there is only a single line of code and the function is not going to be used again.

There are a couple other things that change with arrow functions, like the scope of the keyword this, but we can avoid even having to think about that until much later on.

# Variable Scope and Hoisting

Scope is the term used to describe where a variable is visible (can be accessed from). With either let or const we have the same options for scope. A variable can be in the global scope or in block scope. A block scope is created whereever you write { }. Inside the curly braces is a new block. If a variable is declared inside the curly braces then it is scoped to those curly braces and does not exist outside them.

Functions all (except some arrow functions) have a set of curly braces. So, functions all have their own block scope. There can be other control structures like if statements or loops that also create blocks. Each of these blocks is a new scope to declare variables.

Variables that are declared outside of any function are said to be in the global scope. They are visible anywhere in your code.

Later on we will talk about creating modules with different files. This will add another layer of scope but you will be an expert on scope by the time that we talk about modules.

Block scoped variables only exist while their block is being executed by the JavaScript engine.

let name = 'Bree'; //global variable

function f1() {
  let age = 25; //block scoped and visible anywhere inside this function
  if (age > 17) {
    let isAdulting; //block scoped inside this if statement
    isAdulting = true;
    // name, age, and isAdulting are all visible here
  }
  //name and age are visible here, but NOT isAdulting
}
1
2
3
4
5
6
7
8
9
10
11

# Hoisting

Hoisting is an effect that happens to variables and functions in JavaScript when the script is read for the first time by the JavaScript engine. When you run a script, it is actually being read twice. Once to identify all the functions and variables and determine their scope. And a second time to execute the code.

Function declarations are hoisted to the top of their scope with the first pass. This is why you can call them from anywhere in the script.

Variable declarations are identified with the first pass. The existence of the variable is hoisted to the top of their scope. They can't be used from anywhere but the JavaScript engine is aware of their existence. When you get to the line that declares the variable the default undefined value is assigned to the variable.

So, function expressions can be assigned to variables. The expression itself does NOT get hoisted. The variable name is what gets hoisted to the top of its scope.

// the variables name and f2 are hoisted here but have no value yet
// the whole function f1 is hoisted here so it can be called from anywhere
let name = 'Joanne';

//function declaration
function f1(id) {
  //the variables id and age both have their names hoisted to here
  //id can be undefined or have a value here
  console.log(id);
  let age; //starting here age has the value undefined
  age = 42;
}

let f2 = function () {
  //function expression. function is NOT hoisted
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

BEST PRACTICE

While hoisting can be a useful tool, for your function declarations, it is considered best practice to always declare your function and variable declarations at the top of their scope.

# References

# What to do this week

TODO

Things to do before next week.

  • Read all the content from Modules 2.1, 2.2, and 3.1.
  • Start working on the Hybrid Exercises
Last Updated: 5/31/2023, 8:15:38 PM