Functions and Functional Programming

In this chapter, you will learn how to write functions in JavaScript. JavaScript is a “functional” programming language. Functions are first-class values, and functions can consume and produce other functions. Mastering a functional programming style is essential for working with modern JavaScript.

This chapter also covers the JavaScript parameter passing and scope rules, as well as a detailed coverage of throwing and catching exceptions. Finally, you now have all the tools to understand processing of tagged template literals.

Defining Functions

In JavaScript, you define a function by providing:

  1. The name of the function
  2. The names of the parameters
  3. The body of the function, which computes and returns the function result

You do not specify the types of the function parameters or result. Here is an example:

function average(x, y) {
  return (x + y) / 2
}

The return statement yields the value that the function returns.

To call this function, simply pass the desired arguments:

let result = average(6, 7) // result is set to 6.5

What if you pass something other than a number? Whatever happens, happens. For example:

result = average('6', '7') // result is set to 33.5

When you pass strings, then the + in the function body concatenates them. The resulting string '67' is converted to a number before the division by 2.

That looks rather horrific to a Java or C++ programmer who is used to compile-time type checking. Indeed, it isn't so wonderful in practice. If you mess up argument types, then you only find out when something strange happens at runtime. On the flip side, you can write functions that work with arguments of multiple types, which can be convenient.

The return statement returns immediately, abandoning the remainder of the function. Consider this example—an indexOf function that computes the index of a value in an array :

function indexOf(arr, value) {
  for (let i in arr) {
    if (arr[i] === value) return i
  }
  return -1
}

As soon as a match is found, the index is returned and the function terminates.

Some programmers are opposed to nonlinear control flow and insist that only the last statement of a function should return the result—see TODO.

A function may choose not to specify a return value. If the function body exits without a return statement, or a return keyword that isn't followed by an expression, the function returns the undefined value. This usually happens when a function is solely called for a side effect.

As mentioned in Chapter 2, a return statement should always have at least one token before the end of the line, to avoid automatic semicolon insertion. For example, if a function returns an object, put at least the opening brace on the same line:

return {
  ...
}

If the function returns nothing, add a semicolon

return;

Or tell it how it is:

return undefined

Higher-Order Functions

JavaScript is a functional programming language. Functions are values that you can store in variables, pass as arguments, or return as function results.

For example, we can store the average function in a variable:

let f = average

Then you can call the function:

let result = f(6, 7)

When the expression f(6, 7) is executed, the contents of f is found to be a function. That function is called with arguments 6 and 7.

We can later put another function into the variable f:

f = Math.max

Now when you compute f(6, 7), the answer becomes 7, the result of calling Math.max with the provided arguments.

Here is an example of passing a function as an argument. If arr is an array, the method call

arr.map(someFunction)

applies the provided function to all elements, and returns an array of the collected results (without modifying the original array). For example,

result = [0, 1, 2, 4].map(Math.sqrt)

sets result to

[0, 1, 1.4142135623730951, 2]

The map method is sometimes called a higher-order function: a function that consumes another function.

Function Literals

Let us continue the example of the preceding section. Suppose we want to multiply all array elements by 10. Of course, we can write a function

function multiplyBy10(x) { return x * 10 }

Now we can call:

result = [0, 1, 2, 4].map(multiplyBy10)

But it seems a waste to define a new function just to use it once.

It is better to use a function literal. JavaScript has two syntactical variants. Here is the first one:

result = [0, 1, 2, 4].map(function (x) { return 10 * x })

The syntax is straightforward. You use the same function syntax as before, but now you omit the name. The function literal is a value that denotes the function with the specified action. That value is passed to the map method.

By itself, the function literal doesn't have a name, just like the array literal [0, 1, 2, 4] doesn't have a name. If you want to give the function a name, do what you always do when you want to give something a name—store it in a variable:

let average = function (x, y) { return (x + y) / 2 }

Think of anonymous function literals as the “normal” case. A named function is a shorthand for defining a function literal and then giving it a name. (There is a small difference in the scopes of named functions and variables—see XREF.)

