DEV Community

I Don't Use JavaScript Classes At All. Am I Missing Out on Something?

Nico Zerpa (he/him) on April 07, 2021

If you spend time reading about JavaScript and keeping up with the latest stuff online, you'll notice that many discussions revolve around classes....
Collapse
 
shadowtime2000 profile image
shadowtime2000

Honestly, I don't really see classes as having any use and kind of go against my principles that I try to use. I like having every data structure being pure or json serializable and classes just kind of mess that up with unnecessary logic with this and that (pun intended). I find it much simpler to just use pure functions to manipulate data structures.

Collapse
 
hlaiken profile image
Heath Aiken

IMO javascript is a great language because you can use it functionally, or in a basic (non-class) manner, pretty effectively.

However, for anyone who needs to write code (libraries, tools, components, etc.) that will be used by multiple programmers, classes become very important. In agile shops, you will find that you might be able to whip out some pretty slick (non-class-based) code, but it's unlikely to survive a thorough code review process.

Classes help to define strict interfaces that are easy to read and use. Using the class based get and set keywords, you can easily validate and sanitize class properties, as well as perform business logic.

Additionally, using a well-thought out class constructor can make it easy to clone an object instance.

Defining the toJSON method on a class also allows you to precisely control the serialization of your class as well.

Here is a contrived example:

class Car {
    constructor(car = null) {
        //can define non-public (implied) properties
        this._licensePlate = '';
        this._speed = 0;
        this._direction = 0;
        this._lightsOn = false;
        //take an instance of a Car as a constructor argument
        if(car) {
            for(let k in car) {
                if(!car.hasOwnProperty(k) || this[k] === undefined) continue;
                this[k] = car[k] || this[k];
            }
        }
    }
    //get & set to control the property values
    set speed(value) {
       let v = +value;
       if(isNaN(v)) {
           console.warn('property speed must typof number');
           return;
       }
       this._speed = v;
    }
    get speed() {
        return this._speed;
    }

    set direction(value) {
        let d = +value;
        if(isNaN(v) || (d < 0 || d > 360)) {
            console.warn('property direction must be typeof number between 0 and 360');
            return;
        }
        this._direction = d;
    }
    get direction() {
        return this._direction;
    }

    set lightsOn(value) {
        this._lightsOn = !(!value || value === "false");
    }
    get lightsOn() {
        return this._lightsOn;
    }

    set licensePlate(value) {
        //sanitize the input to remove illegal characters
        this._licensePlate = (value || '').replace(/[^a-zA-Z0-9]/g,'');
    }
    get licensePlate() {
        //guarantee uniformity in license plates
        return this._licensePlate.toUpperCase();
    }

    //computed property. This is not serialized
    get isSpeeding() {
        return (this.speed > 55);
    }

    toggleLights() {
        this._lightsOn = !this._lightsOn;
    }

    //JSON.stringify will automatically use this function to serialize the class
    toJSON() {
        return {
            licensePlate : this.licensePlate,
            speed : this.speed,
            direction : this.direction,
            lightsOn : this.lightsOn
        };
    }

