JavaScript Functions

And I am a weapon of massive consumption / And it’s not my fault it’s how I programmed to function. — Lily Allen and Greg Kurstin, “The Fear”

Definitions and Calls

Basically, a function is a chunk of code. Generally, you first define a function, with a meaningful name, for example:

function greet() {
  console.log('Hello')
  console.log('There')
}

Defining the function does not run the code! To do that, you call, or invoke, the function:

greet()

“Call” and “invoke” mean exactly the same thing.

Parameters and Arguments

Function definitions can have parameters, for example:

function greet(time, name) {
  console.log(`Good ${time}, ${name}`)
}

Here time and name are the parameters. When you invoke the function, you supply arguments:

greet('morning', 'Alice')

The arguments are 'morning' and 'Alice'. Invoking the function causes Good morning, Alice to be written to the console.

Exercise: Make sure you understand the difference between parameters and arguments. Terminology matters.

Return Values

Functions can be thought of as taking in inputs and returning an output.

function.png

Example:

function average(x, y) {
  return (x + y) / 2
}
average(10, -4)     // 3
average(2.5, 8)     // 5.25

In case you are wondering, if you pass too many arguments, the extra ones are ignored. If you pass too few, the extra paremeters will have the value undefined:

average(3, 2, 1)    // 1.5, because x=3, y=2, the extra argument is ignored
average(8)          // NaN, because (8 + undefined) / 2 is NaN

Lots of Examples

Learn by example.

function triple(x) {
  return x + x + x
}

triple(8)                     // 24
triple("ho ")                 // "ho ho ho "
function even(x) {
  return x % 2 == 0
}

even(-8)                      // true
even(27)                      // false
// Returns whether x exactly divides y
function divides(x, y) {
  return y % x === 0
}

divides(5, 20)                // true
divides(2, 17)                // false
// Returns whether a given year is a leap year, according to the
// rules of the Gregorian calendar. A year is a leap year if it
// is (1) divisible by 4 but not 100, or else (2) is divisible
// by 400.
function isLeapYear(y) {
  return divides(4, y) && !divides(100, y) || divides(400, y)
}

isLeapYear(1963)              // false
isLeapYear(1968)              // true
isLeapYear(2000)              // true
isLeapYear(2100)              // false
isLeapYear(2200)              // false
isLeapYear(2399)              // false
isLeapYear(2400)              // true
isLeapYear(920481910)         // false
isLeapYear(9204810000)        // true
function halfOf(x) {
  return x / 2
}

halfOf(-8)                    // -4
halfOf(27)                    // 13.5
function isLongerThan(s, n) {
  return s.length > n
}

isLongerThan("sos", 2)        // true
isLongerThan("sos", 3)        // false
function salePrice(originalPrice, discountPercent) {
  return originalPrice - (originalPrice * discountPercent / 100.0)
}

salePrice(200, 10)            // 180
salePrice(157.95, 15)         // 134.2575
salePrice(157.95, 100)        // 0
function circleArea(radius) {
  return Math.PI * radius * radius
}

circleArea(3)                 // 28.274333882308138
circleArea(1.492705)          // 6.999996901568007
function sum(a) {
  let result = 0
  for (let x of a) {
    result += x
  }
  return result
}

sum([])                       // 0
sum([8])                      // 8
sum([3, 9, 5, -2])            // 15
function average(a) {
  return sum(a) / a.length
}

average([3, 9, 5, -2])        // 3.75
average([8])                  // 8
average([])                   // NaN, but should it be???
Exercise: Write functions to (a) return the largest of its three inputs, (b) return a random number between its two arguments (inclusive to the lower value and exclusive to the upper value), and (c) return a string just like its input, but with the first character capitalized. For part (c) return the empty string if the empty string was given.

Validating Arguments

Sometime a function cannot do what it is supposed to because something, usually the arguments it is given, just don't make any sense. In that case, you should not return anything; instead, you should fail loudly by throwing an error. Simple example: asking whether the first and last character of string are the same makes no sense for the empty string:

function sameFirstAndLast(s) {
  if (s === "") {
    throw new Error("String cannot be empty")
  }
  return s[0] === s[s.length - 1]
}

sameFirstAndLast("sos")       // true
sameFirstAndLast("hello")     // false
sameFirstAndLast("")          // (throws an error)

