articles / Objects and Classes in JavaScript: Prototypes and Composition

Objects and Classes in JavaScript: Prototypes and Composition

Objects and Classes in JavaScript: Prototypes and Composition

JavaScript has had object-oriented features since the start, but they do not work the way they do in Java or C++. There are no classes at the core of the language, only objects that link to other objects. The class keyword added later is convenient syntax over that older model. Knowing what sits underneath makes the syntax predictable instead of surprising.

Prototypes, briefly

Every object has a hidden link to another object called its prototype. When a property is read and the object does not have it, the lookup continues along that chain until it is found or the chain ends. This is how shared behavior works: methods live on a prototype, and many objects link to the same one rather than each carrying its own copy.

const animal = {
  describe() {
    return `a ${this.kind}`;
  }
};

const dog = Object.create(animal);
dog.kind = 'dog';
console.log(dog.describe()); // "a dog"

Here dog has no describe method of its own. The lookup walks up to animal and finds it there, and this still refers to dog.

The class syntax

The class keyword packages a constructor and its methods in one familiar block. Behind the scenes it still creates a function and attaches the methods to a prototype.

class Timer {
  constructor(label) {
    this.label = label;
    this.start = Date.now();
  }
  elapsed() {
    return Date.now() - this.start;
  }
}

const t = new Timer('load');

Classes support inheritance with extends and super, private fields with a # prefix, static members, and getters. The syntax is clear and worth using. The caution is about how deep the inheritance goes.

A subclass is permanently coupled to its parent. Three or four levels deep, a small change near the top can ripple through every descendant in ways that are hard to predict.

Composition over inheritance

Deep inheritance hierarchies were a common source of pain in older codebases. The usual alternative is composition: build behavior from small, independent pieces and assemble them, rather than inheriting a tall tree.

const canFly = (state) => ({
  fly: () => `${state.name} takes off`
});
const canSwim = (state) => ({
  swim: () => `${state.name} dives`
});

function createDuck(name) {
  const state = { name };
  return { ...canFly(state), ...canSwim(state) };
}

const duck = createDuck('Mallard');
duck.fly();
duck.swim();

Each capability is a small function that returns the methods it provides. The object is assembled from exactly the pieces it needs, with no fixed hierarchy to maintain. Adding or removing a capability is a local change.

The trouble with this

The keyword this does not refer to the object a method lives on; it refers to how the method was called. Pull a method off an object and pass it as a callback, and this can quietly become undefined. This catches people coming from class-based languages where the binding is fixed.

const counter = {
  count: 0,
  increment() { this.count++; }
};

const fn = counter.increment;
// fn();        // throws: `this` is not counter

const bound = counter.increment.bind(counter);
bound();        // works: this is pinned to counter

Arrow functions help because they do not have their own this; they use the value from the scope where they were defined. That is why arrow functions are a good fit for callbacks inside a method, and a poor fit for object methods that need to refer to the object itself.

Choosing an approach

Use a class when modeling something with a clear identity and a stable set of methods, such as a value object or a small service. Reach for composition when behavior is shared across unrelated things or when a hierarchy starts to feel forced. A practical habit is to start with plain objects and functions, and introduce a class only once the same shape is being created in several places. Both styles are first-class in JavaScript, and most real codebases use a mix rather than committing to one.

Common questions

Is JavaScript a class-based language?

Not at its core. JavaScript is prototype-based: objects link to other objects. The class keyword is convenient syntax that creates a constructor function and a prototype behind the scenes.

What are private fields in a class?

Fields whose names start with a # are private to the class body. They cannot be read or written from outside the class, which makes internal state genuinely encapsulated.

When should I prefer composition over inheritance?

Prefer composition when behavior is shared across unrelated types or when an inheritance tree grows beyond two or three levels. Composition keeps changes local and avoids tight coupling to a parent class.

What does Object.create do?

It creates a new object whose prototype is the object you pass in. Property lookups that miss on the new object continue along that prototype link.