Arrow Functions

In the preceding section, you saw how to define function literals with the function keyword. There is a second, more concise form that uses the => operator, usually called “arrow”:

let average = (x, y) => (x + y) / 2

You provide the parameter variables to the left of the arrow and the return value to the right.

If there is a single parameter, you don't need to enclose it in parentheses:

let multiplyBy10 = x => x * 10

If the function has no parameters, you need an empty set of parentheses:

let dieToss = () => Math.trunc(Math.random() * 6) + 1

Note that dieToss is a function, not a number. Each time you call dieToss(), you get a random integer between 1 and 6.

If an arrow function is more complex, then you place its body inside a block statement. Use the return keyword to return a value out of the block:

let indexOf = (arr, value) => {
    for (let i in arr) {
      if (arr[i] === value) return i
    }
    return -1
  }

If an arrow function does nothing but return an object literal, then you must enclose the object in parentheses:

let stats = (x, y) => ({
    average: (x + y) / 2,
    max: Math.max(x, y) / 2,
    ...
  })

Otherwise, the braces would be parsed as a block.

There is a technical difference between function literals defined with the function keyword and with the => operator. Arrow functions do not have a this parameter, and they cannot be called with new. That matters when you define methods and constructors, as you will see in chapter 4. When defining functions, you can always use arrow expressions.

Closures

The setTimeout function takes two arguments: a function to execute later, when a timeout has elapsed, and the duration of the timeout in milliseconds. For example, this call says Goodbye in ten seconds:

setTimeout(() => console.log('Goodbye'), 10000) 

Let's make this more flexible:

function sayLater(text, when) {
  let task = () => console.log(text)
  setTimeout(task, when)
}

Now we can call:

sayLater('Hello', 1000)
sayLater('Goodbye', 10000)

Look at the variable text inside the arrow expression () => console.log(text). If you think about it, something nonobvious is going on here. The code of the arrow expression runs long after the call to sayLater has returned. How does the text variable stay around? And how can it be first 'Hello' and then 'Goodbye'?

To understand what is happening, we need to refine our understanding of a function. A function has three ingredients:

  1. A block of code
  2. Parameters
  3. The free variables—that is, the variables that are not parameters and not defined inside the code

A function with free variables is called a closure.

In our example, text is a free variable of the arrow expression. The data structure representing the closure stores a reference to the variable when the function is created. We say that the variable is captured. That way, its value is available when the function is later called.

In fact, the arrow expression () => console.log(text) captures a second variable, namelyconsole. Inside the arrow expression, console means exactly what it meant outside.

But how does text get to have two different values? Each call to sayLater creates a different function, each with its own binding of the text and console variables.

In JavaScript, a captured variable is a reference to another variable, not its current value. If you change the captured variable, the change is visible in the closure. Consider this case:

let text = 'Goodbye'
setTimeout(() => console.log(text), 10000)
text = 'Hello'

In ten seconds, the string 'Hello' is printed, even though text contained 'Goodbye' when the closure was created.

The lambda expressions and inner classes in Java can also capture variables from enclosing scopes. But in Java, a captured local variable must be effectively final—that is, its value can never change.

Capturing mutable variables complicates the implementation of closures in JavaScript. A closure must remember not just the initial value, but the location, of the captured variable. And the captured variable must be kept alive for as long as the closure exists.

The fundamental idea of a closure is very simple. A free variable inside a function means exactly what it means outside. However, the consequences are profound. It is very useful to capture variables and have them accessible indefinitely. The next section provides a dramatic illustration, by implementing objects and methods entirely with closures.

Hard Objects

Let's say we want to implement bank account objects. Each bank account has a balance. We can deposit and withdraw money.

We want to keep the object state private, so that nobody can modify them, except through methods that we provide. Here is an outline of a factory function:

function createAccount() {
  ...
  return {
    deposit: amount => { ... }
    withdraw: amount => { ... }
    getBalance: () => ...
  }
}

Then we can construct as many accounts as we like:

let harrysAccount = createAccount()
let sallysAccount = createAccount()
sallysAccount.deposit(500)