Here’s a more realistic example. We’ll write a function to find a total balance of an account with principal p invested for t years and interest rate r, where interest is compounded n times per year. Note that it makes absolutely no sense to compound interst a negative number of times per year, so we should check that up front and error out if someone tries to do that:

function balanceAfter(p, n, r, t) {
  if (n < 0) {
    throw new Error("Cannot compound a negative number of times")
  }
  return p * Math.pow(1 + (r / n), n * t)
}

Let’s see this function in action:

If you did not have error checking, you might get some crazy output:

    balanceAfter(1000, -2, 0.05, 10) ⟶ 1659.234181850974
    balanceAfter(1000, -0.06, 0.05, 10) ⟶ 2930.1560515835217
    balanceAfter(1000, -0.05000002, 0.05, 10) ⟶ 1581143.8050879508
    balanceAfter(1000, -0.05, 0.05, 10) ⟶ Infinity
    balanceAfter(1000, -0.02, 0.05, 10) ⟶ NaN

Does this give you any ideas?

Function Values

Please understand the difference between a function and a function call. It is hugely important.

function dieRoll() {
  return Math.floor(Math.random() * 6) + 1
}

dieRoll()                     // random value, either 1, 2, 3, 4, 5, or 6
dieRoll                       // the function object itself

Make sure you understand:

This is interesting because, since functions are values, they can be assigned to variables, or even passed as arguments to other functions!

function timesFive(x) {
  return x * 5
}

const quintupleIt = timesFive

timesFive(8)                  // 40
quintupleIt(8)                // 40
function twice(f, x) {
  return f(f(x))
}

twice(quintupleIt, 8)         // 200

How did that work?

twice(quintupleIt, 8)
    = quintupleIt(quintupleIt(8))
    = quintupleIt(8 * 5)
    = quintupleIt(40)
    = 40 * 5
    = 200

Now here is something so cool. You don't have to declare a function and use it by its name (though usually that is the right thing to do). If you like, you can write a function expression directly. You can do this in one of two ways, either as an arrow function:

// Five examples of arrow function expressions

(x) => x + x + x

(s, n) => s.length > n

(p, n, r, t) => p * Math.pow(1 + (r / n), n * t)

() => Math.random() * 100

(x, y) => {
  let xSquared = x * x
  let ySquared = y * y
  return Math.sqrt(xSquared + ySquared)
}

or a non-arrow function:

// Five examples of non-arrow function expressions

function (x) { return x + x + x  }

function (s, n) { return s.length > n  }

function (p, n, r, t) { return p * Math.pow(1 + (r / n), n * t)  }

function () { return Math.random() * 100  }

function (x, y) {
  let xSquared = x * x
  let ySquared = y * y
  return Math.sqrt(xSquared + ySquared)
}
Arrow functions and Non-arrow functions sometimes behave differently.

We haven’t seen examples of how they differ yet, but we will. Unless your functions are prperties of objects, or use the famous expression this, you don’t have to worry about the difference.
Functions that you name in a function declaration (like we’ve been doing all along) behave like non-arrow functions.

Now we can pass functions with their expression directly:

function twice(f, x) {
  return f(f(x))
}

twice(x => x**2, 3)        // 81
twice(x => x + "e", "be")  // "beee"
twice(x => x / 2, 20)      // 5

This is not mysterious at all; in fact it is quite natural. Just work things out:

twice(x => x**2, 3)
    = (x => x**2)((x => x**2)(3))
    = (x => x**2)(3**2)
    = (x => x**2)(9)
    = 9**2
    = 81

A function value you use without first making a named function definition is called an anonymous function. They are frequently used when processing arrays:

const a = [3, 2, 7, 8, -2]

a.every(x => x > 5)               // false
a.some(x => x > 5)                // true
a.map(x => x ** 2)                // [9, 4, 49, 64, 4]
a.filter(x => x % 2 === 0)        // [2, 8, -2]
a.reduce((x, y) => x + y)         // 18

// This writes some lines to the console... try it.
a.forEach(x => {
  console.log(`Look it is ${x}`)
})

Details:

NOTE: The function passed to all of these operations can actually take more than one argument; we’ve only showed the simplest cases. For full details, see the documentation for the Array object at MDN.

