7 ways how to define functions in JavaScript

I remember that I was getting confused from time to time when I was learning JavaScript many years ago.

One of the strange things was how people used functions definitions in their code. When I looked at code at code from friends or co-workers I quickly discovered that there are many ways how to create functions in JavaScript.

But I couldn't figure out why people choose one over another.

This list will bring offers you a clear understanding of the benefits and downsides of each way.

You'll write a lot of functions when you become a JavaScript developer. Time to step up the game.

Function Declaration

loveUnicorns();
function loveUnicorns() {
  console.log('I love unicorns');
}
// Output: "I love unicorns"

The common way how to create functions. Those functions are hoisted to the top of the script. That's even the term for it: Hoisting. The JavaScript interpreter parses the script for those declarations and loads those into memory before the it starts evaluating the script. That's why you can call the function even before the interpreter ran through the function declaration.

Note that this is not an expression. It doesn't end with ;.

As it's not part of the step-by-step run you cannot put it into control statements like if, try, for, while, etc.

Bad example

if (desiredState) {
  function foo() { console.log('bar'); }
}

Anonymous Function Expression

var loveUnicorns = function() {
  console.log('I love unicorns');
};
loveUnicorns();
// Output: "I love unicorns"

The function is only available once the interpreter evaluated the function expression. In this case a variable has been defined that stores the function.

Anonymous functions don't have names. That's why they are anonymous. Here is how to find out.

console.log(loveUnicorns.prototype.constructor.name); // Output: ""

function expressions can be inside of control statements.

if (you.mood == 'evil') {
  var handleUnicorn = function(unicorn) {
    unicorn.destroy();
  };
} else if (you.mood == 'nice') {
  var handleUnicorn = function(unicorn) {
    unicorn.feedRainbows();
  };
} else {
  var handleUnicorn = function(unicorn) {
    console.log('Unicorn appeared: Doing nothing');
  };
}
unicorn = new Unicorn();
unicorn.onappear = handleUnicorn;
unicorn.run();

Named Function Expression

var loveUnicorns = function loveUnicorns() {
  console.log('I love unicorns');
};
console.log(loveUnicorns.prototype.constructor.name);
// Output: "loveUnicorns"

The function name has been added here. That's the only change to the anonymous function expressions.

Let me give you another example that makes it more clear.

var a = function b() { /* do something */ };
console.log(a.prototype.constructor.name);
// Output: "b"

Note that a is just a variable that holds a function with the name b.

Why should you even care about the function name? Because it will help you to debug errors. In the older days the browser just referred to an (anonymous function)  when something went wrong in the call stack. It's better to have a name there. This has changed with ES2015, though. The function name will automatically be the variable name.

Accessor Function (since ES5+)

Accessor functions are newer and not as widely used as function declarations and function expressions.

var unicorn = {
  _rainbowsEaten: 1,
  get rainbowsEaten() {
    return this._rainbowsEaten;
  },
  set rainbowsEaten(value) {
    this._rainbowsEaten = value;
  }
};
console.log(unicorn.rainbowsEaten);
// Output: 1
console.log(typeof(unicorn.rainbowsEaten));
// Output: "number"

unicorn.rainbowsEaten = 2;
console.log(unicorn.rainbowsEaten);
// Output: 2

This approach let's you run code without using (). That is normal in object orientated programming and now JavaScript can do this, too.

The idea is to let the code look like simple variable when in the background there is a function call.

You could use this to validate data that gets passed to the object.

var cat = {
  _lives: 9,
  get lives() {
    return this._lives;
  },
  set lives(value) {
    if (value >= 0 && value <= 9) {
      this._lives = value;
    } else {
      throw new Error('Invalid value for cat.lives: ' + value);
    }
  }
};
cat.lives = 20;
// Output: Uncaught Error: Invalid value for cat.lives: 20

Note that cat.lives only lets you assign values from 0 to 9.

Use this approach when using a normal variable for an object would feel natural but you need a little bit of control.

Arrow Function Expression (ES2015+)

This is pretty new in JavaScript and I love it a lot. Normally I write a lot of CoffeeScript because I work on a lot of Rails projects. Arrow functions are normal in CoffeeScript.

var partyGroup = [berit, gambo, bodo, saskia, felix, nino, marcella];
var ages = partyGroup.map(person => person.age);
var ageSum = 0
ages.forEach(age => ageSum += age);
var ageAvg = Math.round(ageSum / partyGroup.length);
console.log(ageAvg);
// e.g. Output: 25

What just happend here?

Have a look at line 4 and look for =>. That's called a fat arrow or a hashrocket.

It creates a function just like you would do with the anonymous function expression, but without using the function keyword. It lexically binds this  to what in the outer scope. That means you can use without binding this to the function or doing something like var that = this;.

Use {} for multi-line anonymous function expressions.

var goodMovies = [];
budSpencerMovies.forEach(movie => {
  // Pretending to use Lowdash here.
  // Just reads better :)
  if (_(movie.actors).contains('Terence Hill')) {
    goodMovies.push(movie);
  }
});

Method Declaration in Objects (ES2015+)

unicorn = {
  fart() {
    console.log(',.-~*´¨-(RAINBOW)-¨`*·~-.');
  }
}

Here the ES5 version and earlier.

unicorn = {
  fart: function fart() {
    console.log(',.-~*´¨-(RAINBOW)-¨`*·~-.');
  }
}

It's just a little syntactic sugar around the named function expression.

Method Declaration in Classes (ES2015+)

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  get fullName() {
    return this.firstName + " " + this.lastName;
  }
}
var bestActor = new Person('Bud', 'Spencer');
console.log(bestActor.fullName);
// Output: "Bud Spencer"

bestActor.fullName = "Terence Hill";
// Output: Uncaught TypeError: Cannot set property fullName of #<Person> which has only a getter

There are two functions here. The constructor and the getter for the full name. The constructor will be called when the with the new Person(); call. The fullName accessor is only for the get. You cannot use it to set a name.

Conclusion

There are many ways to write functions. But besides all the different ways how to write them, there are still only two types of functions. There are a few function declarations and there are function expressions.

Your takeaways

Declared functions are hoisted and cannot be used in control structures such as if, for, etc.

function expressions have to be evaluated before they can be used. This allows you to put them into control structures. Name them if you can.

Best, Christian