Functions vs Classes in JavaScript
I always wondered why in different JavaScript communities and projects people still prefer using functions instead of objects. Of course, we're not talking about projects written in the functional paradigm. What is even more interesting is that in some projects you won’t find any Class keyword, even though this keyword was introduced in JavaScript 10 years ago (in ES6 version). Recently, quite by accident, I had a conversation with a front-end developer who favors functions over classes. It turned out into an interesting discussion that led to a useful experiment, and that is why.
A little bit of a disclaimer. As always, there is no one right or correct answer in programming. It always depends on a lot of things. In some cases, functions are better, meaning they are more concise and expressive. In some cases, especially when you have a hidden state, objects are better. This post is not intended to convince you that one approach is better than another. It is just an attempt to understand the reasoning behind some decisions.
As I mentioned, this conversation started accidentally from a discussion about this piece of code. Obviously, this is not the production code, but trying to call something on array values seems unnatural to me. So, I mentioned that objects in this case would make more sense. And that started the discussion.
The main arguments for using functions over objects were:
- functions are more concise and readable
- classes are just a wrapper over functions (syntactic sugar)
- classes are useful only when you need to do something and keep the result (which is pretty rare)
- functions are more natural in JavaScript because of heritage
Fair enough, but I would always like to see code instead of just arguments. I made a decision to write something simple but useful, and to compare the end results. One of the simplest things that came to my mind at that moment was Singleton. Why Singleton one might ask? Because it is widely used in various forms in a large number of projects to deal with databases, configuration, logging, and some other services. As a result of this discussion, I came up with three popular solutions in Javascript. Let's compare them.
The simplest function-based implementation of Singleton pattern is just to use a function:
function Singleton() {
'use strict';
if (Singleton._instance) {
return Singleton._instance
}
Singleton._instance = this;
Singleton.getInstance = function () {
return this._instance;
};
this.present = function () {
console.log(`I am ${typeof this}`);
}
}
const instance = new Singleton();
console.log(Singleton._instance) // it does not restrict the access to the property
const another = Singleton.getInstance();
console.log(instance === another); // true
Everything works fine. But, you have to take into account the current value of this
and the access to the private property is not restricted. Let's try a more advanced implementation of Singleton which is based on IIFE (Immediately Invoked Function Expression) pattern.
const Singleton = (function () {
let instance;
function createInstance() {
return {
present() {
console.log(`I am ${typeof instance}`);
}
};
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();
const instance = Singleton.getInstance();
console.log(Singleton.instance) // it does restrict the access to the property (undefined)
const another = Singleton.getInstance();
console.log(instance === another); // true
This implementation restricts the access. But, it does not have the instantiation phase. The Singleton object is not created explicitly, it is obtained from the return by calling the getInstance()
method on it. Let's try the class-based implementation:
class Singleton {
static #instance; // possible since ES2020
constructor() {
if (!Singleton.#instance) {
Singleton.#instance = this;
}
return Singleton.#instance;
}
static getInstance() {
return this.#instance;
}
present() {
console.log(`I am ${typeof this}`);
}
}
const instance = new Singleton();
//console.log(Singleton.#instance) // it does restrict on the language level (SyntaxError)
const another = Singleton.getInstance();
console.log(instance === another); // true
Look how strict and clean it is. We don't need to think about the current value of this
, we don't need to think about the strictness. We just have everything isolated without even thinking about the outside context. But what even more important to me is the readability. The code that is responsible for the Singleton's behavior is separated from the business code. In addition, it is more comprehensible and obvious because the static and dynamic behavior, as well as visibility, are described explicitly.
Now, there is enough information to go through all the arguments and discuss them:
- functions are more concise and readable < in some cases definitely yes, but not in this one
- classes are just a wrapper over functions (syntactic sugar) < this may be true, but they also provide some mechanisms that are missing in the functions (e.g. visibility)
- classes are useful only when you need to do something and keep the result (which is pretty rare) < not necessarily
- functions are more natural in JavaScript because of inheritage < yes, if you work on a legacy code base, there's no other option but to use functions
In conclusion, I know that I’m more used to object-oriented code. That’s why I wanted to conduct this experiment and examine every argument before drawing any final conclusions. In this exact case, using classes has more advantages than disadvantages.
P.S. Probably, there are better ways to implement the Singleton pattern through functions in JavaScript. All the examples that I saw during conducting this experiment were the variations of the above examples. If you know any, let me please know.
Comment this page: