Friday, May 26, 2017

Anonymous Functions and Arrow Functions are Closures

Functions in JavaScript and TypeScript are "first-class citizens;" which means that they can be passed as a parameter, assigned to a variable (and passed as a parameter), and may be dynamically created at runtime. The last stipulation is important: not only do many people consider it a major requirement to be a "first-class citizen," but creating new functions at runtime is how JavaScript and TypeScript function is built.

Dynamically creating functions at runtime is good and bad. It is good because it simplifies the code and can make it cleaner, but it is bad because there is a penalty to pay for creating all these functions.

Function Objects

Consider that when a function is parsed, it needs to be stored somewhere so that it may be executed, over and over again. The parsed function is saved in a function-object, an object with some special properties. First and foremost: the function may be executed.

Because it is stored in an object, a reference to it can be assigned to a variable and passed around as a parameter to another function. This is exactly how callback functions are used; pass them to another function that will execute them when the work is complete. A simple example is to define a function and pass it to another function (setTimeout) that will use it:

1
2
3
4
5
6
7
8
function callMeLater(message: string): void {

    console.log('callMeLater:', message)
}

setTimeout(callMeLater, 2000, 'Simple callback')

// callMeLater: Simple callback

The output is at the end of the example. The setTimeout method is used to trigger the callback two seconds into the future; every example here keeps adding time to that so they can all be run in one script together.

The example uses the strongly typed syntax of TypeScript. The same example works just as easily when the function definition is assigned to a variable. The function is parsed, and a reference to the object is stored in the variable:

1
2
3
4
5
6
7
8
let callMeLater2 = function (message: string): void {

    console.log('callMeLater2:', message)
}

setTimeout(callMeLater2, 4000, 'Variable points to function callback')

// callMeLater2: Variable points to function callback

Even the variable is not needed: simply defining the function inline where it is needed as the parameter is sufficient:

1
2
3
4
setTimeout(function (message: string): void { console.log('inline function:', message) },
    6000, 'inline callback')

// inline function: inline callback

Closures

The really interesting thing is that any variables that are in scope when the function is defined will be in scope when the function is executed, no matter how far in the future that takes place. That call is likely to take place long after the function containing the definition exits, so somehow those variables are preserved. This is the definition of a closure:

1
2
3
4
5
6
7
8
function iPromiseToCallBack(message: string): void {

    setTimeout(function (): void { console.log('closure function:', message) }, 8000)
}

iPromiseToCallBack('This message is bound to the closure')

// closure function: this message is bound to the closure

Only the variables the callback function references will be preserved. If more than one function is defined in the same scope, and uses the same variables, all of the functions will share the same bound variables:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function iPromiseToCallBack2(message: string): void {

    setTimeout(function (): void { console.log('closure function 1:', message);
        message = 'Gotcha!' }, 10000)
    setTimeout(function (): void { console.log('closure function 2:', message) }, 12000)
}

iPromiseToCallBack2('This message is bound to the closure')

// closure function 1: This message is bound to the closure
// closure function 2: gotcha!

It used to be that every time a function was executed procedurally it used the same value for this. That worked because the function call f(x) was actually executed internally as f.call(window, x). That changed in ECMAScript 5 to f.call(undefined, x) and when undefined is used an empty object takes its place during the function call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let iHaveThis = function (value: number): void {

    this.value = !this.value ? 0 + value : this.value + value
    console.log('iHaveThis: this.value =', this.value)
}

setTimeout(iHaveThis, 14000, 1)
setTimeout(iHaveThis, 16000, 2)

// iHaveThis: this.value = 1
// iHaveThis: this.value = 2

About that call method on the function object in the text above: a function can be called with any object as this that you desire. You can always use call to do that, but f you always want to call the function with the same object, then use bind:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let iHaveThis2 = function (value: number): void {

    this.value = !this.value ? 0 + value : this.value + value
    console.log('iHaveThis2: this.value =', this.value)
}

iHaveThis2 = iHaveThis2.bind({})

setTimeout(iHaveThis2, 18000, 1)
setTimeout(iHaveThis2, 20000, 2)

// iHaveThis2: this.value = 1
// iHaveThis2: this.value = 3

Regardless, this is not available in a closure, as proven by the first half of the next example. To allow access to the surrounding this, it is traditional to save it in a variable in scope (I called it self) that will be bound to the closure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function whatAboutThis() {

    this.message = 'What about this?'

    setTimeout(function () { console.log('whatAboutThis inline callback:',
        this.message) }, 0)
}

setTimeout(whatAboutThis, 22000)

// whatAboutThis inline callback: undefined

function whatAboutThis2() {

    let self = this
    this.message = 'What about this?'

    setTimeout(function () { console.log('whatAboutThis2 inline callback:',
        self.message) }, 0)
}

setTimeout(whatAboutThis2, 24000)

// whatAboutThis2 inline callback: What about this?

A better method is to use the bind() method of the function-object, which binds any object as the this value when the function runs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function whatAboutThis3() {

    this.message = 'What about this?'

    setTimeout((function () {
        console.log('whatAboutThis3 inline callback:', this.message) }).bind(this), 0)
}

setTimeout(whatAboutThis3, 26000)

// WhatAboutThis3 inline callback: What about this?

Arrow Functions

An arrow function is a slicker way to define a function dynamically, usually when it needs to be passed as a parameter. ES6 uses the term arrow function, but it really is just an anonymous function or a lambda expression.

The parameters are defined on the left side of the arrow. If there is more than one parameter then they must be defined inside of parentheses. The code is on the right. If there is just one expression then braces are not required, and the value of the expression becomes the return value of the arrow function.

The this value in an arrow function is always bound to the same this as the parent. Most of the time that is what we want anyways, so it just makes it slicker because a variable or bind() call does not need to be used:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function whatAboutThis4() {

    this.message = 'What about this?'

    setTimeout( () => console.log('whatAboutThis4 arrow function:', this.message), 0)
}

setTimeout(whatAboutThis4, 28000)

// whatAboutThis4 arrow function: What about this?


The Penalty

The binding of variables in scope is the huge power of a closure, but the price for that is creating a new function-object for a new copy of the closure each time the definition is encountered. Consider this example where the arrow function is used in a loop. A new function-object is created for each iteration of the loop, and proven by comparing it to the previous function-object!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
let previousArrowFunction: Function
let arrowFunction: Function

for (let i = 1; i <= 5; i++) {

    previousArrowFunction = arrowFunction
    arrowFunction = () =>
        console.log(`new arrow function ${i} === previousArrowFunction ${i - 1}:`,
            previousArrowFunction == arrowFunction)

    setTimeout(arrowFunction, 28000 + (i * 2000))
}

// new arrow function 1 === previousArrowFunction 0: false
// new arrow function 2 === previousArrowFunction 1: false
// new arrow function 3 === previousArrowFunction 2: false
// new arrow function 4 === previousArrowFunction 3: false
// new arrow function 5 === previousArrowFunction 4: false
// new arrow function 6 === previousArrowFunction 5: false

If the creation of the function-objects surpasses the speed of the garbage collector, eventually memory will be exhausted. And even if that does not happen, just the effort to create the function-objects and then garbage collection is slowing down the application.

Consider the penalty every time you are tempted to use an array function as a closure.

No comments:

Post a Comment