One thing cool about all this is that you can chain a bunch of array functions together, so expressions look a “dataflow pipeline.”

// Return the sum of all the square roots of the numbers bigger than 100 from an array.
function sumOfSquareRootsOfNumbersBiggerThan100(a) {
  return a
    .filter(x => x > 100)
    .map(x => Math.sqrt(x))
    .reduce((x, y) => x + y)
}

Note that you don’t have to use arrow functions here. If you want you can be a litte more wordy:

// Return the sum of all the square roots of the numbers bigger than 100 from an array.
function sumOfSquareRootsOfNumbersBiggerThan100(a) {
  function biggerThan100(x) { return x > 100  }
  function add(x, y) { return x + y  }
  return a.filter(biggerThan100).map(Math.sqrt).reduce(add)
}

Mutators

If an arugment is an object, your function can, if you choose to, mutate the argument directly! Be somewhat careful with this. It is sometimes super convenient, and exactly what you want, but if not expected it could surprise the caller. Can you find the difference between these two functions?

// Mutates point p so it moves down one unit in the y-direction
function moveDown(p) {
  p.y -= 1
}

// Returns the point that is one unit down from p in the y-direction. Leaves p unchanged.
function downFrom(p) {
  return { x: p.x, y: p.y - 1 }
}

Let’s define and invoke them in the console, and explore their behavior:

> const player = {x: 9, y: 8}
> moveDown(player)
> player
{x: 9, y: 7}
> const q = downFrom(player)
> q
{x: 9, y: 6}
> player
{x: 9, y: 7}

How about another example?

/*
 * Uppercases all the strings in an array. Mutates the argument.
 */
function uppercaseAll(a) {
  for (let i = 0; i < a.length; i += 1) {
    a[i] = a[i].toUpperCase()
  }
}
/*
* Returns a new array that is equivalent to its input array but
* with its elements uppercased. Does not mutate the argument.
*/
function uppercased(a) {
  let result = []
  for (let s of a) {
    result.push(s.toUpperCase())
  }
  return result
}
> let b = ['good', 'morning', 'los angeles'];
> uppercaseAll(b)
undefined
> b
[ 'GOOD', 'MORNING', 'LOS ANGELES' ]

> b = ['good', 'morning', 'los angeles'];
> uppercased(b)
[ 'GOOD', 'MORNING', 'LOS ANGELES' ]
> b
[ 'good', 'morning', 'los angeles' ]
Exercise: Could uppercaseAll be rewritten to use a for-of loop? Why or why not?

Separation of Concerns

Functions provide a great way to modularize code. Modularizing means to make program components focus on one thing and do that one thing well. We shouldn’t be mixing concerns together unnecessarily. For example you should not mix up computations with user interface concerns. For example, if you had a UI application where you entered a number and wanted to see if it is prime while you type, you should not write:

//
// THIS CODE IS BAD. IT MIXES UP UI AND COMPUTATION CONCERNS. ALSO
// IT REPEATS A LOT OF CODE. IT IS JUST MISERABLE IN SO MANY WAYS.
//
inputBox.addEventListener('input', () => {
  const n = Number(inputBox.value)
  if (isNaN(n) || !Number.isInteger(n)) {
    resultArea.textContent = 'Not an integer'
  } else if (n < 2 || n > Number.MAX_SAFE_INTEGER) {
    resultArea.textContent = 'Number too big or too small'
  } else if (n === 2 || n === 3) {
    resultArea.textContent = 'Prime'
  } else if (n % 2 === 0 || n % 3 === 0) {
    resultArea.textContent = 'Not Prime'
  }
  for (let k = 5, w = 2; k * k <= n; k += w, w = 6-w) {
    if (n % k === 0) {
      resultArea.textContent = 'Not Prime'
      return
    }
  }
  resultArea.textContent = 'Prime'
})

Instead you should separate the UI from the computation. There will be more code, but it will be much cleaner.

//
// THIS CODE IS BETTER. LONGER, BUT BETTER. SEPARATION OF CONCERNS FTW.
//
inputBox.addEventListener('input', () => {
  try {
    resultArea.textContent = isPrime(+inputBox.value) ? 'Prime' : 'Not Prime'
  } catch (e) {
    resultArea.innerHTML = `<span class="error">${e}</span>`
  }
})

