30 Days of JavaScript: Object-Oriented JavaScript, Day 14

30 Days of JavaScript: Building a Simple Weather App
30 Days of JavaScript: Object-Oriented JavaScript, Day 14

Learn Object-Oriented Programming (OOP) in JavaScript with ES6 enhancements, prototypal inheritance, and design patterns for cleaner, more modular code.

By now you’ve written a fair bit of JavaScript in this 30 Days of JavaScript series, and your files are probably starting to sprawl. Loose variables here, standalone functions there. Day 14 is where we tidy that up. Object-Oriented Programming (OOP) is really just a way to keep related data and behavior in one place, modeled after things you already understand: a car, a person, a dog.

We’ll cover how objects and classes work, what prototypal inheritance actually is under the hood, and a few patterns worth keeping in your toolkit. One honest thing up front: the class keyword looks like classes from other languages, but in JavaScript it’s syntactic sugar over prototypes. Learn what it’s hiding and you’ll debug faster.

Table of Contents

Understanding Objects and Classes

An object bundles data and the things you can do with that data. Two parts:

  • Properties: the data, the object’s characteristics (a car’s color or model).
  • Methods: the behavior, what the object can do (a car starting or stopping).
Creating Objects Using Literal Notation:
JS
/**
 * Creates a simple dog object with properties and a method.
 */
const dog = {
  name: 'Buddy',
  breed: 'Golden Retriever',
  bark: function() {
    console.log('Woof! Woof!');
  }
};

That’s fine for one dog. But when you need ten dogs, or ten users, writing the same shape by hand gets old. A class is the blueprint you stamp copies from.

Creating a Class:
JS
/**
 * Creates a class Person with a constructor and a method.
 *
 * @param {string} name - The person's name.
 * @param {number} age - The person's age.
 * @returns {void}
 */
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}
const john = new Person('John', 30);
john.greet(); // Output: Hello, my name is John

The constructor runs when you call new Person(...), and this refers to the fresh object being built. Watch out for that this: it points at whatever the method was called on, so if you ever pass greet around as a bare callback, this can go missing on you. That’s the number one class gotcha in JavaScript.

Private Members:

Sometimes a value should stay locked inside the class. Private class fields, marked with a #, do exactly that. Worth being precise about the timeline: these are not from ES6. Private fields landed in ES2022 and became widely available in browsers around mid-2021. Static methods (below) are the ES6 ones.

JS
/**
 * Defines a class Person with a private field.
 *
 * @param {string} secret - The person's secret.
 * @returns {void}
 */
class Person {
  #secret;
  constructor(secret) {
    this.#secret = secret;
  }
  revealSecret() {
    console.log(`The secret is ${this.#secret}`);
  }
}

The # is real privacy enforced by the language, not a naming convention. Reach for person.#secret from outside the class and it’s a syntax error. One catch: private fields aren’t part of the prototype chain, so subclasses don’t inherit them.

Prototypal Inheritance

Here’s the part the class keyword hides. In JavaScript, objects inherit from other objects through the prototype chain. Every object has a hidden internal link, [[Prototype]], pointing at another object. Ask an object for a property it doesn’t have, and JavaScript walks that chain until it finds one or runs out.

Using Prototypes:
JS
/**
 * Demonstrates prototypal inheritance using a constructor function.
 *
 * @param {string} name - The name of the animal.
 * @returns {void}
 */
function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function() {
  console.log(`${this.name} makes a noise`);
}
/**
 * Extends the Animal prototype with a Dog class.
 *
 * @param {string} name - The name of the dog.
 * @returns {void}
 */
class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks`);
  }
}
const dog = new Dog('Rex');
dog.speak(); // Output: Rex barks

Notice you can mix the two styles: Dog is a class that extends an old-school constructor function. That works because class and function constructors are the same prototype machinery under different syntax. When both define speak, the one closest to the object wins, so Dog‘s version shadows Animal‘s.

Inheritance is useful, but deep chains get brittle fast. Change a base class and you can break things three levels down without meaning to. That’s why a lot of developers reach for composition instead.

Composition over Inheritance:

Composition means building bigger objects out of smaller ones instead of stacking parent classes. It keeps pieces loosely coupled, so you can swap one behavior without disturbing the rest.

ES6 Enhancements and Object Composition

ES6 gave classes a lot of what they needed to feel complete. Static methods are one of the handier additions.

Static Methods:

A static method lives on the class itself, not on the instances. Good for helpers that don’t need any particular object’s data:

JS
/**
 * Demonstrates a static method on a class.
 *
 * @param {number} x - The first number.
 * @param {number} y - The second number.
 * @returns {number} The sum of x and y.
 */
class MathHelper {
  static add(x, y) {
    return x + y;
  }
}
console.log(MathHelper.add(5, 3)); // Output: 8

You call it as MathHelper.add(...), never on an instance. Try it on a new MathHelper() and it won’t be there.

Object Composition:

Back to composition, in practice. Rather than inheriting a stack of behavior you may not need, you combine small objects into the one you want. Fewer surprises, easier to reason about, and each piece stays reusable on its own.

Patterns and Best Practices

Factory Functions:

A factory function is just a function that builds and returns an object. No new, no class, and you get more say over how each object is shaped.

JS
/**
 * Factory function to create a person object.
 *
 * @param {string} name - The person's name.
 * @param {number} age - The person's age.
 * @returns {Object} A person object with a greet method.
 */
function createPerson(name, age) {
  return {
    name,
    age,
    greet() {
      console.log(`Hello, I'm ${this.name}`);
    }
  };
}
const person = createPerson('Alice', 25);
person.greet(); // Output: Hello, I'm Alice
Singleton Pattern:

A singleton guarantees one instance and one shared access point to it. Handy for something you want exactly one of, like a config store or a connection.

JS
/**
 * Singleton pattern to ensure only one instance of the class.
 *
 * @returns {Singleton} The singleton instance.
 */
class Singleton {
  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
}
Conclusion

Today we turned loose code into structured code. Objects bundle data with behavior, classes stamp out consistent copies, and modeling with everyday things like cars and people keeps the whole thing readable.

We also drew the line between what’s genuinely ES6 and what came later: static methods are ES6, private # fields are ES2022. And we looked under the class syntax at the prototype chain that actually powers inheritance, plus composition as the lighter-weight alternative when deep hierarchies start to hurt.

Factory functions and the singleton pattern round out your kit. Neither is fancy, but both solve real problems you’ll hit as projects grow. Use them when they fit, not because a tutorial said to.

The real takeaway: OOP in JavaScript isn’t about memorizing keywords. It’s knowing that class is a friendly face on prototypes, and that this will bite you if you’re not paying attention. Get comfortable with those two ideas and the rest gets a lot easier.

What's Next?

Day 15 is Unit Testing in JavaScript. You’ll write tests that catch breakage before your users do, which is what makes a codebase safe to keep changing. See you there.

Next: 30 Days of JavaScript: Unit Testing in JavaScript, Day 15

Leave a Comment

Your email address will not be published. Required fields are marked *


Scroll to Top