Functions in JavaScript

Featured

The latest version of JavaScript (ES2015) allows us to write functions in a different way by using the arrow function syntax. This not only allows for terser function declarations but also has additional benefits.

Functions are a part of your code that implement a specific task. The most important thing to remember about functions is that they are reusable – which means that they can be called at any point in time and perform the action that they are programmed for.

Imagine that we have an application that needs to greet people, we really wouldn't want to write something like console.log('Hello [name]') multiple times, right? In order to make our lives better we can organise this logic into a function and just call the function when needed. To stick with our example the function could look like this:

function greet(name) {
 return 'Hello' + name;
}

console.log(greet('Steve')); // Hello Steve
console.log(greet('Ann')); // Hello Ann

Of course, functions are normally larger and they can be really complex, achieving advanced functionality.

arrow functions

Arrow functions, or sometimes also referred to as 'fat arrow' functions, allow you to define functions in a terser way in ES2015.

Take a look at this example, probably you have written something similar to this code before:

const numbers = [0, 1, 2];
numbers.map(function(number) {
  return console.log(number);
});

The above can be rewritten using the arrow functions to look like the code below by removing the function keyword and replace it with the syntax of =>. There are some additional rules around on how arguments should be handled as well as how the return keyword is used (we'll discuss these later):

const numbers = [0, 1, 2];
numbers.map(number => console.log(number));

this

The difference between regular JavaScript functions and these new arrow functions is not only the fact that you can have a shorter function signature but that this is lexically bound to the calling function

This last sentence may be a bit confusing (excuse the pun) so let's take a look at how the this keyword works in JavaScript as you (may) know it today.

Take the following, relatively simple example: we have a JavaScript object here that allows us to calculate quotients:

const quotient = {
  numbers: [1, 2, 3, 4, 5, 6, 7],
  results: [],
  divideFn: function(divisor) {
    return this.numbers.map(function (divident) {
      if (divident % divisor === 0) {
        return this.results.push(divident);
      }
    });
  }
};

We have a numbers property where we store our sample dataset, we have a results property where we will place our results and finally a divideFn() that goes through each number and make sure that the number is dividable by the parameter passed to the function itself. Notice that if the module operator (that's the % sign) returns 0 – in other words, if the number is dividable - we'll add that number to our results array.

To run this example, add these two lines to find numbers that can be divided by 3:

quotient.divideFn(3);
console.log(quotient.results);

If we try to run this example we'll face an error, namely, we'll see a message similar to Cannot read property 'push' of undefined. Okay, this is weird - results is definitely an array and therefore it should have a push() method.

If we start to debug this we'll realise that the problem is with the execution context, and with the keyword this. Try to update the code and add a console.log(this); statement just before the second return statement in the if block.

Depending if we run this example using Node.js or our browser we'll see a different result, but nevertheless we will notice that logging the value of this yields an object – but not our object (quotient) in this case.

Let's discuss this a bit further. Notice how we use a .map() function that allows us to iterate through the numbers array. .map() then has an anonymous callback function as well and inside this function is where we have our if statement and where we utilise this. Therefore, this, in this case, is contextually sensitive, meaning that the value of this is referencing the anonymous function, which in turn is added to the global scope.

There are various solutions to "this" problem in JavaScript today (excuse the pun, again). The first one is to create another variable and assign this to it (let's also print the value of that) – this is how the updated code could look like:

const quotient = {
  numbers: [1, 2, 3, 4, 5, 6, 7],
  results: [],
  divideFn: function(divisor) {
    let that = this;
    return this.numbers.map(function (divident) {
      if (divident % divisor === 0) {
        console.log(that);
        return that.results.push(divident);
      }
    });
  }
};

Now we won't see the error and also the value of that is going to be our object.

Another widely used solution is to call the .bind() method passing in this as the argument. Essentially the .bind() method creates a new function scope and therefore it will have the this keyword set to a value provided. Let's try this out as well, and we should see the same result as before:

const quotient = {
  numbers: [1, 2, 3, 4, 5, 6, 7],
  results: [],
  divideFn: function(divisor) {
    return this.numbers.map(function (divident) {
      if (divident % divisor === 0) {
        return this.results.push(divident);
      }
    }.bind(this));
  }
};

Even though these are all good solutions, when using the ES2015 variant of JavaScript we don't need to use any of these. Using the arrow functions will keep the value of this lexically bound to the calling function. Therefore our example can be rewritten to be:

const quotient = {
  numbers: [1, 2, 3, 4, 5, 6, 7],
  results: [],
  divideFn: function(divisor) {
    return this.numbers.map(divident => {
      if (divident % divisor === 0) {
        return this.results.push(divident);
      }
    });
  }
};

Running this example will no longer throw errors and will execute correctly, with the expected end result.

This is a huge addition in my opinion to JavaScript – such anonymous callback functions are being used in popular JavaScript libraries such as jQuery where developers are facing the issues with the this keyword quite frequently.

Let’s take a look at the basic syntax rules behind the arrow functions. Just remember the following:

  • When the function has one argument, do not use parentheses
  • If the function has multiple arguments, use parentheses
  • Curly braces are only required when you have multiple lines of code, otherwise it’s implicit and the curly braces shouldn’t be used
  • If you want to return an object you need to wrap the object in parentheses
// return is implicit
const numbers = [0, 1, 2];
numbers.map(number => console.log(number));

// curly brackets, use return:
numbers.map(number => {
  number *= 2;
  return console.log(number);
});

You can also use arrow functions to define non-callback functions. In the following example, we can see this and also see how we can return an object by using parentheses:

const greet = (name, age) => ({
  name: name,
  age: age
});

console.log(
  greet('Steve', 18)
);