// So awesome! No input-output here! Just pure, beautiful, prime checking! Reusable!!
function isPrime(n) {
  if (isNaN(n) || !Number.isInteger(n)) {
    throw 'Not an integer'
  } else if (n < 2 || n > Number.MAX_SAFE_INTEGER) {
    throw 'Number too big or too small'
  } else if (n === 2 || n === 3) {
    return true
  } else if (n % 2 === 0 || n % 3 === 0) {
    return false
  }
  for (let k = 5, w = 2; k * k <= n; k += w, w = 6-w) {
    if (n % k === 0) {
      return false
    }
  }
  return true
}

See the Pen Trivial Prime Tester by Ray Toal (@rtoal) on CodePen.

This is really important software engineering!

Most functions should be like this one: they should only return a result or throw an exception. Period. Return and/or throw ONLY! They should not print or alert or log anything!

Prompting, alerting, or rendering results to a web page should be part of the “user interface layer” of a program. Functions should just do their business without worrying about the UI. All communication with a user is separated out into its own portion of the script and not part of these utility functions that carry out business concerns.

In addition to making the code more understandable, having a self-contained, nonalerting prime number function is great: the function is reusable.

Exercise: What do we mean by reusable?

Functions as Objects

Properties of Functions

Since JavaScript has only seven types (Undefined, Null, Boolean, Number, String, Symbol, and Object), functions must be (and are) a kind of object (just like arrays). So they can have properties.

When you create a function, it gets two properties whether you want them or not:

So this declaration

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

results in this:

averagefunction.png

You don’t have to worry about this complexity just this second. But note this: remember that since you add new properties to objects at any time, and because functions are objects, you can invent your own properties:

function average(x, y) {
  average.calls += 1
  return (x + y) / 2
}
average.calls = 0                // Let’s invent a new property
console.log(average(4, 8))       // Prints 6
console.log(average(10.5, 11))   // Prints 10.75
console.log(average(0, 1))       // Prints 0.5
console.log(average.calls)       // Prints 3

Functions as Properties

Because functions are values, they can be values of object properties. We generally exploit this in two ways.

First we can package up related functions:

const Geometry = {
  circleArea: function (radius) {
    return Math.PI * radius * radius
  },
  circleCircumference: function (radius) {
    return 2 * Math.PI * radius
  },
  sphereSurfaceArea: function (radius) {
    return 4 * Math.PI * radius * radius
  },
  boxVolume: function (length, width, depth) {
    return length * width * depth
  }
}

Second, we can place functions inside of objects as a way to cleanly represent an object’s “behavior”, made possible by the this expression:

let circle = {
  radius: 5,
  area: function () {return Math.PI * this.radius * this.radius;},
  circumference: function () {return 2 * Math.PI * this.radius;}
}

console.log(circle.area())             // Prints 78.53981633974483
circle.radius = 1.5                    // Change the circle’s radius
console.log(circle.circumference())    // Prints 9.42477796076938

What is the meaning of this? The expression this has one of several meanings depending on context. When called via an expression such as circle.area(), it refers to the object through which the function is called, which we call the receiver. In that case we say the function is a method.

SUPER IMPORTANT FUNCTION FACT

The value of this refers to the object through which the contained function is called only if the function is defined with the function syntax, NOT the arrow syntax.

Writing code designed primarily around objects containing methods is a major component of object-oriented programming.

Before moving on, you need to learn a syntax shortcut. When the value of an object property is a NON-ARROW function, we can omit the word function, like this:

const Geometry = {
  circleArea(radius) {return Math.PI * radius * radius;},
  circleCircumference(radius) {return 2 * Math.PI * radius;},
  sphereSurfaceArea(radius) {return 4 * Math.PI * radius * radius;},
  boxVolume(length, width, depth) {return length * width * depth;}
}

And also like this:

let circle = {
  radius: 5,
  area() {return Math.PI * this.radius * this.radius;},
  circumference() {return 2 * Math.PI * this.radius;}
}
Always use the function form (or its shorthand) when defining methods inside objects. Never use the => arrow form for methods. The value of this is not bound to the containing object for arrow functions.

Functions that create objects

