Introduction

In the previous chapter we got introduced to the ways and syntax to create a function in JavaScript, and in the meanwhile also came across terms such as function definition, invocation and even a couple of code examples on the topic.

With this solid foundation in place, we are now ready to explore more detailed concepts in functions including the return keyword, local scoping of variables, and the wierd aspects of this.

Let's begin!

A quick recap

Before we begin, it's worth a while to recap the ideas we learnt in the previous chapter on how to create functions in JavaScript, and thus get a quick refresher to move on.

So iterating back, there are essentially two ways to create a function in JavaScript - a function declaration and a function expression. Both utilise the function keyword to create a new function.

The declaration syntax has a name following the function keyword, and serves to create a named function as shown below:

function sayHello() {
    alert("Hello World!");
}

In contrast, a function expression assigns a functional value to a given identifier which then acts as a function indirectly. This is shown below:

var sayHello = function() {
    alert("Hello World!");
}

Here the identifier is the variable sayHello, assigned to an anonymous function (a function without a name) that makes an alert, upon invocation.

The declaration suffices when the need be to just create a simple named function like callLogger, makeList, calcPercentage and so on.

The expression comes into the play when the need be to assign functions to array elements or object properties - this is otherwise impossible using the declaration syntax, since its name follows the usual naming rules.

For more info on naming rules in JavaScript please read the Variable Naming chapter

Now let's dive into the real play!

Returning values

Returning values is one of the most fundamental concepts of functions in nearly all programming languages. The return keyword is the hero in this relation.

So what's the return keyword? How to work with it? When to exactly use it? A discussion to answer these questions follows. Let's join it!

Suppose you have a function add() to add two number a and b and log their sum:

function add(a, b) {
    console.log(a + b);
}

add(2, 6);
add(10, 5);
8
15

Each time you call add() the given arguments a and b are added and ultimately logged to the console.

Now although this works well, the add() function is pretty limited to logs only - we can't use the result of the function in expressions, variables or other places.

In this regard, consider the code below:

console.log("2 + 5 is:");
console.log(2 + 5);
2 + 5 is:
7

Here we simply log a string followed by logging the addition of the numbers 2 and 5.

Now suppose we replace the expression 2 + 5 with add(2, 5), our code won't work as we expect it to:

console.log("2 + 5 is:");
console.log(add(2, 5));
2 + 5 is:
undefined

This is because the function add() doesn't return the sum, but rather logs it.

When a function returns a value, it's just like the function invocation expression being replaced with that value.

Returning is accomplished explicity using the return keyword, which is followed by the value/expression to return to the calling location of the function.

Following is the function add() rewritten to return sums rather than log them:

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

Here the return keyword is followed by the expression a + b, which will be returned when the function is called, at any place.

Now our previous code will work as expected; since this time we are manually passing the sum of adding 2 and 5, using add(2 5), to the console.log() method (rather than logging it rightaway).

console.log("2 + 5 is:");

console.log(add(2, 5));
// this is same as
// console.log(7)

Being very specific, in line 3, first add(2, 5) is resolved by adding the numbers 2 and 5, and then returning 7; and after this the console.log() method is resolved by logging the value 7.

Remember that return is not a function and likewise calling return(a + b) will throw an error. However return (a + b) will work fine because (a + b) here is considered a grouped expression.

The result of add(2, 5) can't just be used in console.log(), but rather in all places where a value/expression can be used. Following is a demonstration:

// used in a conditional
if (add(10, 10) === 20) {
    alert("You see that!"); // alert made
}

// assigned to a variable
var sum = add(30, 60);
console.log(sum); // 90

// used in an expression
console.log(3 + add(4, 5)); // 12
When a function returns a value, the place where it's invoked is replaced by that value. For example, the expression 3 + add(4, 5) simply resolves to 3 + 9.

Talking about the values that can be returned by a function, they aren't limited to any single data type.

A function can return a value, of any data type.

It can be a number, a string, a boolean, an object, and yes - even a function!

When a function returns another function we can use this returned value just like we can normally use functions - call it using a pair of parentheses, assign it to an identifier, pass it to another function and so on.

Let's see an example:

function createLogger() {
    return function() {
        console.log("Func inside func!");
    }
}

var logger = createLogger();

console.log(logger); // "function"
logger(); // "Func inside func!"

Calling createLogger() in line 7, returns a function which gets saved in logger. Afterwards, this very function is first inspected in its type using the typeof operator, which returns "function", since logger is a function. In line 10, we finally invoke this function resulting in the log "Func inside func!".

The calling expression could've also been rewritten as follows:

function createLogger() {
    return function() {
        console.log("Func inside func!");
    }
}

createLogger()(); // "Func inside func!"

Compared to the previous code, here we invoke the function returned by createLogger() immediately, without saving it anywhere (as we did in the variable logger previously).

And this is return in action!

Local Scope

As we know back from JavaScript Variables chapter, variables in JavaScript have two scopes (technically three owing to let) that defines the locations where they are accessible.

The ones declared in the main script directly belong to the global scope, and are therefore accessible everywhere in the entire script as highlighted below:

var x = 10;
console.log(x); // 10

Here x is declared in the global scope, and hence acts as a global variable.

In contrast, all variables declared inside functions are local to the function only, and not known to the outerworld. These variables are created on entrance into a function and deleted once it exits. Therefore we say that they have a local scope.

Whenever the JavaScript engine gets the task of resolving a variable, it first searches the current scope for the variable and if no match is found, then the one above it until the global scope is reached.

This is the way in which variable resolution is performed in the language.

Let's consider an example. In the code below, we create a global variable x and a function localFunc() with its own, local variable x.

var x = 50;
console.log(x); // 50

function localFunc() {
    var x = 35;
    console.log(x); // 35
}
localFunc();

console.log(x); // 50

Retrieving x outside the function (in line 2) resolves to the global variable and thus, logs 50. Unlike this, retrieving x inside the function (in line 9) resolves to the local variable and thus logs 35.

We say that the local variable x hides/shadows/overrides the global variable x.

Once localFunc() exits, this local x gets deleted and consequently the global x takes over once again. A very elementary working for resolving variables.

However it's not always a global vs local game - we can also experiment with local scopes only; by nesting functions within functions. This is illustrated as follows. Try to work your way through this code very gradually, understanding each statement as you do so:

function localFunc() {
    var x = 35;
    console.log(x); // 35

    function moreLocalFunc() {
        var x = 20;
        console.log(x); // 20
    }
    moreLocalFunc();

    console.log(x); // 35
}
localFunc();

In line 2, we declare a variable x with the value 35, local to localFunc(), and log it in line 3. After this we create another function moreLocalFunc() and within it declare a separate local variable x with the value 20.

Anywhere within moreLocalFunc() the value of x will remain 20, NOT 35; since the function creates another local variable x. Therefore when we invoke moreLocalFunc() in line 9, we get 20 logged.

Finally, in line 11 we once again log the variable x of localFunc() which is equal to 35.

What we are trying to showcase here is that each function creates its own local scope within which newly declared variables are put in. This scope is accessible only within the function itself - the moment at which control passes to outside the function, the scope and its variables are deleted completely.

But what's the practical significance of this idea of local scoping?

With each function having its own scope, multiple functions can use the same named identifiers without clashing with each other, and as a whole keep all their behaviours and procedures internal to them only.

Had there been no concept of local scoping, variables declared within functions would've been added to the global scope, and in worst cases lead to the assignments overriding previous values saved into the variables.

The procedures happening within functions wouldn't have been private to the functions only and rather been interrupting behaviours in other functions.

To boil it down, it would've been a complete mess without local scopes!