    //super cheap way to clone an object
    copy() {
        return new Car(this.toJSON);
    }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
aminnairi profile image
Amin • Edited

You can also create a function to keep track of your context and disminish the amount of side-effects using a more functional approach.

const createPositiveIntegerFrom = something => Math.abs((Number(something) || 0) | 0);

const Motorcycle = (brand = "Unknown", model = "Unknown", year = new Date().getFullYear(), mileage = 0, owners = 0, price = 0) => ({
  setBrand: newBrand => Motorcycle(String(newBrand), model, year, mileage, owners, price),
  setModel: newModel => Motorcycle(brand, String(newModel), year, mileage, owners, price),
  setYear: newYear => Motorcycle(brand, model, createPositiveIntegerFrom(newYear), mileage, owners, price),
  setMileage: newMileage => Motorcycle(brand, model, year, createPositiveIntegerFrom(newMileage), owners, price),
  setOwners: newOwners => Motorcycle(brand, model, year, mileage, createPositiveIntegerFrom(newOwners), price),
  setPrice: newPrice => Motorcycle(brand, model, year, mileage, owners, createPositiveIntegerFrom(newPrice)),
  brand,
  model,
  year,
  mileage,
  owners,
  price,
  isBrandNew: year === new Date().getFullYear() && mileage === 0 && owners === 0,
  isFirstHand: owners <= 1,
  needsServicing: [10000, 20000, 30000, 40000, 50000, 60000].includes(mileage),
  json: ({brand, model, year, mileage, owners, price})
});

let streetTriple = Motorcycle("Triumph", "Street Triple 765 R").setPrice(15000);

console.log(streetTriple.price);            // 15000
console.log(streetTriple.isBrandNew);       // true
console.log(streetTriple.isFirstHand);      // true
console.log(streetTriple.needsServicing);   // false

streetTriple = streetTriple.setOwners(1).setMileage(40000).setPrice(9500);

console.log(streetTriple.price);          // 9500, Yep... :(
console.log(streetTriple.isBrandNew);     // false
console.log(streetTriple.isFirstHand);    // true
console.log(streetTriple.needsServicing); // true

streetTriple = streetTriple.setOwners(2).setMileage(62000).setPrice(5500);

console.log(streetTriple.price);          // 5500 :')
console.log(streetTriple.isBrandNew);     // false
console.log(streetTriple.isFirstHand);    // false
console.log(streetTriple.needsServicing); // false

console.log(streetTriple.json);
// {brand: 'Triumph', model: 'Street Triple 765 R', year: 2021, mileage: 62000, owners: 2, price: 5500}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
bbarbour profile image
Brian Barbour

I feel like this is such a messy way to read and understand code, compared to just exporting a module with the functions/variables you want public.

Collapse
 
urielsouza29 profile image
Uriel dos Santos Souza

Familiarity Bias

Thread Thread
 
bbarbour profile image
Brian Barbour

Everyone has a bias for what is familiar.

Collapse
 
urielsouza29 profile image
Uriel dos Santos Souza

Model things based on what they do, rather than what they are.

Modelling your software according to what things do instead of what they are, gives you the benefits of classical inheritance without the downsides - the ability to reuse properties/behaviour while keeping your code adaptive to changing requirements.

Collapse
 
andi23rosca profile image
Andi Rosca

“classes are a poor mans clojure” I think it’s from the tao of programming book. But i agree with it in case of JS, since it doesn’t have classes in the same way C# or Java do, you can just use closures when you need to keep state and have functions work on it.

Collapse
 
sebring profile image
J. G. Sebring

I recall it goes

"objects are merely a poor man's closures"

but the key point to the statement is it can be reversed, hence

"closures merely a poor man's object"

Collapse
 
andi23rosca profile image
Andi Rosca

Yeah true. In some ways objects can be considered better since they have more functionality attached to them, but in JS to me at least they’re pretty much the same thing.

I prefer closures since JS doesn’t have private properties on classes but you can choose not to return things from a closure and keep them private in a sense.

Collapse
 
theoboldalex profile image
Alex Theobold

One of my favourite use cases for a JS class is for building a custom service response wrapper that ensures consistency in the way my APIs return JSON. For example, I might want to send along a success prop and a message with my data whereby I can pass custom messages back to the client from my API.

A very simple example...

class ServiceResponse {
  constructor(data = "", success = true, message = "") {
    this.data = data;
    this.success = success;
    this.message = message;
  }
}

module.exports = ServiceResponse;
Enter fullscreen mode Exit fullscreen mode

This allows me to return a default success value of true and an empty message on each call along with the data unless something goes wrong, in which case, I can return early and set the success and message values appropriately.

Collapse
 
dmail profile image
Damien Maillard

I don't use classes at all since several years and don't feel like I will ever need them.

Collapse
 
manonbox__ profile image
Ollie

I use classes when it makes sense from a readability point of view. Whenever I find myself writing a bunch of functions that are related, take in similar or same inputs (i.e. a bunch of related functions all depending on similar variables) then I think you can move this to a class.

This allows you to group your functions together, and reduce or eliminate the arguments they take, as they can use properties set by the constructor.

You can achieve exactly the same by not using a class. You can write lots of functions in a module, or simulate a class behaviour with a closure. You can always get the same result by not writing classes, but I would challenge if they are as readable as a simple class.

Collapse
 
darkle profile image
Coop

One particular place I find classes useful is when you want to create your own errors. In that case you can just extend the regular built in Error class and add properties to it. Then you can check for it with MyError instanceof Error.

Collapse
 
breno profile image
Breno Farias Fonseca

You may never need it, but I can't see any other solution to better understand business rules than to abstract everything into classes and use good object-oriented practices. Another important point is that functional programming is not simply creating partial applications and using them later, there are several practices that, in my view, are more complex than the design patterns related to OOP.

Collapse
 
urielsouza29 profile image
Uriel dos Santos Souza

Familiarity Bias

Collapse
 
taufik_nurrohman profile image
Taufik Nurrohman

The thing that’s a bit off with the class syntax is that we can actually create more methods on the class itself without extending it to another class:

class Foo {
    bar() {}
    baz() {}
}

Foo.prototype.qux = function() {};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
emil profile image
Emil

bigger applications need more encapsulation. If you still are able to do that with functions and modules go for it. But depending on your preferences classes can help you to structure your code which is hard to do only with functions. Sure JS is no OO language but classes and SOLID principles work well with it

Collapse
 
levimichael profile image
Levi Whalen

I think for someone like me who comes from the front end to the back end, the idea of classes felt strange to me. Why am I defining something that is getting passed to me from somewhere else? I think it makes more sense when you think about using JavaScript end to end. Defining a class in the api to be used from back to front really helps you understand why you’d use it. Throw in typescript and things get even more fun.

Collapse
 
__manucodes profile image
manu

Same here I don't use classes.
I use functions instead 🤔

Collapse
 
gwutama profile image
Galuh Utama

Honestly though, classes make UML diagrams straightforward, which are important for enterprise software documentation.

However, I‘m also okay with JS modules which consist only of functions. Describing them im UML diagrams are easy as well.

Collapse
 
ironcladdev profile image
Conner Ow

I don't really use JS classes either. I use OOP by making a constructor with a function.

function Unit(stats){
  this.stats = stats;
};
Unit.prototype.run = function(){
  //...
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
shadowtime2000 profile image
shadowtime2000

This is probably worse because it adds an unnecessary amount of code and is harder to understand at the start, especially with large classes.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
stojakovic99 profile image
Nikola Stojaković

Classes are just syntactic sugar on top of prototypes.