If you are going to create objects with methods, you want to be careful that you don’t make unnecessary copies of the functions in many similar objects:

// WASTEFUL: EACH CIRCLE GETS ITS OWN REDUNDANT COPIES OF ITS METHODS.
// At least we don't have to repeat them in source code, though.
function Circle(r) {
  return {
    radius: r,
    area() {
      return Math.PI * this.radius * this.radius
    },
    circumference() {
      return 2 * Math.PI * this.radius
    }
  }
}

So what’s the problem? Well, execute let c1 = Circle(2); let c2 = Circle(10); and look what you get:

toomanyfunctions.png

We have extra function objects laying around, and they take up space! So how do we fix it? Answer: Prototypes, of course! Each circle needs its own radius, but we can put the area and circle functions in a shared prototype object. There are many ways to set up code to do this:

  1. The circle creation function and the prototype can be stored in separate global variables (YECCH!)
  2. The prototype can be a property of the creator function
  3. The creator function can be a property of the prototype (Weirdish)
  4. The creator function and the prototype can both be properties of some other object (But what then?)

Here is a way to implement option #2 (easy to do because every function already has a prototype property!):

/*
 * A circle datatype. Synopsis:
 *
 *   let c = Circle(5)
 *   c.radius => 5
 *   c.area() => 25&pi
 *   c.circumference() => 10&pi
 */
function Circle(r) {
  let circle = Object.create(Circle.prototype)
  circle.radius = r
  return circle
}
Circle.prototype.area = function () {
  return Math.PI * this.radius * this.radius
}
Circle.prototype.circumference = function () {
  return 2 * Math.PI * this.radius
}

Note how we used the long-form of the function value, and NOT THE ARROW FORM, because we wanted to access this.

Now when we create circles like so:

let c1 = Circle(2)      // Creates a circle with radius 2.
let c2 = Circle(10)     // Creates a circle with radius 10.

we get this:

circleswithprototype.png

Much better. There is only one copy of each method, even if we have millions of circles.

Exercise: Write code to implement construction schemes numbered #3 and #4 in the list above.

new

JavaScript has an operator called new, that you put before a function call to build exactly the same situation above but with fewer lines of code. Here is how we do it with operator new:

/*
 * A circle datatype. Synopsis:
 *
 *   let c = new Circle(5)
 *   c.radius => 5
 *   c.area() => 25&pi
 *   c.circumference() => 10&pi
 */
function Circle(r) {
  this.radius = r
}
Circle.prototype.area = function () {
  return Math.PI * this.radius * this.radius
}
Circle.prototype.circumference = function () {
  return 2 * Math.PI * this.radius
}

// Must call it with new to make it work:
let c1 = new Circle(2)       // Creates a circle with radius 2.
let c2 = new Circle(10)      // Creates a circle with radius 10.

So what is the meaning of new? If you prefix a function call with new then the following happens:

The use of functions as “constructors” with methods in the prototype is so common, JavaScript has some cool syntactic sugar:

/*
 * A circle datatype. Synopsis:
 *
 *   let c = new Circle(5)
 *   c.radius => 5
 *   c.area() => 25π
 *   c.circumference() => 10π
 */
class Circle {
  constructor(r) {
    this.radius = r
  }
  area() {
    return Math.PI * this.radius * this.radius
  }
  circumference() {
    return 2 * Math.PI * this.radius
  }
}

// Must call it with new to make it work:
let c1 = new Circle(2)   // Creates a circle with radius 2.
let c2 = new Circle(10)  // Creates a circle with radius 10.

Note: The class construct generates exactly the same code as the previous example. Even though you said “class Circle,” you made a function called Circle, and the area and circumference methods got placed in Circle.prototype.

Context

The special keyword this has one of four different meanings.

  1. If used in a global scope, it refers to the global object
  2. If used in a function called via apply, call, bind (and a few other operations), a custom value for this can be used.
  3. If used in a function invoked with the new operator, it refers to the object being created. (We saw this already.)
  4. If used in a function called without new, it refers to the object the function was called through; this includes the global object—because all global variables, including global functions, are properties of some unnamed global object.

Using this globally

Here’s an example when used in the global scope (this is rare):

this.x = 2              // Set the x property of the global object.
console.log(x)          // Prints 2. (Global vars are properties of global object)
function f(x) {
  this.y = x + 1
}
f(2)                    // f is called as a global function.
console.log(y)          // Prints 3.

