You already know how to make certain parts of the code reusable by saving them into a function. You can pass arguments to a function and then refer to them as function parameters.

Today I’m going to tell you about some of the advanced features of functions in JS. This is a difficult lecture, so, if you don’t understand something - read it again, try to solve the practical tasks, and ask questions if any.

Sigma was supposed to tell you that a function is like a mathematical formula - you pass the necessary values into it, and you get the result which the function returns using the return keyword.

const mult = (x, y) => {
  return x * y;
}

The mult(x, y) function takes two numbers as parameters and returns their product.

Conveniently, functions in JS can be stored in variables. But functions have another very cool property in JS. A function can be created not only at the time of writing a program but also at runtime.

This is possible because the function does not have to return a number, string, or object.

A function can return another function.

const multiplyBy = (x) => {
  return (y) => {
    return x * y;
  }
}

const multiplyBy2 = multiplyBy(2);
const multiplyBy3 = multiplyBy(3);

console.log(multiplyBy2(10));    // 20
console.log(multiplyBy3(10));    // 30

In this example, the multiplyBy(x) function will take a value and return another function. This inner function will “remember” the value of the x parameter passed to the top-level multiplyBy(x) function and will be able to use it internally. Next, using the multiplyBy(x) function, we create two other functions multiplyBy2 and multiplyBy3. The first will take any number and multiply it by 2, the second by 3. You can verify this with the console.log at the end of the example.

Looks pretty gimmicky, but things get a lot more interesting when we come to the second important property of JS functions.

A function can be passed into another function as a parameter.

Imagine that you have an array of some data. And you need to filter it according to some rule. You don’t know the rule yet, but it would be very convenient if you could change the filtering rules along the way.

Perhaps you only want to keep items with a price of less than $100. Or maybe you would like to see only the stuff that can be delivered by the 31st of December.

In general, it would look like a function that takes an element of the array, analyzes it, and returns true if it meets certain conditions, otherwise - false. Then we could apply this function to each element of the array and figure out which ones are right for us.

So, in JS this is already implemented as a filter function built into all JS arrays. It takes a function, applies it to each element of the array, and returns a new array that consists only of those elements for which this function returned true.

const animals = ['Cow', 'Horse', 'Dog', 'Cat', 'Whale', 'Dolphin', 'Rabbit'];

const longerThan = (n) => {
  return (s) => {
    return s.length > n;
  }
}

console.log(animals.filter(longerThan(3)));  // ['Horse', 'Whale', 'Dolphin', 'Rabbit']
console.log(animals.filter(longerThan(5)));  // ['Dolphin', 'Rabbit']

The longerThan(n) function is similar to multiplyBy(x). It will return another function that will “remember” n, accept the string s, and return true if the length of the string s is greater than n. We can pass this function to filter, which will remove from the array all values for which the result is false.

Scope

As you write your program, you will create many functions and variables. Not all of them will be available to each other. To understand well the errors that will arise during the development process, you need to remember a few rules.

JS code in the base case is executed from top to bottom

This means that there are exceptions and special rules, for example, in the case of the for loop, we will move “up” after the last statement is executed and continue if the condition is true.

You cannot use variables or functions before they have been declared.

console.log(message);   // ReferenceError: Cannot access 'message' before initialization
const message = 'hello';

This code will not work because on the first line, we are trying to access the message constant, which doesn’t exist yet. If we swap the lines, everything will be fine and we will see the hello line on the screen.

You cannot use variables or functions outside the scope

Each block of code wrapped in curly braces {} creates a separate scope, which is invisible from the outside.

const animals = ['Cow', 'Horse', 'Dog', 'Cat', 'Whale', 'Dolphin', 'Rabbit'];

for (let i = 0; i < animals.length; i ++) {
  let totalLength = 0;
  totalLength += animals[i].length;
}

console.log(totalLength); // ReferenceError: totalLength is not defined

The variable totalLength is declared inside of a for loop, which is a separate block of code wrapped in curly braces. When, on the last line, we try to display its value on the screen, we get an error. It occurs because variables declared in internal blocks of code have their own scope and are invisible from the outside. Even though they existed inside.

But, note that on the contrary, this rule does not work! After all, we easily access the animals array inside the for loop.

You cannot create variables with the same name in the same block of code

const message = 'hello';
const message = 'hello world';
console.log (message); // SyntaxError: Identifier 'message' has already been declared

This rule is quite logical since if it were not there, it would be unclear which message to use when displaying.

If there are variables with the same name inside the code block and outside, then the “closest” variable will be used.

const animals = ['Cow', 'Horse', 'Dog', 'Cat', 'Rabbit'];

for (let i = 0; i < animals.length; i ++) {
  const animals = ['Whale', 'Dolphin'];
  console.log(animals[i]);
}

This is an important example, let’s break it down in detail.

  • We create an array animals with five strings 'Cow', 'Horse', 'Dog', 'Cat', 'Rabbit'.
  • We start the loop from zero to animals.length, which is five.
  • Inside of the loop, we create a new array animals with two items 'Whale', 'Dolphin'.
  • We display the elements of the array animals with indices from 0 to 4 in order. That is, as long as the condition i < animals.length is true.

Attention, question! Which of the two arrays will be used for displaying the data?

Whale
Dolphin
undefined
undefined
undefined

Although initially, we used the “upper” array animals and its length 5 to determine the number of loop iterations, then it was redefined in the internal scope and used in console.log. Eventually, since the internal animals array has only two elements, we see undefined as the last three lines.

Closures

Let’s go back to the example where we learned how to write a function that returns another function.

const longerThan = (n) => {
  return (s) => {
    return s.length> n;
  }
}

We already know that each time the longerThan(n) function is called, a new function will be created that will “remember” the n parameter that was used when it was created. You can create as many functions as you want with different values of n and for each of them n will be different. This is a closure.

A closure is a function with all external variables available to it.

This may seem simple or obvious, but if we had stored n as a regular variable, we would have failed as n would change all the time and this change would break the program logic.

FAQ

— Is it possible to write a function that will return a function that will return a function?
— Yes, it is. And you can even go deeper with that.

— Why do we need to complicate things so much? Some kind of scopes, closures.
— The increase in the complexity of the program is inevitable with the increase in the complexity of the problem. You can build a hut with sticks and stones, but a skyscraper?