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.

TodoModel as a Class

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 functions
  • this 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.

Follow on Twitter