It is more common to accidentally screw up and whack global variables by misusing constructors:

function Point(x, y) {
  this.x = x
  this.y = y
}
let p = new Point(4, -5)   // New point created.
let q = Point(3, 8)        // DANGER! DANGER! Sets global x and y!

apply, call, and bind

The function methods apply, call, and bind allow you to specifically define the object you want to use as the value of this.

function f(a, b, c) { this.x += a + b + c  }
let a = { x: 1, y: 2 }
f.apply(a, [10, 20, 5])     // Calls f(10, 20, 5), using 'a' as this.
f.call(a, 3, 4, 15)         // Calls f(3, 4, 15), using 'a' as this.
f.bind(a)(10, 100, 30)      // Calls f(10, 100, 30) using 'a' as this.
console.log(a.x)            // Logs 198.

These methods allow you to hijack constructors:

function Point(x, y) {
  this.x = x
  this.y = y
}
let p = { z: 3 }
Point.apply(p, [2, 9])  // Now p is {x: 2, y: 9, z: 3}
Point.call(p, 10, 4)    // Now p is {x: 10, y: 4, z: 3}
Point.bind(p)(3, 5)     // Now p is {x: 3, y: 5, z: 3}

More About Parameters and Arguments

So until now, we just seen relatively simple functions. You declare them with n parameters and pass them n arguments. But there’s much more to be aware of!

Too many or too few arguments

What if you pass too many or too few arguments? Answers: Unmatched parameters are undefined. Extra arguments are ignored.

function f(x, y) {
  return [x, y]
}

f()          // [ undefined, undefined ]
f(1)         // [ 1, undefined ]
f(1, 2)      // [ 1, 2 ]
f(1, 2, 3)   // [ 1, 2 ]

Defaults on parameters

If you want, you can specify default parameter values in case values are not passed (or someone passes undefined):

function f(x = 5, y, z = 2) {
  return [x, y, z]
}

f()                 // [5, undefined, 2]
f(8)                // [8, undefined, 2]
f(7, 9)             // [7, 9, 2]
f(2, 1, 0)          // [2, 1, 0]
f(3, 8, undefined)  // [3, 8, 2]
f(undefined, 1)     // [5, 1, 2]
Exercise: What do you make the claim that “Parameters for which you do not explicity specify a default, acutally have a default value of undefined?”

Rest Parameters

Ever wonder how some functions, like Math.max, can take any number of arguments whatsoever? Even a million arguments but it still works? Here’s how. You can make the last parameter be a rest parameter, which will ”roll up” or “pack” all of the final arguments into an array. Like this:

function f(x, ...y) {
  return y
}

f(8)                // []
f(5, 9)             // [9]
f(2, 1, 0)          // [1, 0]
f(3, 8, 8, 3)       // [8, 8, 3]

You can use destructuring for parameters

When calling functions, you are really assigning arguments to parameters, so destructuring works as you’d expect:

function f({x, y}, [a, b]) {
  return x + " " + y + " " + a + " " + b
  return `${x} ${y} ${a} ${b}`
}

f({y: 10, z: 5}, [1, 2, 3])   // undefined 10 1 2
f({x: 0, y: 8}, [1])          // 0 8 1 undefined
let z = {x: 3, y: 5}
f(z, [9, 2])                  // 3 5 9 2

Calling functions with spreads

Spreads work as you’d expect:

function f(first, second, third) {
  return `${first} ${second} ${third}`
}

let dogs = ['spike', 'sparky', 'spot']
f(dogs)       // 'spike,sparky,spot undefined undefined'
f(...dogs)    // 'spike sparky spot'

Functions Returning Functions

JavaScript does functions better than almost any other language. The real power of JavaScript is often seen when you have functions inside of functions, and especially when functions return functions.

The classic example from mathematics of an operation that creates a new function is composition:

function compose(f, g) {
  return x => f(g(x))
}

We can use it like this:

const square = x => x * x
const addSeven = x => x + 7
const squareThenAddSeven = compose(addSeven, square)

Let’s evaluate:

