What ES6 Classes Don’t Offer: Key Limitations You Should Be Aware Of

Understanding the Limitations and Missing Features of ES6 Classes in JavaScript

Photo by Tra Nguyen on Unsplash

JavaScript now natively supports the class keyword, and it is not merely syntactic sugar; it represents a fundamental aspect of JavaScript’s functionality. For example, JavaScript does not support calling a superclass method directly with syntax like super.callMethodOfSuperClass(). Despite these advancements, JavaScript classes still lack certain features found in other object-oriented languages.

Today we will be seeing few of them.

Static Class ✋🏻

Static class is just like a regular class, the only difference is that we can’t instantiate it using new keyword.

Wait what? Why would I ever want to have a class like this if I can't instantiate it?

Well, there can be multiple use cases. One could be to have a utility class just to perform some checks like whether the string is empty. For such tasks, we don’t want to create an object. We can use it like this:

String.isEmpty(str);

Isn’t it awesome?

But In javascript, we can make a class field staic and method static . But we can’t have a static class. If we try to do so, we’ll get an error

Error in case of Static Keyword

Alternate Solution

class StaticString {
constructor() {
if (this instanceof StaticString) {
throw Error('You can not instantiate a static class');
}
}

static isEmpty(str) {}
}
If you try to use new, you will get error.

Interface 🖼️

An interface allows us to define rules or a contract that a class must adhere to.

interface IMovie {
movieName: string;
id: number;
getMovieInfo(): string; // Method in the interface
}

class IronMan implements IMovie {
movieName: string;
id: number;

constructor(movieName: string, id: number) {
this.movieName = movieName;
this.id = id;
}

getMovieInfo(): string {
return `${this.movieName} (ID: ${this.id})`;
}
}

const ironManMovie = new IronMan('Iron Man', 1);
console.log(ironManMovie.getMovieInfo()); // Outputs: "Iron Man (ID: 1)"

We can use interfaces in TypeScript, but they are not natively supported in JavaScript yet. However, we can achieve similar functionality using JSDoc annotations.

Here’s an example using a class with JSDoc:

/**
* @interface IMovie
* @property {string} movieName
* @property {number} id
* @method getMovieInfo
*/

/**
* @implements {IMovie}
*/
class IronMan {
constructor(movieName, id) {
this.movieName = movieName;
this.id = id;
}

/**
* @returns {string}
*/
getMovieInfo() {
return `${this.movieName} (ID: ${this.id})`;
}
}

const ironManMovie = new IronMan('Iron Man', 1);
console.log(ironManMovie.getMovieInfo()); // Outputs: Iron Man (ID: 1)

Abstract Class 🧑‍🏫

We use an abstract class to define a contract that a subclass must follow, ensuring that the subclass implements the required methods.

We can’t have an abstract class in JavaScript yet. But we can achieve kind of similar thing using additional code.

abstract class Animal {
abstract makeSound(): void; // Contract: subclasses must implement this method

move(): void {
console.log("The animal moves.");
}
}

class Dog extends Animal {
makeSound(): void {
console.log("The dog barks.");
}
}

const myDog = new Dog();
myDog.makeSound(); // Outputs: The dog barks.
myDog.move(); // Outputs: The animal moves.

But if you try to do it in Javascript, you will get an error.

error with abstract class

Alternative

class Animal {
constructor() {
if (new.target === Animal) {
throw new Error("Cannot instantiate abstract class Animal directly.");
}
}

makeSound() {
throw new Error("Method 'makeSound()' must be implemented.");
}

move() {
console.log("The animal moves.");
}
}

class Dog extends Animal {
makeSound() {
console.log("The dog barks.");
}
}

const myDog = new Dog();
myDog.makeSound(); // Outputs: The dog barks.
myDog.move(); // Outputs: The animal moves.

// Attempting to instantiate Animal directly will throw an error:
// const myAnimal = new Animal(); // Error: Cannot instantiate abstract class Animal directly.

Protected: Access Modifier 🕵️‍♂️

In javascript, we can have public and private class fields but, still we don’t have support for protected access modifier.

protected fields are not accessible inside the derived class.

Alternative:

For this, there is no direct alternative as data can be accessed always. But still, we can use two things.

  1. Naming convention — use _ to prefix a protected field (Just of convention, it doesn't enforce any rule)
  2. Use Symbols to create hidden fields.
const _name = Symbol('name');
const _makeSound = Symbol('makeSound');

class Animal {
constructor(name) {
this[_name] = name; // Protected-like property using Symbol
}

_makeSound() { // Protected-like method using underscore
throw new Error("Method '_makeSound()' must be implemented.");
}

getName() {
return this[_name];
}
}

class Dog extends Animal {
constructor(name) {
super(name);
}

_makeSound() { // Implementing the protected-like method
console.log(`${this[_name]} barks.`); // Accessing protected-like property
}

bark() {
this._makeSound(); // Using the protected-like method
}
}

const myDog = new Dog('Rex');
myDog.bark(); // Outputs: Rex barks.

Overloading 🚚

In JavaScript, we can’t have two functions with the same name.

JavaScript uses a single method definition with variable arguments and types.

function first() {
}

function first(num) {
} // this will override above one.

we can achieve this by having different parameters defined.

function first(a,b,c) {
if(!a && !b && !c) return 0

if(a && !b && !c) return 2

if(a && b && !c) return a + b;
}

first(); // returns 0

first(2)// returns two

first(2,3)// returns 5

Please let me know if there’s anything I’ve overlooked. 🫣

--

--