Module 5

Functions

Declaring functions at the beginning of the code is just good practice, not a JavaScript syntax requirement. Function declarations are automatically moved to the top of the scope, so we can use them before the declaration as long as they are in the scope. So,

let name = Alice

function showName() {
    console.log(name);
}

showName(); // -> Alice

will work exactly the same as:

let name = Alice

showName(); // -> Alice


function showName() {
    console.log(name);
}

Functions as first-class members

This term means that functions can be treated as any data, which can be stored in variables or passed as arguments to other functions.

function showMessage(message) {
    console.log('Message: ${message}');
}

let sm = showMessage; // showMessage function stored in variable sm
sm("This works!"); // -> Message: This works!
console.log(typeof sm); // -> function

This property is especially useful when passing the function as a call parameter to other functions.

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

function multiply(a, b) {
    return a * b;
}

function operation(func, first, second) {
    return func(first, second);
}

console.log(operation(add, 10, 20)); // -> 30
console.log(operation(multiply, 10, 20)); // -> 200

Function expressions

To store a function in a variable or pass it as an argument to call a function, you do not necessarily have to declare it previously and use its name. Below, we first declare the add function and then store it in the variable myAdd.

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

let myAdd = add;
console.log(myAdd(10, 20));     // -> 30
console.log(add(10, 20));   // -> 30

Instead, we can shorten this notation and declare the add function, and at the same time store it in the myAdd variable. This form of defining a function is called function expression. In this case, it is specifically a named function expression, because the function has a name (add).

let myAdd = function add(a, b) {
    return a + b;
}

console.log(myAdd(10, 20)); // -> 30
console.log(add(10, 20)); // -> 30

anonymous function expression

For an anonymous function expression, simply remove the name following the function keyword to change the function to anonymous.

let myAdd = function(a, b) {
    return a + b;
}
console.log(myAdd(10, 20)); // -> 30

This will be much more evident when passing a function as a call parameter to another function.

function operation(func, first, second) {
    return func(first, second);
}

let myAdd = function(a, b) {
    return a + b;
}

console.log(operation(myAdd, 10, 20)); // -> 30

console.log(operation(function(a, b) {
    return a * b;
}, 10, 20)); // -> 200

It only gets interesting when we call the operation function again. This time, the first argument is the anonymous function (again the function expression), which is defined directly in an operation call. The result is a multiplication, although the name of the new function (or the variable in which it could be placed) will not appear anywhere. The function has been defined only to pass it once into the operation function. At first glance, it may look like a completely useless mechanism, but in the real world, it is used very often.

Callback functions

  • The simple meaning of a callback function is that it's a something-handler function for some event.

  • A function that receives a callback as an argument can call it anytime.

Synchronous callbacks

Subsequent instructions are executed in the order in which they are placed in the code. If you call a function, the instructions in it will be executed at the time of the call.

let inner = function() {
    console.log('inner 1');
}

let outer = function(callback) {
    console.log('outer 1');
    callback();
    console.log('outer 2');
}

console.log('test 1');
outer(inner);
console.log('test 2');

Execution of the above code will cause the console to print out the following text in this exact order:

test 1
outer 1
inner 1
outer 2
test 2

Asynchronous callbacks

Using appropriate functions, we combine a specific type of event with a selected callback function, which will be called when the event occurs.

One of the simplest cases when there is an asynchronous execution of instructions is the use of the setTimeout function. This function takes another function (a callback) and the time expressed in milliseconds as arguments. The callback function is executed after the specified time, and meanwhile, the next program instruction (placed in the code after setTimeout) will be executed.

Thus, the moment the callback function is called is not determined by its order, but by an arbitrarily imposed delay. The delay only applies to the callback function given to setTimeout, while the rest of the code is still executed synchronously.

let inner = function() {
console.log('inner 1');
}

let outer = function(callback) {
console.log('outer 1');
setTimeout(callback, 1000) /*ms*/;
console.log('outer 2');
}

console.log('test 1');
outer(inner);
console.log('test 2');

The result is actually a bit different than we observed in the previous example :

test 1
outer 1
outer 2
test 2
...
inner 1

setTimeout and setInterval functions