squareThenAddSeven(5)
    = compose(addSeven, square)(5)
    = addSeven(square(5))
    = addSeven(5 * 5)
    = addSeven(25)
    = 25 + 7
    = 32
If you can follow the above example, where we continually simplify an expression by replacing function calls with their results, feel free to use this method of working out complex-looking function-heavy expressions. It gives you a pretty good insight into what is going on.
Exercise: Try out the squareThenAddSeven example in a shell. Then create and test some composition examples of your own.

Here’s another one, which is pretty simple:

function add(x) {
  return y => x + y
}

How does this work? Example:

add(8)(5)
    = (y => 8 + y)(5)
    = 8 + 5
    = 13

So add(8) is the function that adds 8 to its argument. Note that add(x)(y) is an expression that adds x and y, as if add was some kind of two-argument function. But it’s not: it’s a one argument function that returns a function. It only looks like a two-argument function. Functions like this are called curried functions. Because of Haskell Curry.

We could also use a curried function like this:

const addTenTo = add(10)

then

addTenTo(5)
    = (y => 10 + y)(5)
    = 10 + 5
    = 15
And by the way, we could have written the addfunction like this:
    const add = x => y => x + y

Ooooh, ever seen the lambda calculus? That’s just the JavaScript way to say $\lambda x. \lambda y. x + y$, isn’t it?

In practice we can manufacture functions for all kinds of things:

function delimitWith(prefix, suffix) {
  return s => `${prefix}${s}${suffix}`
}
const withParentheses = delimitWith("(", ")")
const withBrackets = delimitWith("[", "]")
const withBraces = delimitWith("{", "}")

Now, withParentheses, withBrackets, and withBraces are functions you can use.

Exercise: What is the result of calling withBrackets("sic")?

But now here’s an amazing use of returning functions. We can make a function so that every time we call it, it returns a different value, based on the last time we called it. For example, here’s a way to write a function such that the first time we call it we get 0, the next time 1, then 4, then 9, then 16, and so on. Each call returns the next square.

const nextSquare = (() =>{
  let n = -1
  return () =>{ n += 1; return n * n  }
})()

nextSquare()                  // 0
nextSquare()                  // 1
nextSquare()                  // 4
nextSquare()                  // 9
nextSquare()                  // 16

The value assigned to nextSquare is the result of calling an anonymous function. This function returns an (inner) function which is then assigned to nextSquare. This little function has access to the variable n. No one else can see or change n. This function, which has access to the variables closed around it but not accessible to any place else in the code, is called a closure.

Exercise: Try this out.

Hiding data within (usually anonymous) functions is super common. Remember the BMI script from earier in the semester? It had a bunch of global variables. Look how we can hide them:

(function () {
  let poundsBox = document.getElementById("weight")
  let inchesBox = document.getElementById("height")
  let bmiSpan = document.getElementById("bmi")

  const KILOS_PER_POUND = 0.453592
  const METERS_PER_INCH = 0.0254

  function computeBMI() {
    let kilos = +poundsBox.value * KILOS_PER_POUND
    let meters = +inchesBox.value * METERS_PER_INCH
    bmiSpan.innerHTML = kilos / (meters * meters)
  }

  poundsBox.addEventListener('input', computeBMI)
  inchesBox.addEventListener('input', computeBMI)
}())

This code is a single statement: a function call. The function it calls set up the event handlers. The handlers manipulate private data, hidden from any other scripts the browser might have loaded. The code is unobtrusive.

It’s called something else, too. Any time you have an expression that is a function call of an anonymous function expression, we call this an immediately invoked function expression, or an IIFE.

Some Technical Details

Function parameters are distinct variables

When you call a function, any parameters are new variables distinct from any other variable. Completely new, completely distinct. Distinct from the arguments passed in. Distinct from global variables of the same name. Distinct from the same parameter in different invocations. Totally new, totally distinct from all other variables.

So if you change a parameter’s value, that will not affect the argument:

let x = 5
function f(y) { y = 10; console.log(y)  }
f(x)
console.log(x)
// Try it... This script logs 10 then 5. Changing y did NOT change x.

Nor does changing a parameter’s value change globals of the same name:

let x = 5
function f(x) { x = 10; console.log(x)  }
f(100)
console.log(x)
// Try it... This script logs 10 then 5. Changing parameter x did NOT change global x.

