An introduction to Javascript Functions

This post is a basic introduction to functions in Javascript. It demonstrates how functions in Javascript can be used like objects.

What is a function in Javascript?

A Javascript function is a first-class object, i.e., it is just like any other object. This means that:

  1. We can pass functions as arguments to other functions. These passed functions are usually called callbacks.
  2. We can return functions from other functions just like we return variables.
  3. We can assign attributes to functions just like other objects.

Let us explore each of the above cases with examples.

Passing functions as arguments

Execute the following code:

const main=function(func)
{
  console.log(`Function '${func.name}' was called.`);
  func();
}
const helloWorld=function(){console.log("Hello World");}
const welcome=function(){console.log("Welcome to Codespeedy");}
main(helloWorld);
main(welcome);

The browser output is:

Function 'helloWorld' was called.
Hello World
Function 'welcome' was called.
Welcome to Codespeedy

Let us understand this:

We create three variables ‘main’, ‘helloWorld’ and ‘welcome’ and assign a function to each of them. The variables are marked as ‘const’ to prevent them from being reassigned.
We call the functions contained by those variables just like a regular function call, i.e., by suffixing the variable name with ‘()’. Required function arguments are placed inside the parenthesis during the function call.

Our ‘main’ function takes in a function as an argument and stores it in the ‘func’ variable. That function is then called by executing ‘func()’. This executes the function stored by func.

At the bottom, we call our ‘main’ function twice, once with the function ‘helloWorld’, and once with the function ‘welcome’.
Both these functions are executed by the ‘main’ function.

Since our ‘main’ function executes the function stored in ‘func'(by calling ‘func()’), we must pass in a function to ‘main’. Passing any other data type will give us an error. When we execute:

main("Not a function but a string.");

we get the following message on the browser console:

Uncaught TypeError: func is not a function

This is followed by a stack trace of the error. The error tells us that as we did not pass in a function to ‘main’, the ‘func()’ function call failed. This happened because the value of ‘func’ was “Not a function but a string.”, which could not be called as a function.

Functions as return values.

Functions, like objects, can be used as the return values of other functions. One of the best examples to demonstrate this type of behavior is by the use of decorators.
Decorators are an integral feature of the Python programming language. At its simplest form, a decorator is a function that takes in a function as an argument and returns a new function. The new function is a wrapper around the original function call but has some modifications. We can pass additional arguments to the decorator to specify what modifications we would like to have.

Let us create our own decorator as an example.

We want our decorator to perform the following actions:

  1. It must notify us before our function starts execution.
  2. It must notify us after our function ends execution.

Create your decorator like so:

const decorator=function(func)
{
  const new_func=function()
  {
    console.log(`Before function ${func.name}.`);
    func();
    console.log(`After function ${func.name}.`);
  };
  return new_func;
}

Our decorator function is named ‘decorator’. As seen in the previous topic, it takes in a single function as an argument. Inside our decorator, a new function named ‘new_func’ is created.
Function ‘new_func’ is just a wrapper around the call to the original function ‘func’. However, observe that we have two logging statements before and after ‘func’ executes. When ‘new_func’ executes, the first message is logged to the console. The original function is then called. Upon its exit, the second message is logged to the console.

Now create a simple function such as:

const original_func=function(){console.log("Inside the original.");}

And call our modified function:

decorator(original_func)();

The console output we get is:

Before function original_func.
Inside the original.
After function original_func.

When we call the decorator with our function like ‘decorator(original_func)’, the value returned is the modified function. We run this modified function instantly by adding the parentheses after it.

Note that we also could have done this:

const modified_func=decorator(original_func);
modified_func();

We are assigning the modified function to a variable, and can now call our modified function via that variable multiple times in our code without having to rerun the decorator. The output would be the same in each case.

Assigning attributes to functions.

By definition, a function is a programmatical construct that takes in some input, processes it, and returns some output. A function has no internal state of its own. However, there are some use cases where it is useful for a function to have some inner state that is visible to the function only. As Javascript functions are objects, we can make this possible.

Consider a function that contains a numeric variable as its internal state. The function returns the number after it every time it is called. The variable is initialized to 0. Let us analyze this:

  1. Our function must have some state that contains this number. State that must be preserved between function calls.
  2. We cannot create a variable inside the function as it would go out of scope and get reset every time we called our function.
  3. We do not want to create a global variable as it could conflict with other global variables and pollute the global namespace.

Fortunately, Javascript allows us to set attributes to functions. Consider the following code snippet:

const getNext=function()
{
  if(getNext.state===undefined)
    getNext.state=0;
  return getNext.state++;
};

The internal state of our ‘getNext’ function is stored in the ‘state’ attribute. When our function is called for the first time, it has no attributes. Thus, we first check if the attribute exists or not by comparing it against ‘undefined’.

If the attribute is undefined, it never existed before. Thus, we are in our first function call and can safely set the state attribute to 0.

During subsequent function calls, the if condition will fail. This is because the ‘state’ attribute is no longer undefined. It has been set and it exists. In this case, the return statement is executed directly. The internal state is simultaneously incremented.

When we call our function repeatedly in a for loop, we see that consecutive values are printed from 0 onwards.

for(let i=0;i<5;++i)
  console.log(getNext());

Our browser output is:

0
1
2
3
4

 

Leave a Reply

Your email address will not be published.