The setTimeout function is used when you want to cause a delayed action. A similar function is setInterval. This time, the action is also performed with a delay, but periodically, so it is repeated at fixed intervals. In the meantime, the main program is executed, and at every specified time, the callback given as an argument for a setInterval call is called.

Interestingly, the setInterval function returns an identifier during the call, which can be used to remove the timer used in it (and consequently to stop the cyclical callback function call). We will do this in the next example. First, we run setInterval, which will call the callback function (i.e. the inner function) in one-second intervals. Then we call setTimeout, which will turn off the timer associated with the previously called setInterval after 5.5 seconds. As a result, the inner function should be called five times. In the meantime, the rest of the program will be executed ...

let inner = function() {
console.log('inner 1');
}

let outer = function(callback) {
    console.log('outer 1');
    let timerId = setInterval(callback, 1000) /*ms*/;
    console.log('outer 2');

    setTimeout(function(){
        clearInterval(timerId);
    }, 5500);
}

console.log('test 1');
outer(inner);
console.log('test 2');

setInterval is asynchronous and uses anonymous function expression (i.e. the inner function).

setInterval is asynchronous and have anonymous function which is defined directly in an operation call.

output :

outer 1
outer 2
test 2
...
inner 1
inner 1
inner 1
inner 1
inner 1

Usually, however, asynchronous function calls are related to slightly different situations. They are determined by events not related to timers, but rather generated outside of the program. As we have said before, they can be, for example, actions performed by the user, such as a mouse click on an interface element on a page.

addEventListener

The window in which this page is located is represented in the client-side JavaScript by a global window variable. The window object has a method named addEventListener. This function allows you to register a certain action to be performed in response to a window-related event. Such an event can be a "click", which is a single mouse click on any place on the page.

The action to be taken is passed to the addEventListener method as a callback function.

window.addEventListener("click", function() {
    console.log("clicked!");
});

Nothing special should happen immediately after it is started. Only when you click anywhere on the page should a message appear on the console: "clicked!". Our function is not called until the "click" event occurs, which is absolutely asynchronous. In the meantime, between subsequent clicks, the rest of the program could be executed.

In fact, it is not a very good idea to connect a click response to a window object. Most often, such actions are associated with specific elements of the interface (buttons, checkboxes, etc.) which allow for their differentiation. This is only to demonstrate a function call with a user-generated event.

Arrow functions

  • An arrow function is a shorter form of a function expression.

  • We can, therefore, assume that both ways of defining functions, i.e., function expression and arrow function expression, allow you to define identically working functions.

For example, the function add, which we already know:

let add = function(a, b) {
    return a + b;
}
console.log(add(10, 20)); // -> 30

can be written as follows:

let add = (a, b) => {
    return a + b;
}
console.log(add(10, 20)); // -> 30

or simplified even more (the function has only one statement, whose value returns):

let add = (a, b) => a + b;
console.log(add(10, 20)); // -> 30

The arrow expression is mainly used for short functions, often anonymous, which can be presented as even more compact in this form. They differ from ordinary functions by one more thing apart from the form of notation, in other words, how the keyword this is interpreted inside them.

One typical example of using arrow functions is the forEach method, available in Array type data. The forEach method is another, and frankly speaking, currently the most used one. This method takes as an argument ... a function. This function will be called each time for each element of the array. We can create any function for this purpose. There is one condition, which is that it must have at least one parameter.

let names = ['Alice', 'Eve', 'John'];
function showName(element) {
    console.log(element);
}
names.forEach(showName); // -> Alice, Eve, John

The showName function has been passed as a call argument to the forEach method of the names array. Therefore, showName will be called three times, for each element of the names array, and in each call its parameter will be equal to the successive name, i.e. in turn: Alice, Eve and John. The only thing showName has to do is to display the received element (name).

The same effect can be achieved by passing an anonymous arrow function to the forEach method.

let names = ['Alice', 'Eve', 'John'];

names.forEach(a => console.log(a));

We do not even store it in a variable because we assume that we will use it only here and will not refer to it again.

Summary

Third-party functions are usually made available in the form of libraries (i.e., sets of functions), which are dedicated to solving a specific class of problems, for example, Leaflet (maps), D3.js (data visualization), or jQuery (DOM manipulation).