Nor is a change to a parameter remembered in future calls:

function f(x) { console.log(x); x = 10  }
f(100)
f()
// Try it... This script logs 100 then undefined.
Exercise: Wait, what? Changing the parameter doesn’t change the argument? How is this statement consistent with the examples in which we wrote mutators?

Scope

A variable’s scope is the region of the source code where it is visible. In JavaScript, a variable has either global scope, function scope, or block scope.

From these rules you can infer:

Now here are two other language design choices made by Eich. He didn’t have to do things this way, but he did, and you absolutely positively have to know these things:

To really understand this stuff, you have to work through examples. These will become second nature to you at some point, but you have to pay the price in effortful study:

Example 1. Globals are visible within inner scopes.

let message = 'Hello'       // variable message visible from here to end of script
function greet() {
  console.log(message)      // Refers to (global) declaration on line 1
}
greet()                     // Prints "Hello"

Example 2. Locals are not visible in outer scopes.

function meaning() => {
  let answer = 42           // Local variable, in scope until the end of the function
}                           // End of function, so answer is now out of scope
meaning()
console.log(answer)         // Throws ReferenceError, can’t see answer out here

Example 3. Locals hide globals of the same name.

let message = 'Hello'
function greet() {
  let message = 'Hola'      // A NEW local variable, DIFFERENT than the outer one
  console.log(message)      // Prints "Hola": inner one shadows (hides) outer one
}
greet()
console.log(message)        // Prints "Hello": global was just hidden, not changed.

Example 4. Forgetting let, const, or var trashes a global.

let message = 'Hello'
function greet(); {
  message = "Hola"          // O NOES! This is an assignment, NOT a declaration
  console.log(message)      // We assigned to the global, so logs "Hola"
}
greet()
console.log(message)        // Prints "Hola": global got changed! Disaster!

Example 5. The scope of local variables declared with let or const is the whole function, but JavaScript throws an error if you try to use a variable before its declaration (the temporal dead zone).

let message = 'Hello'
function greet() {
  console.log(message)      // Throws a ReferenceError (we’re in the TDZ)
  let message = 'Hola'      // Because the declaration is here
  console.log(message)
}
greet()                     // The error will be propagated out of here
console.log(message)        // Execution never gets here

Example 6. Local variables declared with var are hoisted.

let message = 'Hello'
function greet() {
  console.log(message)      // Prints undefined, local variable not yet initialized
  var message = 'Hola'      // New local variable defined here, but scope is whole body
  console.log(message)      // Prints "Hola" now (as expected)
}
greet()
console.log(message)        // Prints "Hello" - global was just hidden, not changed.

Hoisting? What does that mean? It means that variable declarations within a function body work as if they are invisibly pulled up to the top of the body. In other words, this:

function f() {
  console.log(x)
  var x = 1
  console.log(x)
}

works exactly the same as this:

function f() {
  var x
  console.log(x)
  x = 1
  console.log(x)
}

Hoisting is pretty weird, so do yourself a favor and always use let or const, and never use var. (Technically, let and const sort of do hoisting, but only hoisting in terms of shadowing outer variables of the same name; you can’t do anything in the TDZ.)

Example 7. The scope of local variables declared with let or const is its innermost block:

const x = 3
if (true) {
  let x = 5            // New variable, local to this block
  console.log(x)       // Prints 5
}
console.log(x)         // Prints 3
Exercise: Find out from the official language specification, what exactly constitutes a block and what does not.

Example 8. With let or const, you get a fresh variable each time through the loop. What does this mean? To find out, let’s try to create an array of functions $a = [f_0, f_1, ..., f_9]$ such that calling $f_i$ returns $i$, e.g. a[5]() === 5. This will fail miserably:

// HORRIBLE NEWBIE MISTAKE
const a = []
for (var i = 0; i < 10; i += 1) {
  a[i] = () => i
}

a[3]()         // 10
a[7]()         // 10

Wha?! Actually this makes sense! The scope of i is very large, going beyond the for loop. We are filling an array with functions that all return the same i. None of these functions are called until the loop is finished, and by this time, i is 10.

With let, there is no such problem:

const a = []
for (let i = 0; i < 10; i += 1) {
  a[i] = () => i
}

a[3]()         // 3
a[7]()         // 7