DEV Community

loading...

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

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
uriel29 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
steelvoltage 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
uriel29 profile image
Uriel dos Santos Souza

Familiarity Bias

Thread Thread
steelvoltage profile image
Brian Barbour

Everyone has a bias for what is familiar.

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