JavaScript: Object-Oriented Programming

Learn how to model real-world concepts with constructor functions, prototypes, and ES6 classes.

Constructor Functions

A constructor function is a regular function used as a blueprint to create multiple objects with the same structure. By convention, constructor function names start with an uppercase letter.

Constructor Function

A constructor function defines the shape of an object, its properties are set via this inside the function body.

  • Name starts with uppercase by convention: Person, not person
  • Properties are assigned with this.property = value inside the body
  • Must be called with the new keyword to work correctly
  • Methods placed on ConstructorName.prototype are shared across all instances

Constructor Function

Define a Person blueprint and share the greet method via the prototype.

The new Keyword

Calling a function with new triggers four automatic steps that turn it into an object factory.

What new Does

new automates the boilerplate of creating and returning an object from a constructor.

  • 1. Creates a brand-new empty object
  • 2. Sets the new object's prototype to Constructor.prototype
  • 3. Runs the constructor body with this pointing to the new object
  • 4. Returns the new object automatically (unless the constructor explicitly returns a different object)

new Keyword

Without new the constructor returns nothing and this is wrong.

Prototype Chain

Every JavaScript object has an internal link to another object called its prototype. When you access a property, JavaScript walks this chain of linked objects until it finds the property or reaches null.

Prototype Chain

The prototype chain is how JavaScript implements inheritance, shared properties live higher up the chain.

  • Property lookup order: own properties first, then prototype, then prototype's prototype, and so on
  • The chain ends at Object.prototype, whose prototype is null
  • Methods like toString() and hasOwnProperty() are inherited from Object.prototype

Prototype Chain Lookup

speak lives on Animal.prototype, not on dog, found via the chain.

__proto__ vs prototype

These two look similar but serve different purposes: prototype is a property on constructor functions; __proto__ is the actual internal link on every object instance.

prototype vs __proto__

prototype is the template; __proto__ is the live link used during property lookup.

  • Function.prototype: the object that new instances will inherit from
  • instance.__proto__: points to the constructor's prototype object (same reference)
  • Use Object.getPrototypeOf(obj) instead of __proto__ in production code
  • __proto__ is a legacy accessor, avoid mutating it directly

prototype vs __proto__

Dog.prototype and rex.__proto__ point to the exact same object.

Prototypal Inheritance

One constructor can inherit from another by linking its prototype to an instance of the parent, so instances of the child share the parent's methods.

Prototypal Inheritance

Linking prototypes lets child constructors reuse and extend parent behaviour without copying code.

  • Use Object.create(Parent.prototype) to set up the chain correctly
  • Call the parent constructor with Parent.call(this, ...args) to copy own properties
  • Reset Child.prototype.constructor back to Child after linking
  • ES6 extends does all of this automatically, prefer it for new code

Prototypal Inheritance

Dog inherits speak from Animal and adds its own bark method.

ES6 Class Declaration

The class keyword is syntactic sugar over the prototype-based system. It provides a cleaner, more readable way to define constructor functions and their prototype methods.

class Declaration

Classes are the modern, recommended way to write object-oriented JavaScript.

  • A class body is always in strict mode
  • Classes are NOT hoisted like function declarations, define before use
  • Methods defined inside the class body are placed on the prototype automatically
  • Under the hood, a class is still a function: typeof Person === "function"

Class Declaration

The class syntax is cleaner but produces the same prototype structure underneath.

Static Methods

A static method belongs to the class itself, not to instances. Call it directly on the class name.

static Methods

Use static methods for utility functions that relate to the class but do not need instance data.

  • Called as ClassName.method(), not instance.method()
  • this inside a static method refers to the class, not an instance
  • Common uses: factory helpers, validators, comparison functions

Static Method

fromFahrenheit is a factory that creates a Temperature without exposing the math.

Getters and Setters

Getters and setters let you define properties that run logic when read or written, while looking like plain property access to the caller.

get / set

Getters and setters add validation or computed properties behind a simple property syntax.

  • get propName(): runs when the property is read
  • set propName(value): runs when the property is assigned
  • Setters are ideal for validating incoming values before storing them
  • A getter with no matching setter makes the property effectively read-only

Getters and Setters

area is computed on demand; the radius setter rejects negative values.

Inheritance with extends

The extends keyword creates a subclass that inherits all methods from the parent class and can add or override them.

extends

extends sets up the prototype chain automatically, replacing the manual Object.create pattern.

  • The subclass inherits all instance and static methods from the parent
  • Override a method by redefining it in the subclass body
  • Call the parent's version of a method with super.methodName()
  • A subclass constructor MUST call super() before using this

extends and Method Override

Dog overrides speak but still passes instanceof Animal checks.

super Keyword

super has two uses: calling the parent constructor (super()) and calling a parent method (super.method()).

super

super lets a subclass reuse the parent's constructor and methods without duplicating code.

  • super(args) in a constructor: calls the parent constructor
  • super.method() in a method: calls the parent's version of that method
  • Calling super() is mandatory in a subclass constructor, omitting it throws a ReferenceError
  • You can call super.method() even when you override the method, to extend rather than replace it

super in Constructor and Method

ElectricCar builds on Vehicle's describe() using super.describe().

Encapsulation and Private Fields

Encapsulation means hiding internal state and only exposing what the outside world needs. ES2022 introduced true private fields with the # prefix.

Private Fields (#)

Fields prefixed with # are strictly private, accessing them from outside the class throws a SyntaxError.

  • Declare at the top of the class body: #fieldName;
  • Only methods inside the same class can read or write them
  • Unlike the _convention, the # prefix is enforced by the language itself
  • Private methods are also supported: #methodName() { }

Private Fields

#balance is enforced private, only accessible through the public getter and deposit method.

PatternSyntaxTruly Private?Notes
Underscore convention_fieldNoJust a naming hint, still accessible
Closure variablelet field in IIFEYesWorks pre-ES2022, but awkward with classes
Private class field#fieldYesLanguage-enforced, modern and preferred

Knowledge Check

1. What does the new keyword do when used with a constructor function?

2. Where do shared methods belong in the constructor function pattern?

3. What is the prototype chain?

4. What does the constructor method in a class do?

5. How is a static method different from an instance method?

6. What must you call inside a subclass constructor before accessing this?

7. How do you declare a private field in an ES2022 class?