By Cristian Salcescu
Discover Functional JavaScript was named one of the best new Functional Programming books by BookAuthority!
ECMAScript 2015 (aka ES6) comes with the class
syntax, so now we have two competing patterns for creating objects. In order to compare them, I’ll create the same object definition (TodoModel) as a class, and then as a factory function.
class TodoModel {
constructor(){
this.todos = [];
this.lastChange = null;
}
addToPrivateList(){
console.log("addToPrivateList");
}
add() { console.log("add"); }
reload(){}
}
TodoModel as a Factory Function
function TodoModel(){
var todos = [];
var lastChange = null;
function addToPrivateList(){
console.log("addToPrivateList");
}
function add() { console.log("add"); }
function reload(){}
return Object.freeze({
add,
reload
});
}
Encapsulation
The first thing we notice is that all members, fields, and methods of a class object are public.
var todoModel = new TodoModel();
console.log(todoModel.todos); //[]
console.log(todoModel.lastChange) //null
todoModel.addToPrivateList(); //addToPrivateList
The lack of encapsulation may create security problems. Take the example of a global object that can be modified directly from the Developer Console.
When using factory function, only the methods we expose are public, everything else is encapsulated.
var todoModel = TodoModel();
console.log(todoModel.todos); //undefined
console.log(todoModel.lastChange) //undefined
todoModel.addToPrivateList(); //taskModel.addToPrivateList
is not a function
this
this
losing context problems are still there when using class. For example, this
is losing context in nested functions. It is not only annoying during coding, but it’s also a constant source of bugs.
class TodoModel {
constructor(){
this.todos = [];
}
reload(){
setTimeout(function log() {
console.log(this.todos); //undefined
}, 0);
}
}
todoModel.reload(); //undefined
or this
is losing context when the method is used as a callback, like on a DOM event.
$("#btn").click(todoModel.reload); //undefined
There are no such problems when using a factory function, as it doesn’t use this
at all.
function TodoModel(){
var todos = [];
function reload(){
setTimeout(function log() {
console.log(todos); //[]
}, 0);
}
}
todoModel.reload(); //[]
$("#btn").click(todoModel.reload); //[]
this and arrow function
The arrow function partially solves the this
loosing context issues in classes, but at the same time creates a new problem:
this
is no longer loosing context in nested functionsthis
is loosing context when the method is used as a callback- arrow function promotes the use of anonymous functions
I refactored the TodoModel
using the arrow function. It’s important to note that in the process of refactoring to the arrow function we can loose something very important for readability, the function name. Look for example at:
//using function name to express intent
setTimeout(function renderTodosForReview() {
/* code */
}, 0);
//versus using an anonymous function
setTimeout(() => {
/* code */
}, 0);
Discover Functional JavaScript was named one of the best new Functional Programming books by BookAuthority!
For more on applying functional programming techniques in React take a look at Functional React.
Learn functional React, in a project-based way, with Functional Architecture with React and Redux.