Skip to main content

How to decide between classes v. closures in JavaScript

JavaScript logo against an orange sky.

Before the arrival of ES6 classes in JavaScript, one of the fundamental ways to create a factory that produces similar types of objects was through closures and JavaScript constructor functions.

Closures and classes behave differently in JavaScript with a fundamental difference: closures support encapsulation, while JavaScript classes don’t support encapsulation.

NB: There is a proposal for this and it is in stage 3. It’s enabled in some browsers by default and can also be enabled through a Babel plugin.

Encapsulation is one of the core tenets of OOP (object oriented programming), and it’s essentially about protecting the private data of an object such that they can only be accessed or mutated via the public API exposed by the same object.

The public API makes sure that the private data of the object is accessed in a controlled way, and may decide to update private data provided that certain validation conditions are met.

Traditionally, JavaScript developers used _ to prefix the properties or methods that they intended to be private.

This s is problematic for a few reasons.

First off, new developers might not be aware of this and might modify private data.

Additionally, experienced developers might modify private data thinking they are sure of what they are doing, and this may cause unintended side effects.

Let’s consider an example that implements a user model first using classes (which are synthetical sugar for constructor functions), and then do the same with a closure.

Note the difference:

// class Example
 class UserClasses {
  constructor({firstName, lastName, age, occupation}){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.occupation = occupation;
  }
  describeSelf() {
    console.log(`My name is ${this.firstName} ${this.lastName}, I am ${this.age}years     Old and i work as a ${this.occupation}`);
  }
getAge() {
    return this.age;
  }
}
const gbolahan = new UserClasses({firstName: "Gbolahan", lastName: "Olagunju", age: 28, occupation: "Software Developer"});
gbolahan.describeSelf();
//My name is Gbolahan Olagunju. I am 28 years old and I work as a Software Developer.
// closure Example
const UserClosure = ({firstName, lastName, age, occupation }) => {
  return ({
    describeSelf : () => {
      console.log(`My name is ${firstName} ${lastName}, I am ${age}years Old and i work as a ${occupation}`);
    },
    getAge: () => age;
  })
}
const zainab = UserClosure({firstName: "Zaynab", lastName: "Olagunju", age: 30, occupation: "Economist"});
zainab.describeSelf();

//My name is Zaynab Olagunju. I am 30 years Old and I work as a Economist.

From the above example, you’ll notice that we can implement an object blueprint using either closures or classes. However, there are a few differences that are important for us to identify.

The classe model uses the this keyword to refer to private data, while we aren’t referring to this in any way in the closure implementation. For this reason, closures are preferable as thisin JavaScript doesn’t always work as expected when compared to other traditional OOP languages.

The class implementation uses the new keyword to create an instance, while we simply just call the function in the closure implementation.

The closure implementation supports encapsulation, since we don’t directly have access to its private data except through the methods it exposes. We can manipulate the private data of the class implementation, thus making the class implementation more brittle.

On the other hand, classes can be faster.

Consider this example:

const zainab = UserClosure({firstName: "Zaynab", lastName: "Olagunju", age: 30, occupation: "Economist"});

console.log(zainab.firstName) // undefined
//can only be accessed via the expose API
console.log(zainab.getAge()) // 30
vs
const gbolahan = new UserClasses({firstName: "Gbolahan", lastName: "Olagunju", age: 28, occupation: "Software Developer"});

console.log(gbolahan.firstName) // Gbolahan

Here, the class implementation tends to be faster because of how it is implemented internally by the browser or Node environment.

Every instance of the class shares the same prototype, meaning that a change in the prototype will also affect every instance. Meanwhile, every instance created by the closure implementation is unique.

Let’s see how this plays out visually:

Class implementation.

From the diagram above, we can roughly imagine that the class implementation creates one blueprint in memory that all instances created through it will share.

On the other hand, the closure implementation creates a fresh reference in memory for every instance, thus making it less memory efficient.

Let’s implement this in Node and see the values that this logs out using process.memoryUsage():

// class Example
class UserClass {
  constructor({firstName, lastName, age, occupation}){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.occupation = occupation;
  }
  describeSelf() {
    console.log(`My name is ${this.firstName} ${this.lastName}, I am ${this.age}years Old and i work as a ${this.occupation}`);
  }
  getAge() {
    return this.age;
  }
  showStrength () {
    let howOld = this.age;
    let output = 'I am';
    while (howOld-- > 0) {
      output += ' very';
    }
    return output + ' Strong';
  }
}
const individuals = [];
for (let i = 0; i < 4000; i++) {
    const person = new UserClass({firstName: "Zaynab", lastName: "Olagunju", age: [i], occupation: "Economist"})
    individuals.push(person)
  }
  const used = process.memoryUsage();
for (let key in used) {
  console.log(`${key} ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`);
}
const start = Date.now()
individuals.map(person => person.showStrength());
console.log('Finished displaying strength in ' + (Date.now() - start) / 1000 + ' seconds');

//This was the result that was displayed by my mac
// rss 29.72 MB heapTotal 17.73 MB heapUsed 6.99 MB external 0.01 MB
// Finished displaying strength in 1.233 seconds

Now let’s compare this to the closure implementation:

const UserClosure = ({firstName, lastName, age, occupation }) => {
  return ({
    describeSelf : () => {
      console.log(`My name is ${firstName} ${lastName}, I am ${age}years Old and i work as a ${occupation}`);
    },
    getAge: () => {
      return age;
    },
    showStrength: () => {
      let howOld = age;
      let output = 'I am';
      while (howOld-- > 0) {
        output += ' very';
      }
      return output + ' Strong';
    }
  })
}
const individuals = [];
for (let i = 0; i < 4000; i++) {
    const person = UserClosure({firstName: "Zaynab", lastName: "Olagunju", age: [i], occupation: "Economist"})
    individuals.push(person)
  }
  const used = process.memoryUsage();
for (let key in used) {
  console.log(`${key} ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`);
}
const start = Date.now()
individuals.map(person => person.showStrength());
console.log('Finished displaying strength in ' + (Date.now() - start) / 1000 + ' seconds')
// rss 30.12 MB heapTotal 18.23 MB heapUsed 8.03 MB external 0.01 MB
// Finished displaying strength in 4.037 seconds

NB: using process.memoryUsage() is not the most accurate way to determine memory usage, as it varies slightly on different runs. Still, it gets the job done.

Conclusion

Closures offer simplicity, since we don’t have to worry about the context that this is referring to.

Meanwhile, classes tend to be slightly more performant if we are going to be creating multiple instances of an object.

If we are creating multiple instances of an object, classes will best suit our needs. Meanwhile, if we don’t plan to create multiple instances, the simplicity of closures may be a better fit for our project.

The needs of the project will determine whether closures or classes are most appropriate.

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...