So you think you know your functions?

JavaScript is known for having a steep learning curve, due to the familiar semantics and permissive nature. Even though it is easy to quickly get some level of understanding of its main features, the language’s real power relies in more advanced topics such as prototypes or first-class functions.

Functions are an essential part of JavaScript, and a function declaration is all you need to get started:

function foo() { ... }

Even better, you can declare your function through a function expression:

let foo = function() { ... }

Named function expressions can also be used:

let foo = function foo() { ... }

Even though these techniques end up defining a function, there are a couple of important distinctions between them. First of all, hoisting cause function declarations and expressions to have different results based on their location in the code source.

Hoisting

Hoisting is the term used to define the particularity of the JavaScript execution environment which makes sure that all the scope’s variable and function declarations are took into account before the actual execution starts:

// Function declaration
console.log(foo);   // 'function'
console.log(foo()); // 'foo'
function foo() {
  return 'foo';
}

// Function expression
console.log(bar);   // 'undefined'
console.log(bar()); // Throws: bar is not a function
let bar = function bar() {
  return 'bar';
}

You’ll notice that the foo function is accessible in the console.log statements even though it is defined after them. This is possible due to the fact that hoisting makes sure that any function declaration - such as foo in this scenario - is analyzed prior to any statement execution.

On the other hand, the bar function expression has a different behavior. As previously stated, variable declarations will be took into account before the execution starts. This means that the let bar variable will be available at the execution time, but it will be initialized as undefined. The variable will point to the actual function only when the let bar = function bar() assignment is executed. Obviously, this will make the bar function inaccessible prior to the assignment.

Anonymous functions

Function calls can be observed in stack traces, so naming your function expressions is fairly important:

function main() {
  var foo = function() {
    console.log('foo');
  }
  var bar = function() {
    foo();
  }
  bar();
}
main();

main contains two expressions where nameless functions are assigned to both foo and bar. These functions are called anonymous, and the execution runtime has no means to identify them. Therefore, you’ll end up with a stack trace as follows:

(anonymous function) // This is foo
(anonymous function) // This is bar
main

While this isn’t a problem in a simple example, complex projects will be much more difficult to debug if a lot of anonymous classes were to call each other. This can be easily fixed by adding a name to your function: let foo = function foo() { ... }. However, keep in mind that this is not a one size fits all solution.

ES2015 compliant JavaScript engines are able to infer the name of an anonymous function from the context. This means that you’ll have an explicit stack trace because your anonymous function is assigned to the foo variable. However, some older engines have a difficult time handling named function expressions, so you should be avoiding them if you still have to support older versions.

Finally, unlike function declarations, the name you use in a function expression will be available only inside the defined function and in stack traces. Other than that, the variable name has to be used:

let foo = function bar() {
  bar();  // successful call
}
foo();  // successful call
bar();  // throws: bar is not defined

At this point, you may lean towards function declarations since they are always accessible due to hoisting, and there is no naming complexity involved. However, function expressions turn out to be more flexible and powerful due to the first-class support in the JavaScript language.

First-class functions

JavaScrtipt has first-class function support, meaning that it treats functions as objects which can be stored in variables, passed as arguments, or returned from other function constructs. This notion shouldn’t be confused with the higher order function term which defines functions that receive other functions as arguments.

Passing functions around as lambdas is a powerful tool that enables more flexibility and better code reuse. Lambdas are functions used as data in higher order functions.

let isEven = function isEven() {
  return arguments[0] % 2 == 0;
}

let addTo = function addTo(total, number, condition) {
  return condition(number) ? total += number : total;
}

let total = 0;
total = addTo(total, 2, isEven);  // 2 will be added to total
total = addTo(total, 3, isEven);  // 3 will not be added

console.log(total); // 2

In the previous example, isEven is a lambda used by addTo to decide if a specific value should be added to the total. It is easy to see the flexible nature of this approach since the condition could be easily replaced with an isOdd, or with any other function that returns a boolean.

The isEven function shows another particularity of the JavaScript language - the arguments attribute. This is an array like list of values that contains all the arguments passed to the function. Therefore, even though there are no arguments defined in the function isEven() signature, you can still access any passed values through the internal arguments reference. This offers yet another level of flexibility to functions, but it should be used cautiously since the lack of function arguments will cause ambiguity.

Calling functions

While the function term describes a standalone construct, it is common to use the method term to describe function constructs that are linked to objects. In most scenarios, methods will be defined in the object declaration step, but JavaScript provides multiple solutions that allow calling stand-alone functions with different objects as context:

let pirate = {
  name : 'Jack'
}
let talk = function talk(greet) {
  console.log(greet + ', ' + this.name);
}

talk('Hello');  //'Hello, ''
talk.call(pirate, 'Hi');  //'Hi, Jack'
talk.apply(pirate, ['Hello']);  //'Hello, Jack'

let pirateTalk = talk.bind(pirate);
pirateTalk('Hi'); //'Hi, Jack'

The this keyword inside the talk function will point to the function context. This context is the associated object if the function is a method, and to the Window object otherwise. As a result, console.log(this.name) will output an empty name since talk() is defined as stand-alone, and therefore bound to Window.

To make this work, either call or apply can be used to set the pirate object as the function context. The only small difference between these two is the type of arguments they accept.

The downside of call and apply is that the binding between the object and the function is not permanent. You’ll have to always remember to use call or apply when working with functions and the this keyword. As a solution, bind can be used to permanently create a new function that has talk’s behavior and the pirate object as context.

Clojures

let api = (function api(){
  let version = 0.1;
  let test = function test() {
    console.log(version);
  }
  return { test: test }
})();
api.test(); // '0.1'

In short, a clojure is a stateful function. In Javascript, you can create clojures by defining functions inside other functions. Based on this simple construct, the inner function will have access to its outer scope variables, even after the outer function has returned. In the previous example, the inner function test has access to the version variable, and it can reliably make use of it at any time after the api function execution.

Scope handling in JavaScript is a common source of confusion for people new to the language. Unlike most C-family languages where variables have a lifespan defined by brackets blocks, JS variables lifespan is tied to the function they are defined in - this changes in ES2015 with the introduction of the let keyword. Clojures and function scopes are the building blocks for the mechanism used to ensure privacy in JS objects.

Finally, the clojue example shows another trick you can use to create a new scope to encapsulate modules or data - the immediately invoked function expressions, or IIFE. Just like the name implies, JavaScript allows you to execute a function as soon as it is defined: let api = (function(){ ... })().