Discover Functional JavaScript - An Overview of Functional and Object Oriented Programming in JavaScript (Functional Programming With JavaScript and React Book 1)
Discover Functional JavaScript - An Overview of Functional and Object Oriented Programming in JavaScript (Functional Programming With JavaScript and React Book 1)
Cristian Salcescu
JavaScript is popular as being the language of the browser. At the same time,
it is a powerful and expressive programming language.
Many code examples from this book, but not all, are available at
https://github.com/cristi-salcescu/discover-functional-javascript. I encourage
you to write the code yourself. Writing is another form of learning, and in
most cases, the code you understand best is the code you wrote.
Feedback
Primitives
Number, boolean, string, undefined, and null are primitives.
Number
At the moment there is only one number type in JavaScript, the 64-bit binary
floating-point. Decimal numbers’ arithmetic is inexact. As you may already
know, 0.1 + 0.2 does not make 0.3 . But with integers, the arithmetic is
exact, so 1 + 2 === 3.
String
A string stores a series of Unicode characters. Text can be inside double
quotes "" or single quotes ''.
"text".substring(1,3); //"ex"
"text".indexOf('x'); //2
"text".concat(" end"); //"text end"
Strings, like all primitives, are immutable. For example concat() doesn’t
modify the existing string but creates a new one.
Boolean
The language has truthy and falsy values. false, null, undefined, ''(empty
string), 0 and NaN are falsy. All other values are truthy.
var declares and optionally initializes a variable. Variables declared with var
have function scope. They are treated as declared at the top of the function.
This is called variable hoisting.
The let and const declarations have block scope. A variable declared with
const cannot be reassigned.
Objects
An object is a dynamic collection of properties.
The property key is a unique string. The property value can be a primitive, an
object, or a function.
There are two ways for accessing properties: the dot notation and the bracket
notation. Object’s properties can be read, added, edited or removed.
let obj = {};
obj.message = "read me"; //add
obj.message = "enjoy"; //edit
delete obj.message; //remove
Primitives vs objects
Primitives, except null and undefined, are treated like objects in the sense
they have methods, but they are not objects.
Numbers, strings, and booleans have equivalent wrapper objects. These are
built with the Number, String and Boolean functions.
Array
Arrays are indexed collections of values. Each value is an element. Elements
are ordered and accessed by their index number.
A simple array like let arr = ['A', 'B', 'C'] is emulated using an object
like the one below:
{
'0': 'A',
'1': 'B',
'2': 'C'
}
Removing values from an array with delete will leave holes. splice() can
be used to avoid the problem, but it can be slow.
let arr = ['A', 'B', 'C'];
delete arr[1];
console.log(arr); //['A', empty, 'C']
console.log(arr.length); //3
JavaScript’s arrays don’t throw “index out of range” exceptions. If the index
is not available, they will return undefined.
Functions
Functions are independent units of behavior. There are three ways to define a
function:
Arrow function
Function invocation
//as methods
theObject.doSomething(arguments);
//as constructors
new Constructor(arguments)
//with apply() or call()
doSomething.apply(theObject, [arguments]);
doSomething.call(theObject, arguments);
Functions can be invoked with more or fewer arguments than declared in the
definition. The extra arguments will be ignored, and the missing arguments
will be set to undefined.
this
Methods are functions that are stored in objects. In order for a function to
know which object to work on, this is used. this represents the function’s
context.
theObject.getName(); //"name"
Functions are objects, as a result, functions can have methods. The apply()
and call() methods execute the function with a given this. call() provides
the arguments individually, while apply() accepts an array with all
arguments.
let otherObject = {
name: "otherName"
}
function getName(){
return this.name;
}
getName.apply(otherObject); //"otherName"
getName.call(otherObject); //"otherName"
There is no point to use this when a function is invoked with the function
form. In that case, this is undefined or is the window object, depending if
strict mode is enabled or not. Consider the next example:
"use strict";
function getName(){
return this.name;
}
getName();
//Cannot read property 'name' of undefined
The value of this depends on how the function is invoked, not where the
function is defined. This is, of course, a source of confusion.
arguments
return
return stops the function execution and returns the result. When it returns no
value or there is no return at all, the function still returns undefined.
The automatic semi-colon insertion may create problems when using return.
The following function will not return an empty object, but rather undefined.
function getObject(){
return
{
}
}
getObject();
Dynamic typing
JavaScript has dynamic typing. Values have types, variables do not. Types
can change at run time.
function log(value){
console.log(value);
}
log(1);
log("text");
log({message: "text"});
let s = "text";
typeof(s); //"string"
Exceptions
JavaScript has an exception handling mechanism. It works like you may
expect, by wrapping the code using the try/catch statement. The statement
has a single catch block that handles all exceptions.
You can throw values of any type, but it is common to throw error objects.
Final thoughts
JavaScript allows accessing properties on primitives.
let
let declares and optionally initializes a variable in the current scope. The
current scope can be either a module, a function, or a block.
The scope defines the lifetime and visibility of a variable. Variables are not
visible outside the scope in which they are declared.
Consider the next code that emphasizes the let block scope:
let x = 1;
{
let x = 2;
}
console.log(x); //1
const
const declares a variable that cannot be reassigned. It becomes a constant
only when the assigned value is immutable.
Modules
Before modules, a variable declared outside any function was a global
variable. With modules, a variable declared outside any function is hidden
and not available to other modules unless it is explicitly exported.
Let’s first look at default exports and imports. Exporting makes a function or
object available to other modules.
//module "./TodoStore.js"
export default function TodoStore(){}
//module "./UserStore.js"
export default function UserStore(){}
When using the default export, the import name can be different than the
export name. In the next example, the flow function exported as default is
used with the name pipe.
import pipe from "lodash/flow";
There can be one default export and many named exports per module file.
Spread/Rest
The ... operator can be the spread operator or the rest parameter, depending
on where it is used.
console.log(arr);
//["a", "b", "c", 1, 2, 3]
The rest parameter gathers the remaining arguments when the number of
arguments exceeds the number of named parameters. The rest parameter is
the last parameter in the function and is an array.
function process(x,y, ...arr){
console.log(arr)
}
process(1, 2, 3, 4, 5);
//[3, 4, 5]
function processArray(...arr){
console.log(arr)
}
processArray(1, 2, 3, 4, 5);
//[1, 2, 3, 4, 5]
Cloning
The spread operator makes cloning of objects and arrays simpler and more
expressive.
const book = { title: "JavaScript: The Good Parts" };
const bookGateway = {
getBooks: function() {},
editBook: function() {}
};
Concatenating arrays
console.log(arr);
//[1, 2, 3, 4, 5, 6]
arguments
Property short-hands
Consider the next code:
function BookGateway(){
function getBooks() {}
function editBook() {}
return {
getBooks: getBooks,
editBook: editBook
}
}
With property short-hands, when the property name and the name of the
variable used as value are the same, we can just write the key once.
function BookGateway(){
function getBooks() {}
function editBook() {}
return {
getBooks,
editBook
}
}
const stores = {
todoStore,
userStore
};
Destructuring assignment
The destructuring assignment syntax extracts values from objects or arrays
into variables.
Default parameters
Functions can have default parameters. Look at the next example:
function log(message, mode = "Info"){
console.log(mode + ": " + message);
}
log("An info");
//Info: An info
Class
Class is sugar syntax for creating objects with a custom prototype. It has a
better syntax than the previous one, the function constructor. Take a look at
the next example:
class Service {
doSomething(){
console.log("do-something");
}
}
Inheritance
Arrow functions
Arrow functions can create anonymous functions on the fly. They can be
used to create small callbacks, with a shorter syntax.
Consider the next code selecting only the title from a collection of to-dos:
const todos = [
{id: 1, title: "learn", type:"NC", completed: false},
{id: 2, title: "connect", type:"RC", completed: true}
];
this
Arrow functions don’t have their own this and arguments. Arrow function
can’t be used as constructor function.
Using bind(), apply() or call() to set the value of this on an arrow
function has no effect. Consider the code below:
this.message = "help";
logMessage(); //"help"
logMessage.call({message : "identify"}); //"help"
this and arguments are treated like any other free variables used in the
function definition.
Proper tail-calls
A recursive function is tail recursive when the recursive call is the last
thing the function does.
console.log(n);
print(1, 3);
//1
//2
//3
Promises
A promise is an object that represents a possible future result of an
asynchronous operation.
Creating a promise
function logMessage(){
console.log("process ended");
}
delay(5000)
.then(logMessage);
The Promise constructor takes a single argument, a function called “the
executor function”. This function is executed immediately when the promise
is created.
Both functions can be called with one argument. The promise is resolved or
rejected only once. Any future invocations of these methods have no effect.
Using a promise
function doSomething(){ }
function handleError(error){ }
fetchUsers()
.then(doSomething)
.catch(handleError);
then() can handle both success and failure, but it is clearer to handle success
with it and handle rejection with catch().
Final thoughts
let and const declare and initialize variables.
The spread operator, rest parameter, and property shorthand make things
easier to express.
First-class functions
JavaScript has first-class functions. It means that functions can be used as any
other value. Numbers and functions are both values, so we can use a function
in a similar way we use a number:
//store in variable
const doSomething = function (){ };
//store in property
const obj = {
doSomething : function(){ }
}
//pass as argument
process(doSomething);
//is equivalent to
let doSomething = function() {}
Higher-order functions
A higher-order function is a function that takes another function as
argument, returns a function or does both.
function process() {
console.log("process");
}
doWithLoading(process);
//start loading
//process
//end loading
filter()
filter() selects values from a list using a predicate function that decides
what values to keep.
const numbers = [1,2,3,4,5,6];
function isEven(number){
return number % 2 === 0;
}
A predicate function is a function that takes one argument and returns true
or false. It tests if the input value satisfies the condition.
map()
map() transforms a list of values to another list of values using a mapping
function.
function toCodeOfLength(number){
return "a".repeat(number);
}
reduce()
reduce() reduces a list of values to one value using a reducer function.
The reducer function takes the accumulator value and the current value and
computes the new accumulator value. The reduce() method executes the
reducer function for each element in the array.
function addNumber(total, number){
return total + number;
}
There is also a reduceRight() method that starts from the last element in the
list.
function multiply(product, value){
return product * value;
}
As you note, when multiplying the initial value for the accumulator is 1.
sort()
sort() sorts the elements of the array using a comparison function. It is an
impure method, it modifies the input array.
The comparison function takes two arguments, a and b for example, and
returns negative, positive or zero. When result is negative then a comes
before b. When it is positive a comes after b. When it is zero, a and b are
not sorted in regards to each other.
function asc(a, b){
return a - b;
}
numbers.sort(asc);
//[1, 2, 3, 4, 5, 6]
numbers.sort(desc);
//[6, 5, 4, 3, 2, 1]
asc()and desc() are comparison functions. Given the same input, the
comparison function should always return the same output.
Extended toolbox
find() returns the first value that satisfies the predicate function. It stops
processing when it finds the first value.
findIndex() returns the index of the first value that satisfies the predicate
function.
some() checks if at least one value in the list passes the test implemented
by the predicate function. It stops processing when the predicate returns
true.
every() checks if all values in the list pass the test implemented by the
predicate function. It stops processing when the predicate returns false.
filter(), map(), reduce() make the basic toolbox for working with
collections in a functional style.
Chapter 4: Closures
Closure is an inner function that has access to the outer scope, even after
the outer scope container has executed.
The scope defines the lifetime and visibility of a variable. The outer scope
container can be a function, a block, or a module.
Nested functions
Functions can be nested inside other functions. Consider the next code:
(function autorun(){
let x = 1;
function log(){
console.log(x);
}
log();
})();
log() is a nested function. log() accesses variable x from its outer scope.
The log() function is a closure. The outer scope container is the outer
function.
The inner function accessing variables from the outer function is a closure.
Lexical scope
Lexical scope is the ability of the inner function to access the outer scope in
which it is defined. Consider the next code:
(function autorun(){
let x = 1;
function log(){
console.log(x);
};
function run(fn){
let x = 100;
fn();
}
run(log); //1
})();
The log() function is a closure. It refers to the x variable from its parent
function autorun(). log() doesn’t use the x variable from the run()
function.
The closure function has access to the scope in which it is created, not the
scope in which it is executed.
The local function scope of autorun() is the lexical scope of the log()
function.
Timer
(function autorun(){
let x = 1;
setTimeout(function log(){
console.log(x);
}, 10000);
})();
If setInterval() had been used, the x variable would have lived forever or
until clearInterval() was called.
Event
(function autorun(){
let x = 1;
$("#btn").click(function log(){
console.log(x);
});
})();
When the variable x is used in an event handler, it lives forever or until the
handler is removed.
Network call
(function autorun(){
let x = 1;
fetch("/todos").then(function log(){
console.log(x);
});
})();
The x variable lives until the response gets back from the Web API and the
callback is executed.
In all these cases, log() is a closure and survives the invocation of the parent
function.
The variable lifetime depends on the closure lifetime. Variables live as long
as the closure referring them lives. If a variable is shared by many closures,
all closures should be garbage collected before the variable is garbage
collected.
Closures in loop
Closures store references to outer variables, they don’t copy the actual values.
Check the next example:
function initEvents(){
for(var i=1; i<=3; i++){
$("#btn"+i).click(function log(){
console.log(i); //4
});
}
}
initEvents();
In this example, three closures were created, all sharing the same i variable.
All three closures are event handlers. Because i changes during the loop, all
logs will display the same value, the last one.
The simplest way to fix this issue is to use the for statement with a let
declaration inside. It will create a variable local to the block scope for each
iteration.
function initEvents(){
for(let i=1; i<=3; i++){
$("#btn"+i).click(function log(){
console.log(i); //1 2 3
});
}
}
initEvents();
When the variable declaration is done outside the for statement, even if it is
done with let, all closures will use the same variable. All logs will display
the same value, the last one.
function initEvents(){
let i;
for(i=1; i<=3; i++){
$("#btn"+i).click(function log(){
console.log(i); //4
});
}
}
Generators
A generator is a function that returns the next value from the sequence
each time it is called.
I think the ES6 generator is an unnecessary feature that makes code more
complicated.
The ES6 generator creates an object that has the next() method. The next()
method creates an object that has the value property. ES6 generators promote
the use of loops and in functional programming, we want to get away from
loops.
function increment(){
state += 1;
return state;
}
function decrement(){
state -= 1;
return state;
}
return {
increment,
decrement
}
})();
counter.increment(); //1
counter.increment(); //2
counter.decrement(); //1
This practice is known as the Revealing Module pattern.
function increment() {
state += 1;
return state;
}
function decrement() {
state -= 1;
return state;
}
export default {
increment,
decrement
}
//module "./index.js"
import counter from "./counter";
counter.increment(); //1
counter.increment(); //2
counter.decrement(); //1
Scope objects
When a function is executed a new private scope object is created. The scope
object contains all the arguments and local variables. It also contains a
reference to the scope object of the outer scope where it was created.
Scope objects are allocated like any other object. Scope objects are not
automatically cleaned up after the function execution. They are garbage
collected like ordinary objects when there are no more references to them.
Garbage collection
The private state of a closure becomes eligible for garbage collection after the
closure itself is garbage collected. To make this possible, the closure should
no longer have a reference to it.
Then, two functions working with the closure function are defined:
function clearAllObjects(){
if(add){
add = null;
}
}
$("#add").click(addALotOfObjects);
$("#clear").click(clearAllObjects);
Clicking “Add” will add 10,000 to-dos to the closure’s private state.
Clicking “Clear” will set the closure reference to null. After doing that, the
private state is garbage collected.
Final thoughts
Closures are inner functions having access to variables from the outer scopes.
First-class functions and closures open the way for functional programming
in JavaScript.
Chapter 5: Function decorators
A function decorator is a higher-order function that takes one function as
an argument and returns another function, and the returned function is a
variation of the argument function.— Reginald Braithwaite, author of
JavaScript Allongé
In order to see the concept in practice, we will write some common function
decorators found in libraries like lodash.js, underscore.js, or ramda.js.
once()
once(fn) creates a function that executes the original function only one time.
It is useful when we want to execute the function once, no matter how many
times is called from different places.
function once(fn){
let returnValue;
let canRun = true;
return function(...args){
if(canRun) {
returnValue = fn(...args);
canRun = false;
}
return returnValue;
}
}
function process(){
console.log("process");
}
after()
after(fn, count) creates a version of the function that executes only after a
number of calls. For example, it can be used to run the function after all
asynchronous operations have finished.
function after(fn, startCount){
let count = 0;
return function(...args){
count = count + 1;
if (count >= startCount) {
return fn(...args);
}
}
}
function logResult() {
console.log("finish");
}
//"1st"
//"2nd"
//"finish"
throttle()
throttle(fn, wait) creates a version of the function that, when invoked
repeatedly, will call the original function once per every wait milliseconds. It
is useful for limiting events that occur faster.
function throttle(fn, interval) {
let lastTime;
return function throttled(...args) {
if(!lastTime || (Date.now() - lastTime >= interval)) {
fn(...args);
lastTime = Date.now();
}
}
}
In this example, moving the mouse will generate a lot of mousemove events,
but the original function will be called once per second.
debounce()
debounce(fn, wait) creates a version of the function that, when invoked
repeatedly, will call the original function after wait milliseconds since the
last invocation. It is useful for running a function only after the event has
stopped arriving.
function debounce(fn, interval) {
let timer;
return function debounced(...args) {
clearTimeout(timer);
timer = setTimeout(function(){
fn(...args);
}, interval);
}
}
memoize()
Memoization is a technique for improving execution speed by saving the
previous results and use them to avoid computation. Memoization is different
than caching, in the sense that it works only with deterministic functions.
return map[x];
}
}
console.log(factorial(3)); //6;
//n=3
//n=2
//n=1
console.log(factorial(5)); //120;
//n=5
//n=4
unary()
unary(fn) creates a version of the function that takes only one argument. It is
usually used to fix problems when the function is called with more arguments
than necessary.
function unary(fn){
return function(first){
return fn(first);
}
}
numbers.forEach(console.log);
//1 0 (6) [1, 2, 3, 4, 5, 6]
//2 1 (6) [1, 2, 3, 4, 5, 6]
//...
numbers.map(parseInt);
//[1, NaN, NaN, NaN, NaN, NaN]
numbers.map(unary(parseInt));
//[1, 2, 3, 4, 5, 6]
binary()
binary(fn) creates a version of the function that takes only two arguments. It
can be used to fix problems when the function is called with more than two
arguments.
function binary(fn){
return function(a, b){
return fn(a, b);
}
}
In the next example, Math.max() is called with more arguments than we may
expect. Because one of the arguments, the forth, cannot be converted to a
number the result is NaN.
const numbers = [1, 2, 3];
numbers.reduce(Math.max);
//NaN
To make sure Math.max() is called with two arguments we can use the
binary() decorator:
numbers.reduce(binary(Math.min));
//1
The decorator should return a function using the function keyword, not
the arrow syntax.
The original function should be called with fn.apply(this, args) or
fn.call(this, ...args).
Final thoughts
Function decorators are a powerful tool for creating variations of existing
functions without modifying the original functions. They can be part of the
functional programming toolbox for reusing common logic.
Chapter 6: Pure functions
A pure function is a function that given the same input always returns the
same output and has no side effects.
Pure functions promise to make code easier to read, understand, test, debug,
compose. These are big promises. Let’s see what they can do.
return name;
}
capitalize("chapter");
//"Chapter"
When reading a pure function we can focus on one place, the current
function. Pure functions give the reader less code to read.
When compute() is a pure function, we are sure that console will log [1, 2,
3], we don’t need to read and understand the compute() function.
Side effects
A side effect is a change of a variable from outside the local environment or
interaction with the outside environment that happens during the computation
of the result.
Mutating the external state may create unexpected issues in some other
distant part of the code. Especially changing input values can create hard to
understand bugs. We don’t know where the input object comes from or how
changing it will affect other functions.
function getNextValue(){
startValue += 1;
return startValue;
}
getNextValue(); //1
getNextValue(); //2
addValue(numbers, 1);
console.log(numbers);
//[1]
addValue(numbers, 2);
console.log(numbers);
//[1, 2]
Free variables
A free variable is a variable used in a function that is neither a local
variables nor a parameter of the function.
Pure functions can access free variables if they are constants. A constant is a
variable declared with const and storing an immutable value.
const vatTax = 20;
function getFinalPrice(price){
return price * (1 + vatTax/100);
}
getFinalPrice(10);
//12
Pure functions don’t access variables from the outside environment that can
change.
Immutability
An immutable value is a value that, once created, cannot be changed.
Pure functions treat input values as immutable. Ideally, input values should
be immutable values. It is a good practice to return an immutable value.
When this is used, we need to use the function as a method or call the
function with call() or apply() and pass in the context object.
When this is used and we invoke the function with the function form, we
create a side effect. In this case, the function may give different results
depending on the environment in which it is run. this becomes a free
variable.
Making the context object explicit means to add it to the parameter list.
function transform(context) { }
Deterministic function
A pure function is a deterministic function. For a specific input, it always
returns the same output.
The next function has no side effects, but it is not deterministic. It is not a
pure function:
function getNextValue(){
return Math.random();
}
Referential transparency
An function has referential transparency if it can be replaced with its
output value and the application behavior doesn’t change.
Because we can replace the function with its result it means that the function
has no side effects. A referentially transparent function does nothing else but
computing the result.
function getMaxId(todos) {
if(todos.length){
const ids = todos.map(prop("id"));
return Math.max(...ids);
}
return 0;
}
console.log(newTodos);
//[{title: "improve", id: 1}]
function sort(arr){
const newArr = [...arr];
return applySort(newArr);
}
console.log(numbers);
//[3, 2, 1];
console.log(sortedNumbers);
//[1, 2, 3]
function runFunction(){
return doImpureTask();
}
function getFunction(){
return doImpureTask;
}
runFunction() is impure. getFunction() is pure.
In a purely functional style, loops are replaced by pure array methods like
filter(), map() or reduce() or by recursion.
Note that replacing all loops with recursion is practical when the engine does
the tail-call optimization. At this moment, the tail-call optimization is not
supported by major browsers.
const arr = [
{ id:1, name:"mango" },
{ id:2, name:"apple" }
];
function getEvens(numbers) {
return numbers.filter(isEven);
}
function getEvens(numbers) {
const evenNumbers = []
numbers.forEach(function(number){
if (isEven(number)) {
evenNumbers.push(number);
}
});
return evenNumbers;
}
The problem
The problem with pure functions is that we can’t write the whole application
using pure functions. There is state change, there is user interface rendering,
there are user interactions, there are network calls. All these cannot be
implemented with pure functions. Pure functions don’t communicate with the
external environment.
What can we do then? We want all the benefits of pure functions, but at the
same time, we cannot use 100% pure functions to do a practical application.
The benefits of pure functions are significant and we should write as many
pure functions as we can. At the same time, we need impure code to
communicate with the environment.
Impure code
Side effects are a common source of bugs because they make code harder to
read and reason about.
Side effects can make code hard to understand, but they are necessary.
Our best approach is to encapsulate side effects. Objects are useful for
encapsulating side effects.
Collecting the impure code together will make it easier to investigate these
parts when bugs arise. By isolating side effects we know where to look for
unexpected issues.
The goal is not only to encapsulate but to reduce the possible kinds of side
effects in a function.
Final thoughts
Taking all these into consideration, it becomes clear that pure functions make
code easier to read and understand. Nevertheless, we should not lose the
importance of making pure functions small, do one thing, and have intention
revealing names.
We should aim for purity as much as we can and encapsulate side effects.
The purpose is to have a big percent of code pure and a low percent impure.
This way we end up with a bigger part of the code that is easier to reason
about and less code that can cause problems.
Imagine you are waiting in a queue. At the entrance, you got an electronic
device showing your number. Just before you were very close to being
served, your order number has changed on the screen. As you ask around
why it happened, you understand that anyone in the building can change your
number, but nobody knows who. While you are waiting, the order number
continues to change, again and again. You now face the task of asking
everyone in the building to stop changing your order number.
Back to code, imagine the order device as an order object with the number
property. Imagine that the order.number property can be changed by any
piece of code in the application. Detecting what part of the code did the
change is hard. You may need to check all code that uses the order object
and imagine how it may happen.
Primitives
Primitive values are immutable, objects are not.
Reading code
Take a look at the next code:
const arr = [1, 2, 3];
doSomething(arr);
console.log(arr);
//[1, 2, 3] ?
Can we easily say that log() will print [1, 2, 3] to console? No, we cannot.
The array is a mutable data structure. It can change in the doSomething()
function. We need to read and understand the doSomething() function in
order to say what will be logged to the console.
If the array was an immutable data structure, we could say what will be
logged to the console without reading the doSomething() function. There
will be less code to read and understand.
Constant
const declares a variable that cannot be reassigned. It becomes a constant
only when the assigned value is immutable.
In short, using const with a primitive value defines a constant. Using const
with an object value doesn’t necessarily define a constant.
const book = {
title : "How JavaScript Works",
author : "Douglas Crockford"
};
Freezing objects
Freezing objects makes them immutable.
Edit property
const title = "How JavaScript Works";
const newBook = Object.freeze({ ...book, title });
console.log(book);
//{title: "JavaScript The Good Parts",
// author: "Douglas Crockford"}
console.log(newBook);
//{title: "How JavaScript Works",
// author: "Douglas Crockford"}
Add property
const description = "Looking at the fundamentals";
const newBook = Object.freeze({ ...book, description});
console.log(book);
//{title: "JavaScript The Good Parts",
// author: "Douglas Crockford"}
console.log(newBook);
//{title: "JavaScript The Good Parts",
// author: "Douglas Crockford"}
// description: "Looking at the fundamentals"}
Remove property
The destructuring syntax can be used to create a new object without the
author property:
console.log(book);
//{title: "JavaScript The Good Parts",
// author: "Douglas Crockford"}
console.log(newBook);
//{title: "JavaScript The Good Parts"}
The pure array methods don’t modify the existing array but create new
values.
reverse(), sort() should be pure, but they are not. To use them, we must
first make a copy of the original array.
books.push({});
//Cannot add property 0
books.pop();
//Cannot delete property '0'
Add
Remove
Edit
return book;
});
Immutable library
Immutable.js provides immutable data structures like List and Map. These
data structures are highly optimized.
List
console.log(Array.from(books));
//[{title: "JavaScript Allongé"},
// {title: "You Don't Know JS"}]
console.log(Array.from(newBooks));
//[{title: "JavaScript Allongé"},
// {title: "You Don't Know JS"},
// {title: "Mastering Immutable.js"}]
Edit
set(index, value) returns a new List having the new value at position
index.
console.log(Array.from(newBooks));
//[{title: "Mastering Immutable.js"},
// {title: "You Don't Know JS"}];
Remove
remove(index) returns a new List with the value at the index position
removed.
const newBooks = books.remove(0);
console.log(Array.from(newBooks));
//[{title: "You Don't Know JS"}];
Map
Add
set(key, value) returns a new Map containing the new key-value pair. If
the key already exists, the value will be replaced. It can be used for both
adding or editing a property.
const description = "Networks and Hierarchies";
const bookWithDesc = book.set("description", description);
console.log(bookWithDesc.toObject());
//{title:"The Square and the Tower",
// author:"Niall Ferguson",
// description:"Networks and Hierarchies"}
Edit
const title = "Civilization";
const newBook = book.set("title", title);
console.log(book.toObject());
//{title: "The Square and the Tower",
// author: "Niall Ferguson"}
console.log(newBook.toObject());
//{title: "Civilization",
// author: "Niall Ferguson"}
Remove
console.log(newBook.toObject());
//{title: "The Square and the Tower"}
I like the public interface of the List data structure but I find working with
Map not so nice as working with an object literal. When working with Map, we
need to access all properties as strings. There is no dot notation, so we need
to get conformable with the string syntax.
The immutable List data structure can be converted to a native array using
Array.from(), spread operator or the toArray() method. The Map data
structure can be converted to an object literal with the toObject() method.
These conversions can be slow for big arrays/objects.
Transfer objects
Transfer objects are plain objects containing only data that move around the
application from one function to another.
Input values
Unexpected changes can create bugs hard to find and understand. That will
mean more time to investigate problems, so immutability saves
troubleshooting and debugging time.
All input values should be treated as immutable values regardless if they are
or not. This way we avoid creating unexpected changes somewhere else in
the application.
Change detection
Immutability offers efficiency at detecting a change in an object. To detect if
an object has changed, we just need to compare the previous reference with
the current one. There is no need to check the values of all properties.
Final thoughts
Immutability makes code easier to read. When values cannot change, code is
much easier to reason about.
There are two ways for working with immutable values in JavaScript. One is
to freeze all objects and arrays at creation and then use the spread operator or
the pure array methods to create new arrays. The other option is to use a
library like Immutable.js that offers immutable data structures.
Immutability is not about variables that cannot change, but about values that
cannot change.
Chapter 8: Partial application and currying
Partial application and currying are techniques for transforming functions
into other functions with fewer arguments. They have a similar purpose, but
they work in different ways. Let’s see how.
Partial application
Partial application is the process of fixing a number of arguments to a
function by producing another function with fewer arguments. The
returned function is a partially applied function.
Fixing or binding the first arguments to a function creates a new function that
takes the remaining arguments. Consider function f(x, y, z):
function f(x, y, z){
console.log(x);
console.log(y);
console.log(z);
}
f1(10, 100);
//1
//10
//1000
f2(4);
//1
//2
//4
f3();
//1
//2
//3
Imagine you have a function and you want to create another one accepting
fewer arguments. First of all, why would you want that? One reason may be
that you want to create a specialized function from a generic one. Another
reason may be that you actually need a function that takes fewer arguments
and can be used with functions like filter() or map().
bind()
The bind() method can do partial application, however, the value given to
this requires attention. Take a look at the next example:
Notice how logInfo() function is created. It requires only one argument, the
message. logInfo() is a partially applied function.
partial()
To better understand how partial application works, let’s create our own
partial() function decorator.
partial() creates a version of the function that takes fewer arguments than
the original function.
function partial(fn, ...leftArgs) {
return function(...rightArgs) {
return fn(...leftArgs, ...rightArgs);
}
}
const todos = [
{id: 1, type: "NC", title: "create"},
{id: 2, type: "RE", title: "review"}
];
The anonymous predicate for filter() takes one argument, the current todo,
and uses it to call isTodoOfType().
Another option is to use partial application and create a new function with the
type already set:
Then we can use the new predicate function as a callback for the filter()
method. Here is the code:
function getTodos(type){
const isOfType = partial(isTodoOfType, type);
return todos.filter(isOfType, type);
}
Argument order
The argument order is important.
The partial() utility fixes arguments from left to right. The following code
will not work properly:
function isTodoOfType(todo, type){
return todo.type === type;
}
function getTodos(type){
return todos.filter(partial(isTodoOfType, type));
}
const shoppingList = [
{
productId: 1,
quantity: 2
},
{
productId: 2,
quantity: 1
}
];
const products = {
1 : { id: 1, name: "mango"},
2 : { id: 2, name: "apple"}
}
console.log(newList);
//[
//{id: 1, name: "mango", quantity: 2},
//{id: 2, name: "apple", quantity: 1}
//]
Currying
Currying transforms a function that takes multiple arguments into a
sequence of functions where each takes one argument.
Consider for example the f(x,y,z) function with three arguments. Here is
how we can write the curried version:
function f(x){
return function(y){
return function(z){
console.log(x);
console.log(y);
console.log(z);
}
}
}
f(1)(2)(3);
//1
//2
//3
The isTodoOfType can be created as a curried function. It takes the type and
returns a predicate function that takes the todo and returns a boolean. See the
code below:
function isTodoOfType(type){
return function(todo){
return todo.type === type;
}
};
function getTodos(type){
return todos.filter(isTodoOfType(type));
}
const todos = [
{id: 1, type: "NC", title: "create"},
{id: 2, type: "RE", title: "review"}
];
curry()
In functional languages like Haskell, functions are curried by default. Calling
the function with fewer arguments results in a new function taking the
remaining arguments. Calling the function with all arguments executes the
function.
curry() offers a more flexible apply system than a truly curried function. It
can apply multiple arguments at once, not just one. When applying all
arguments the function is executed.
import { curry } from 'lodash';
f(1, 2, 3);
f(1, 2)(3);
f(1)(2)(3);
//1
//2
//3
The function length property gets the number of parameters the function is
expecting. However, the rest parameter does not count into the function
length property, neither default values.
function f1 (a, b) { }
function f2 (a, ...b) { }
function f3 (a, b=0) { }
f1.length; //2
f2.length; //1
f3.length; //1
return function(...args){
const allArgs = [...leftArgs, ...args];
if (allArgs.length >= noArguments){
return f(...allArgs);
} else{
return curry(f, length, allArgs);
}
}
}
Arity
The arity of a function represents the number of arguments it takes.
Full Application
Applying fewer than the total number of arguments to a function is a partial
application.
f(1,2,3);
f.apply(null, [1,2,3]);
f.call(null, 1,2,3);
Thunk
A thunk is a function that delays the invocation of another function. The
thunk function calls the original function with all arguments.
function sum(x, y, z){
const total = x + y + z;
console.log(total);
return total;
}
function sumThunk() {
return sum(1, 2, 3);
}
sumThunk();
//6
thunk(fn, args) takes a function and all the arguments and creates a new
function that executes the original function with all arguments.
function thunk(fn, ...args){
return function(){
return fn(...args);
}
}
thunkify(fn) takes a function fn and returns a new function that asks for
arguments and returns a third function that, when called, invokes fn with all
arguments.
function thunkify(fn) {
return function(...args) {
return function() {
return fn(...args);
}
}
}
I think it is better to use partial() only with fewer arguments than the total
number of arguments, and use thunk() to create a nullary function that calls
the original function with all arguments.
function f(x, y, z){
console.log(x);
console.log(y);
console.log(z);
}
Final thoughts
Partial application transforms a function with multiple arguments in a
function with fewer arguments.
Suppose we have two function f() and g() taking one argument.
const y = g(x);
const z = f(y);
compose()
In order to compose multiple functions, we can create the compose() utility
function.
f(g(x)) === compose(f,g)(x);
function f(y){
return y * y;
}
function g(x){
return x + x;
}
f(g(2)) === compose(f,g)(2);
const books = [
{id:1, title: "JavaScript The Good Parts", type: "T"},
{id:2, title: "Civilization", type: "H"},
{id:3, title: "Javascript Allongé", type: "T"}
];
In this case, we use function composition. The map() function takes as input
the return value from the prop() function.
pipe()
pipe() creates a pipeline of functions, where the output of one function is
the input for the next one.
function isTechnology(todo){
return todo.type === "T";
}
function creatItemHtml(todo){
return `<div>${todo.title}</div>`;
}
function createListHtml(todos){
const htmlArr = todos.map(creatItemHtml);
return htmlArr.join("");
}
function renderHtml(html){
$("#books").html(html);
}
const books = [
{ id:1, title: "JavaScript The Good Parts", type: "T" },
{ id:2, title: "Civilization", type: "H" },
{ id:3, title: "Javascript Allongé", type: "T" }
];
The books processing can be rewritten with the pipe() utility function:
import pipe from "lodash/flow";
pipe(
filterBooks,
createListHtml,
renderHtml
)(books);
function logData(data){
console.log(data)
return data;
}
pipe(
filterBooks,
logData,
createListHtml,
logData,
renderHtml
)(books);
function handleError(error) {
console.error(error);
}
fetchBooks()
.then(filterBooks)
.then(createListHtml)
.then(renderHtml)
.catch(handleError);
Final thoughts
Function composition is the process of applying one function to the result of
another. With function composition we eliminate the intermediary variables.
The name of a variable, function … should answer all the big questions. It
should tell you why it exists, what it does, and how it is used. If a name
requires a comment, then the name does not reveal its intent.— Tim
Ottinger in Clean Code
The function name turns out to have a very important role in readability as
we can easily read and understand meaningful words. The function name is
our tool for expressing the intention of a piece of code.
Now check out the same logic refactored to pure functions with intention
revealing names:
function isTechnology(book){
return book.type === "T";
}
With good function names we can read the code faster. We can read the
function name and understand its purpose. There is no need to analyze its
code.
I suggest using names that express the function purse and add value to the
existing code. For example, names like onClick() or myCustomClickEvent()
express where the function is used not what it does. The same function can
very well handle other events or be called in other places.
The sum() function can be written with the arrow syntax. In this case, the
arrow function infers the name of the variable.
const sum = (a, b) => a + b;
const total = numbers.reduce(sum, 0);
Simple code
There are situations when the code is so small that creating a named function
adds no value. In that case, we can stay with the anonymous function until
the code becomes more complex. Consider the next code:
const books = [
{title:"How JavaScript works",type:"T"},
{title:"Square and tower",type:"H"},
{title:"Functional-Light JavaScript",type:"T"}
];
prop() takes the name of a property and returns a function that asks for an
object an returns the property of that object.
//with arrow function
const prop = key => obj => obj[key];
Final thoughts
Intention revealing function names improve readability and offer a better
debugging experience.
It’s widely estimated that developers spend 70% of code maintenance time
on reading to understand it.— Kyle Simpson, author of Functional-Light
JavaScript
Below is the imperative code filtering the to-dos and creating new objects
having the userName property.
let filteredTodos = [];
for(let i=0; i<todos.length; i++){
let todo = todos[i];
if (todo.type === "RE" && !todo.completed) {
filteredTodos.push({...todo,userName: todo.user.name});
}
}
console.log(filteredTodos);
//[{
// completed: false,
// desc: "task2",
// id: 2,
// type: "RE",
// user: {id: 2, name: "user2"},
// userName: "user2"
//}]
function toTodoView(todo) {
return { ...todo, userName: todo.user.name };
}
Point-free style
Point-free is a technique that improves readability by eliminating the
unnecessary arguments.
Below is the imperative code computing the total price and the price for
fruits:
let totalPrice = 0, fruitsPrice = 0;
for(let i=0; i<shoppingList.length; i++){
let line = shoppingList[i];
totalPrice += line.quantity * line.price;
if (line.type === "FRT") {
fruitsPrice += line.quantity * line.price;
}
}
console.log(totalPrice);
//50
console.log(fruitsPrice);
//35
Taking the functional approach, in this case, will require the use of reduce()
to compute the total price.
reduce() reduces a list of values to one value
As we did before, we refactor the code using pure functions with intention
revealing names, pure array methods, and a point-free style:
function addPrice(totalPrice, line){
return totalPrice + (line.quantity * line.price);
}
function areFruits(line){
return line.type === "FRT";
}
Decomposition
Our natural way of dealing with a problem is to break it into smaller pieces
and then put everything back together.
Our aim should be to create small pure functions with intention revealing
names and then compose them back together.
Naming these small functions requires time, but if it is done well, it will
make the code easier to read.
Final thoughts
Working with collections in a functional style breaks the data transformations
in steps like filter, map, reduce, or sort. At the same time, it requires to define
small pure functions to support those transformations.
Synchronous code is easy to reason about, but there are situations where we
need to do things without blocking the interface. Looking from this
perspective:
We need a way to listen for user interactions or to get data from Web APIs
without blocking the interface. This is the role of the asynchronous
programming model.
block(5000);
logMessage();
delay(5000, logMessage);
This time the application is not blocked. After 5s the message is displayed.
The most common asynchronous tasks are user events, network calls, timing
events.
Callbacks
Callbacks offer a simple way to run code when asynchronous operations are
completed.
A callback is a function passed as an argument to another function.
The most common scenarios where callbacks are used for asynchronous tasks
are events.
DOM events
User interactions may result in many events like click, focus, blur,
keypress. Listening to these events doesn’t block the interface.
Look at the next examples using callbacks to handle DOM events with
jQuery:
function doSomething(){ }
$("#btn").on("click", doSomething);
$("#input").on("keypress", doSomething);
There are two functions for executing code at specified time intervals.
setInterval(doSomething, 5000);
Nested callbacks
Callbacks are great for simple tasks like event handling, but they are hard to
combine. Consider fetchUsers(), fetchPosts(), fetchComments() as being
able to do network calls and accept a callback to execute when the operation
is completed.
fetchUsers(function(users) {
fetchPosts(function(dictionaries) {
fetchComments(function(todos) {
console.log(users);
console.log(dictionaries);
console.log(todos);
});
});
});
What about starting all network calls at the same time and then do
something? It is possible with callbacks, but it is harder and can make code
more complicated than necessary.
Promises
Promises offer a better option for composing asynchronous tasks.
A promise is an object that represents a possible future result of an
asynchronous operation.
Promises can be given to some other parts of the code before knowing the
result.
Network calls
Promises are commonly used to handle network calls. The application is not
blocked while waiting for the Web API response. Even more, the network
calls can run in parallel.
Network calls can be done with the fetch() function. The fetch() function
returns a promise.
Composing promises
function fetchPosts() {
return fetch("/posts");
}
function fetchComments() {
return fetch("/comments");
}
function doSomething() { }
function handleError(error) {
console.error(error);
}
fetchUsers()
.then(fetchPosts)
.then(fetchComments)
.then(doSomething)
.catch(handleError);
Promises support a chaining system that allows us to pass data through a set
of functions. Promises use callbacks.
If there is an error at any step, the chain control jumps to the closest rejection
handler down the chain.
Promise.all() returns a new promise that resolves when all promises have
resolved or reject when one of the promises rejects.
Promise.all(allPromises)
.then(doSomething)
.catch(handleError);
async/await
async/await allows us to write asynchronous code that looks like
synchronous code. async/await is a syntactical sugar for using promises.
Let’s see how to use other asynchronous tasks inside a function. Check the
next example:
(async function startApplication(){
const users = await fetchUsers();
const posts = await fetchPosts();
const comments = await fetchComments();
console.log(users);
console.log(posts);
console.log(comments);
})();
The code after await fetchUsers() executes when fetchUsers gets the
result. The code after await fetchPosts() executes when fetchPosts gets
the result. In a way, it can be imagined like:
fetchUsers().then((users) => {
fetchPosts().then((posts) => {
fetchComments().then((comments)=>{
console.log(users);
console.log(posts);
console.log(comments);
});
});
});
When we want to execute all network calls in parallel, we can use await with
Promise.all():
I don’t think that making asynchronous code look like synchronous code is
necessarily a good idea. It may lead to trouble understanding which code is
executed asynchronously and which is executed synchronously. I think that
promises are clearer at showing which part of the code deals with
asynchronous tasks.
The engine uses an event loop. Callback functions are added to the event loop
when an event occurs and there is an event listener attached to it. The event
loop uses a queue, so callbacks are processed in the order they are added.
Functions in the event loop must finish fast. If they don’t the application
becomes unresponsive and after some minutes the browser displays the
“script is taking too long to run” dialog.
DOM events, timing events, network requests, all add callbacks to the event
loop.
The next code doesn’t finish fast and blocks the application:
for(let i=0; i<10000000000; i++){}
Web Workers
Web workers offer a way to run long-running tasks. These tasks run in
parallel and don’t affect the main thread. When a long-running task finishes,
the result must be communicated back to the main thread.
There is no shared memory between the main thread and web workers.
In the next example, the main thread creates a web worker and sends it a
message to start the long-running process. It then listens for the result and
after terminates the worker.
const worker = new Worker("worker.js");
The worker listens for messages from the main thread. When it gets the
"start" message, it begins the long-running computation. After the
computation finishes, the result is sent to the main thread with
postMessage().
function computeValue() {
for (let i = 0; i < 10000000000; i++) {}
return 10000000000;
}
Final thoughts
Callbacks are the fundamental unit of asynchronous programming.
Promises are objects that represent future results. Promises offer a better way
for composing asynchronous tasks.
Property key
The property key is a unique string.
There are two ways to access properties: dot notation and bracket notation.
When the dot notation is used, the property key must be a valid identifier.
const obj = {
message : "discover"
}
obj.message; //"discover"
Accessing a property that doesn’t exist will not throw an error, but will return
undefined .
obj.otherProperty; //undefined
When the bracket notation is used, the property key does not have to be a
valid identifier, it can have any value.
const french = {};
french["Thank you"] = "Merci";
When a value, that is not a string, is used as the property key, it is converted
to a string, via the toString() method when available.
const obj = {};
//Number as key
obj[1] = "Number 1";
obj[1] === obj['1']; //true
//Object as key
const number1 = {
toString : function() {return '1';}
}
obj[number1] === obj['1']; //true
In the previous example, the object number1 is used as the property key. It is
then converted to a string and the result of the conversion, '1', is used as the
property key.
Property value
The property value can be a primitive, an object, or a function.
Object as value
Objects can be nested inside other objects. See the next example:
const book = {
title : "How JavaScript Works",
author : {
firstName : "Douglas",
lastName : "Crockford"
}
}
book.author.firstName; //"Douglas"
app.authorGateway = {
getAuthors : function() {}
};
app.bookGateway = {
getBooks : function() {}
};
Function as value
Prototypes
Objects have a “hidden” link __proto__ to the prototype object, from which
they inherit properties.
For example, objects created with an object literal have a link to the
Object.prototype :
Prototype chain
The prototype object has a prototype of its own. When a property is accessed
and the object does not contain it, the engine will look down the prototype
objects until it either finds the requested property or until it reaches null.
Read-only
The prototype link is used only for reading values. When a change is made, it
is made only on the current object not on its prototype, not even when a
property with the same name exists on the prototype.
Built-in prototypes
let no = 1;
no.__proto__ === Number.prototype; //true
no.__proto__.__proto__ === Object.prototype; //true
All objects, functions and primitives, except null and undefined, inherit
properties from Object.prototype. All of them have the toString()
method, for example.
Map
We can think of an object as a map. The keys in the map are the names of the
object’s properties.
italian["Yes"] = "Si";
italian["No"] = "No";
italian["Yes"]; //"Si"
Object literal
Object literal offers a simple, elegant way for creating objects.
const timer = {
secret: 0,
start : function() {},
stop : function() {},
}
However, this syntax has a few drawbacks. All properties are public, methods
can be redefined, and we can’t use the same methods on new instances.
timer.secret; //0
timer.start = function() {
console.log("don't start");
}
timer.start(); //"don't start"
Object.create()
Object.create(prototypeObject) creates a new object, using the argument
object as its prototype.
const timerPrototype = {
start : function() {},
stop : function() {}
};
When the prototype is frozen, the objects that inherit from it won’t be able to
change the properties defined within. In this case, the start() and stop()
methods can’t be redefined.
const timerPrototype = Object.freeze({
start : function() {},
stop : function() {}
});
Function constructor
Initially, the language proposed the function constructor as a sugar syntax for
building objects with a custom prototype. Take a look at the next example:
function Timer(){
this.secret = 0;
}
Timer.prototype = {
start : function() {},
stop : function() {}
}
All functions defined with the function keyword can be used as function
constructors. The function constructor is called with the new operator. The
new object has the prototype set to FunctionConstructor.prototype.
const timer = new Timer();
timer.__proto__ === Timer.prototype;
new
return newObj;
}
Class
class offers a much better sugar syntax for building objects with a custom
prototype. Check out the next example:
class Timer{
constructor(){
this.secret = 0;
}
start() {}
stop() {}
}
No privacy
The prototype-based inheritance patterns have no privacy. All object’s
properties are public.
Object.keys() returns an array with all the own property keys. It can be
used to iterate over the object’s properties.
function logProperty(name){
console.log(name); //property name
console.log(obj[name]); //value
}
Object.keys(obj).forEach(logProperty);
Final thoughts
Objects are dynamic in nature and can be used as maps. All object’s
properties are public.
Objects inherit from other objects.
Constructor function and class are a sugar syntax for creating objects that
inherit from a custom prototype object. They first define the custom
prototype with all methods and then the new operator creates objects that
inherit from that prototype.
Chapter 14: Objects with closures
Closure - is a consequence of functions that can nest and functions that are
first class values, and JavaScript has it, and is almost completely right in
the way it implements them, and it’s maybe the best feature ever put into a
programming language.— Douglas Crockford
Closures can be created as functions with private state. More than that, we
can create many closures sharing the same private state. This way, we can
build objects as collections of closures. Closures encapsulate state.
Factory functions
Let’s see how we can do that. We start from the Revealing Module pattern,
give that function a name and use it to build new encapsulated objects. Here
is an example of the Counter function that can create counter objects:
function Counter(){
let state = 0;
function increment(){
state += 1;
return state;
}
function decrement(){
state -= 1;
return state;
}
return Object.freeze({
increment,
decrement
});
};
The Counter function is called factory function. Objects built this way have a
series of benefits.
Encapsulation
Encapsulation means information hiding.
When using closures, only the methods exposed are public, everything else is
encapsulated.
const counter = Counter();
counter.state; //undefined
The private data of the object can be modified only through its public
methods.
No this
There are no this changing context issues simply because when building
objects with closures, this is not used at all.
Let’s build a few other objects with closures and compare them with the ones
built with prototypes.
Timer
The timer object is used to do asynchronous operations at a specific interval.
In contrast with setInterval it waits for the previous operation to finish
before making a new call. It implements the Recursive setTimeout pattern.
Below the timer object is created both with prototypes and with closures
executeAndStartTimer(){
this.callback().then(() => {
this.timerId = setTimeout(this.executeAndStartTimer,
this.interval);
});
}
start(){
if(this.timerId === 0){
this.executeAndStartTimer();
}
}
stop(){
if(this.timerId !== 0){
clearTimeout(this.timerId);
this.timerId = 0;
}
}
}
function getTodos(){
return fetch("/todos");
}
function executeAndStartTimer(){
callback().then(function makeNewCall(){
timerId = setTimeout(executeAndStartTimer, interval);
});
}
function stop(){
if(timerId){
clearTimeout(timerId);
timerId = 0;
}
}
function start(){
if(!timerId){
executeAndStartTimer();
}
}
return Object.freeze({
start,
stop
});
}
Encapsulation
With class all members, fields, and methods, of the object, are public.
With closures only the start() and stop() methods are public.
The timerId and executeAndStartTimer() are private.
No this
this may unexpectedly lose context when the method is used as a callback. It
may lose context when used in nested functions.
When the start() method is used as an event handler, like in the code
below, this loses context:
$("#btnStart").click(timer.start);
There are no such issues when using closures as this is not used.
Stack
The stack is a data structure with two principal operations: push() for adding
an element to the collection, and pop() for removing the most recent element
added. It adds and removes elements according to the LIFO, Last In First
Out, principle.
Look at the next example:
const stack = Stack();
stack.push(1);
stack.push(2);
stack.push(3);
stack.pop(); //3
stack.pop(); //2
function push(value){
list.push(value);
}
function pop(){
return list.pop();
}
return Object.freeze({
push,
pop
});
}
The stack object has two public methods push() and pop(). The internal state
can only be changed through these methods.
Queue
The queue is a data structure with two principal operations: enqueue() for
adding an element to the collection, and dequeue() for removing the first
element from the collection. It adds and removes elements according to the
FIFO, First In First Out, principle.
Here is an example:
const queue = Queue();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
queue.dequeue(); //1
queue.dequeue(); //2
function enqueue(value){
list.push(value);
}
function dequeue(){
return list.shift();
}
return Object.freeze({
enqueue,
dequeue
});
}
As we have seen before, the internal state of the object can’t be accessed
from the outside.
Event emitter
An event emitter is an object with a publish/subscribe public interface. It is
used to communicate between different parts of an application.
eventEmitter.subscribe("update", doSomething);
eventEmitter.subscribe("update", doSomethingElse);
eventEmitter.subscribe("add", addItem);
eventEmitter.publish("update", {});
function doSomething(value) { }
function doSomethingElse(value) { }
function addItem(value) { }
First, we subscribe with two functions for the update event, and with one
function for the add event. When the event emitter publishes the update
event, doSomething() and doSomethingElse() will be called.
function EventEmitter(){
const subscribers = [];
function subscribe(type, callback){
subscribers[type] = subscribers[type] || [];
subscribers[type].push(callback);
}
return Object.freeze({
subscribe,
publish
});
}
Only the publish() and subscribe() methods are public. Everything else is
private. this is not used.
Memory
Classes are better at memory conservation, as they are implemented over the
prototype system. All methods will be created once in the prototype object
and shared by all instances.
The memory cost of using closures is noticeable when creating thousands of
the same object. Here is a measurement of the memory cost in Chrome:
return Object.freeze({
doSomething
});
}
return Object.freeze({
doSomething : service.doSomething,
doSomethingElse
});
}
specialService.doSomething();
//"do-something"
specialService.doSomethingElse();
//"do-something-else"
Final thoughts
Closures open a new way of doing object-oriented programming without
classes or prototypes.
Objects built with closures are encapsulated, favor composition and have not
this changing context issues.
Chapter 15: Method decorators
The decorators act like keywords or annotations, documenting the
method’s behavior but clearly separating these secondary concerns from
the core logic of the method.— Reginald Braithwaite, author of JavaScript
Allongé
Method decorators are a tool for reusing common logic. They are
complementary to object-oriented programming. Decorators encapsulate
responsibility shared by different objects.
function add(todo){
const start = Date.now();
if(currentUser.isAuthenticated()){
todos.push(todo);
} else {
throw new Error("Not authorized");
}
return Object.freeze({
add
});
}
const currentUser = {
isAuthenticated : function(){
return true;
}
}
The add() method has the primary responsibility of adding a new todo to the
internal state and two secondary responsibilities of authorizing the user and
logging the duration of execution. The secondary concerns can repeat in other
methods.
function TodoStore(){
const todos = [];
function add(todo){
todos.push(todo);
}
return Object.freeze({
add: compose(logDuration, authorize)(add)
});
}
Now the add() method just adds the todo to the list. The other
responsibilities are implemented by decorating the method.
Logging duration
A common scenario is logging the duration of a method call. The following
decorator logs the duration of a synchronous call.
function logDuration(fn){
return function(...args){
const start = Date.now();
const result = fn(...args);
const duration = Date.now() - start;
console.log(fn.name + "() duration : " + duration);
return result;
}
}
Authorization
The authorize() decorator makes sure the user has the right to execute the
method. This decorator is more complex as it has a dependency on another
object currentUser. In this case, we can use a function
createAuthorizeDecorator() to build the decorator. The decorator will
execute the original method only if the user is authenticated.
function createAuthorizeDecorator(currentUser) {
return function authorize(fn) {
return function(...args) {
if (currentUser.isAuthenticated()) {
return fn(...args);
} else {
throw new Error("Not authorized");
}
};
};
}
Decorator composition
Often we need to apply multiple decorators on a method. A simple way is to
call the decorators one after the other:
function add() { }
Another way is to compose all decorators and then apply the new decorator
on the original function.
import { compose } from "./tools";
function TodoStore(){
function add(){}
return Object.freeze({
add: compose(logDuration, authorize)(add)
});
}
Decorator order
In some cases, the order in which decorators get executed may not be
important. But often the order does matter.
We may want to apply the decorators directly to the function that creates the
object. The decorate() function can help with that:
function decorate(factory, ...decorators) {
return function(...args) {
const newObject = decorateMethods(factory(...args),
...decorators);
return Object.freeze(newObject);
};
}
In the next example, all public methods of the object created by TodoStore
are decorated with logDuration() and authorize().
import { decorate } from "./tools";
import { logDuration, authorize } from "./decorators";
function TodoStore(){
function get(){}
function add(todo){ }
function edit(id, todo){}
function remove(id){}
return Object.freeze({
get,
add,
edit,
remove
});
}
export default decorate(TodoStore, logDuration, authorize);
function addAndMutate(todo) {
todos = add(todos, todo);
}
return Object.freeze({
add: addAndMutate
});
}
function Store(storeConfig) {
return function() {
let state = storeConfig.state;
function setter(fn) {
return function(...args) {
state = fn(state, ...args);
};
}
function getter(fn) {
return function(...args) {
return fn(state, ...args);
};
}
return Object.freeze({
...decorateMethods(storeConfig.getters, getter),
...decorateMethods(storeConfig.setters, setter)
});
};
}
getter() takes a pure function and creates a function that returns the current
state transformed with the pure function.
setter() takes a pure function and creates a function that modifies the state
using the pure function.
With a library like Store.js we can write the TodoStore using only pure
functions:
import Store from "./Store";
Final thoughts
Decorators encapsulate common logic reusable between objects. They are
complementary to object-oriented programming.
Let’s create a TodoStore class that manages a list of todos. The TodoStore
class has a private list and two public methods: push() and getBy(). The
Todo class defines the todo transfer object. The two classes are then used in
the main() function.
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
public TodoStore()
{
this.list = new ArrayList<Todo>();
}
In 1995 the JavaScript language was created. In a way it put together the
prototypes patterns ideas from the Self language with the functional patterns
from Scheme, in a Java-like syntax.
getBy(text){
return this.list
.filter(todo => todo.title.includes(text));
}
}
class Todo{
constructor(id, title){
this.id = id;
this.title = title;
}
}
(function startApplication(){
const todoStore = new TodoStore();
The prototype system is more flexible than classes and is good at memory
conservation, but it turns out to have some drawbacks:
Functional programming
JavaScript rediscovered and brought to the mainstream the functional
programming paradigm.
Pure functions have a big set of advantages. They are easier to read,
understand, test, debug, or compose. Pure functions are great, but we can’t
write a practical application using only pure functions.
function push(todo){
list.push(todo);
}
function getBy(text){
return list.filter(todo => todo.title.includes(text));
}
return Object.freeze({
push,
getBy
})
}
(function startApplication(){
const todoStore = TodoStore();
todoStore.push(Todo(1, "find"));
todoStore.push(Todo(2, "read"));
todoStore.push(Todo(3, "share"));
Data objects that expose data and have no behavior. Data objects are
immutable. They are created with the Todo() function in this example.
Behavior objects that expose behavior and hide data. There is only one
object in this example and is created with the TodoStore() function.
Final thoughts
Object-oriented programming, functional programming, purely functional
programming are all paradigms at our disposal for building better
applications.
The prototype system tries to simplify class inheritance but it turns out it has
its own problems. It has no encapsulation and this creates losing context
issues. However, the prototype system is good at memory conservation and is
flexible enough to transpile class-based objects to prototype-based objects. In
the end, the prototype system is good for transpiling reasons, making
JavaScript a target compilation language.
Continue your learning path with Functional Architecture with React and
Redux and use React and Redux to build several applications with an
incremental level of complexity.
The functional architecture implies getting the initial state, showing it to the
user using the view functions, listening for actions, updating the state based
on those actions, and rendering the updated state back to the user again.
Enjoy the Functional Programming with JavaScript and React book series!
Table of Contents
Chapter 1: A brief overview of JavaScript
Chapter 2: New features in ES6+
Chapter 3: First-class functions
Chapter 4: Closures
Chapter 5: Function decorators
Chapter 6: Pure functions
Chapter 7: Immutability
Chapter 8: Partial application and currying
Chapter 9: Function composition
Chapter 10: Intention revealing names
Chapter 11: Making code easier to read
Chapter 12: Asynchronous programming
Chapter 13: Objects with prototypes
Chapter 14: Objects with closures
Chapter 15: Method decorators
Chapter 16: Waiting for the new programming paradigm