Note that an account object contains only methods, not data. After all, if we added the balance to the account object, anyone could modify it. There are no “private” properties in JavaScript.

Where do we store the data? It's simple—as local variables in the factory function:

function createAccount() {
  let balance = 0
  return {
    ...
  }
}

We capture the local data in the methods:

function createAccount() {
  ...
  return {
    deposit: amount => {
        balance += amount
      }
    withdraw: amount => {
        if (balance >= amount) 
          balance -= amount
      }
    getBalance: () => balance
  }
}

Each account has its own captured balance variable, namely the one that was created when the factory function was called.

You can provide parameters in the factory function:

function createAccount(initialBalance) {
  let balance = initialBalance + 10 // Signon bonus
  return {
    ...
  }
}

Or, if you don't want to give a signon bonus, just capture the parameter variable instead of a local variable:

function createAccount(balance) {
  return {
    deposit: amount => {
        balance += amount
      }
    ...
  }
}

At first glance, this looks like an odd way of producing objects. But these objects (which Douglas Crockford, the author of “JavaScript—the Good Parts”, calls hard objects) have two significant advantages. Once you understand closures, they are dead simple. The state, consisting solely of captured local variables of the factory function, is automatically encapsulated. After learning in Chapter 4 how JavaScript objects, classes, methods, and the dreaded this parameter work, you may well come back to this much more elegant approach.

Throwing Exceptions

If a function is unable to compute a result, it can throw an exception. Depending on the kind of failure, this can be a better strategy than returning an error value such as NaN or undefined.

You use a throw statement to throw an exception:

throw value

The exception value can be a value of any type, but it is conventional to throw an error object. The Error function produces such an object with a given message string.

let reason = 'someFunction: Element ' + elem + ' not found'
throw Error(reason)

When the throw statement executes, the function is terminated immediately. No return value is produced, not even undefined. Execution does not continue with the function call but instead in the nearest catch or finally statement, as described in the following sections.

Exception handling is a good mechanism for unpredictable situations that the caller might not be able to handle. It is not so suitable for situations where failure is expected. Consider parsing user input. It is exceedingly likely that some users provide unsuitable input. In JavaScript, it is easy to return a “bottom” value such as undefined, null, or NaN (provided, of course, those could not be valid inputs). Or you can return an object with properties such as { success: ...} or { failure: ... }.

Do not throw an exception if you expect the caller of a function to catch it.

Scopes and Hoisting

The scope of a variable is the region of a program where the variable can be accessed. In programming languages such as Java or C++, the scope of a local variable (that is, a variable defined inside a function) extends from the point where the variable is declared until the end of the enclosing block. In JavaScript, a local variable declared with let has the same behavior:

{ // Start of block
  ... // Attempting to access someVariable throws a ReferenceError
  let someVariable // Scope starts here
  ... // Can access someVariable, value is undefined
  someVariable == 42
  ... // Can access someVariable, value is 42
} // End of block, scope ends here

Unfortunately, it is not quite so simple. You can access local variables in functions whose declarations precede the variable declaration:

{ 
  function doWork() {
    console.log(someVariable) // Ok to access variable
    ...
  }
  let someVariable = 42
  doWork() // Prints 42
}

To understand how this works, we need to delve more deeply into JavaScript scopes. JavaScript has three kinds of scopes:

Variables defined with let or const have block scope. If the variable is declared in the header of a for loop, its scope extends to the end of the closing } of the loop:

for (let i = 0; i < 10; i++) {
  console.log(i)
}
// i no longer in scope here

Variables declared with the archaic var keyword, as well as named functions, have function scope.

Every declaration is hoisted to the top of its scope. That is, the variable or function is known to exist even before its declaration, and space is reserved to hold its value.

You can always access a hoisted variable or function inside a nested function. For example, when the code for the doWork function is generated, the function knows the location of someVariable, even though that variable is declared later.

Now let us look at statements in the same scope, not in a nested function. With let and const declaration, accessing a variable before it is declared throws a ReferenceError. Colloquially, the statements from the beginning of the block until the variable declaration are called the “temporal dead zone” of the variable.

However, if a variable is declared with var, then its value is simply undefined until the variable is initialized.

You should not use var. It has function scope, which is too broad:

function someFunction(arr) {
  // i, element already in scope but undefined
  for (var i = 0; i < 10; i++) {
    var element = arr[i]
    ...
  }
  // i, element still in scope
}

Moreover, var doesn't play well with closures. Consider this example:

for (var i = 0; i < 10; i++) {
  setTimeout(() => console.log(i), 1000)
}

Because the var declaration is hoisted to the enclosing function scope, there is a single variable i. Each of the callbacks () => console.log(i) prints the contents of that single variable. When the loop is exited, i has the value 10. One thousand milliseconds later, the value 10 is printed ten times.

In practice, that is never what is intended. If you capture a loop variable in a closure, you want the value of the current loop iteration. Use let to get that behavior:

for (let i = 0; i < 10; i++) {
  setTimeout(() => console.log(i), 1000)
}

Now, a fresh variable is captured in each loop iteration.

If you assign to a variable that was never declared, the variable is added to the global scope and remains accessible beyond the block in which it is declared.

  
{ // Start of block
  ...
  someVariable = ... // The variable is added to the global scope
  ...
} // End of block, scope does not end here
console.log(someVariable) // Ok—someVariable is still defined here

Technically, the variable is defined as a property of a global object—called window in browsers and global in node.js.

This is so easily done by accident that you want to turn on strict mode—see TODO. Strict mode disallows assigning to undeclared variables.

A named function is also hoisted to the top of its scope—that is, the enclosing function. As a consequence, you can call a function before it is declared:

function outerFunction() {
  innerFunction() // Ok—innerFunction is hoisted  
  function innerFunction() {
    ...
  }
}

If you declare a function inside a block, its scope is hoisted to the enclosing function, but its initialization is hoisted to the enclosing block. Consider this scary example:

if (Math.random() < 0.5) {
  say('Hello')
  function say(greeting) { console.log(greeting + '!') }
}  
say('Goodbye')

The scope of say is not the block of the if statement, but the enclosing function (or the global scope). Therefore, you can call it outside the if statement. But if the if statement was not executed, say has the value undefined, and the call say('Goodbye') throws a TypeError.

When the block is entered, the initialization of say occurs at the beginning of the block. Then the call say('Hello') is successful even though it precedes its declaration, as is the call say('Goodbye').

This behavior is so confusing that strict mode forbids function declarations in nested blocks.

As long as you use strict mode and avoid var declarations, the hoisting behavior is unlikely to result in programming errors. Still, the rules are unintuitive, and I suggest that you structure your code so that you don't depend on them. Put all function declarations at the beginning of their enclosing function, before calling any of them. If those functions access any local variables, put those variable declarations before the function declarations.

In ancient times, JavaScript programmers used “immediately invoked functions” to limit the scope of var declarations and functions:

(function () {
  var someVariable = 42
  function someFunction(...) { ... }
  ...
})() // cite Function is called here—note the ()
// someVariable,
  someFunction no longer in scope

After the anonymous function is called, it is never used again. The sole purpose is to encapsulate the declarations.

This device is no longer necessary. Simply use:

{
  let someVariable = 42
  const someFunction = (...) => { ... }
  ...
}

Strict Mode

As you have again seen in the preceding section, JavaScript is a mix of confusing old features and more conventional new ones. Strict mode outlaws some old features. You should always use strict mode.

To enable strict mode, place the line

'use strict'

as the first non-comment line in your file. (Double quotes instead of single quotes are ok, as is a semicolon.)

If you want to force strict mode in the node.js REPL, start it with

node --use-strict

You can also apply strict mode to individual functions:

function strictInASeaOfSloppy() {
  'use strict'
  ...
}

There is no good reason to use per-function strict mode with modern code. Apply strict mode to the entire file.

Finally, if you use ECMAScript modules (see XREF), strict mode is enabled by default.

Here are the key features of strict mode: