Module 4 · Objects, Classes & Data Structures ⏱ 19 min

Constructors & Prototypes

By the end of this lesson you will be able to:
  • Create many objects from one blueprint using a constructor function and the new operator
  • Share methods through the prototype chain instead of copying them onto every instance
  • Distinguish a value's own properties from the ones it inherits, and avoid the missing-new trap

Hand-writing one object literal is fine. Hand-writing a hundred that all need the same shape — the same fields, the same methods — is a recipe for typos and drift, because there is nothing enforcing that they match.

A constructor solves that. It is an ordinary function whose job is to initialise a fresh object, and you launch it with the new operator. Write the blueprint once, stamp out as many objects as you like, and each one is guaranteed to carry the same structure. Pair a constructor with a prototype and the methods are shared too — one copy in memory, not a hundred. This pair is the foundation objects were built on, and it is still exactly how class works under the hood.

flowchart LR
  N["new Dog('Rex')"] --> C["blank object created"]
  C -->|"this = that object"| R["constructor runs,<br/>sets this.name"]
  R --> O["object returned"]
  style O fill:#1e7f3d,color:#fff
new makes a blank object, runs the constructor with this bound to it, and returns the object.

A constructor and new

By convention a constructor is written with a capital first letterDog, Account, Timer — and that capital is a hint to the reader, not a language rule. The real magic is the keyword that starts the call:

function Account(owner) {
  this.owner = owner;     // 'this' is the brand-new object `new` just made
  this.balance = 0;
}

const a = new Account("Ada");

Four things happen on new Account("Ada"): a fresh empty object is created; that object is bound to this for the call; the constructor body runs and stamps properties onto it; finally the object is returned (unless the constructor explicitly returns its own object). Drop the new and none of that happens — a fact we will exploit as a bug shortly.

Each call to new stamps out an independent object with the same shape. Press Run.
function Account(owner) {
  this.owner = owner;
  this.balance = 0;
}

const a = new Account("Ada");
const b = new Account("Grace");
a.balance = 100;

console.log(a.owner, a.balance);
console.log(b.owner, b.balance);
console.log(a === b);

Prototypes: share the methods, not the copies

Put a method inside the constructor and every single instance gets its own private copy of that function — a thousand objects, a thousand function objects in memory, all doing the same thing. The prototype exists to stop that waste.

Every function automatically comes with a prototype object hanging off it. When you ask a.describe(), JavaScript first checks a itself; if describe is not an own property there, it follows a hidden link — the prototype chain — up to Account.prototype, and finds it there. So you store a method once on the prototype, and every instance reaches it through that chain. One copy, shared by all.

flowchart LR
  I["g1 instance<br/>own: name"] -->|"__proto__"| P["Greeter.prototype<br/>greet()"]
  P -->|"__proto__"| B["Object.prototype<br/>hasOwnProperty()"]
  I -.->|"greet not on g1?<br/>walk up"| P
  style P fill:#e0900b,color:#fff
An instance links to the constructor's prototype; a missing property is found by walking up that chain.
One greet on the prototype is shared by every instance — they are literally the same function.
function Greeter(name) {
  this.name = name;
}

Greeter.prototype.greet = function () {
  return `Hi, I'm ${this.name}`;
};

const g1 = new Greeter("Ada");
const g2 = new Greeter("Grace");

console.log(g1.greet());
console.log(g2.greet());
console.log(g1.greet === g2.greet);

Own versus inherited

Because methods live on the prototype, an instance has two kinds of properties: the own ones the constructor wrote onto it (like name), and the inherited ones it reaches through the chain (like greet). Object.keys(instance) lists only own properties, and instance.hasOwnProperty("name") returns true while instance.hasOwnProperty("greet") returns falsegreet exists, just not on the instance directly.

This distinction matters the moment you loop over an object or copy one: you almost never want to drag inherited methods along. Reading walks the whole chain, but ownership stops at the object itself. Keep that asymmetry in mind and prototype lookups will never surprise you.

flowchart TD
  Q{describe an own property?} -->|"no"| W["walk up the chain"]
  W --> F["found on Animal.prototype"]
  Q -->|"yes"| O["shown by Object.keys"]
  style F fill:#1e7f3d,color:#fff
  style O fill:#3776ab,color:#fff
Reading a property searches the whole chain; Object.keys and hasOwnProperty see own properties only.
describe is inherited; kind is own. Reading finds both; Object.keys lists only kind.
function Animal(kind) { this.kind = kind; }
Animal.prototype.describe = function () { return `I am a ${this.kind}`; };

const a = new Animal("cat");

console.log(a.describe());
console.log(Object.keys(a));
console.log(a.hasOwnProperty("describe"));
Exercise

What does this print? new builds an object and runs the constructor with this bound to it.

function Box(size) { this.size = size; }
const b = new Box(9);
console.log(b.size);
console.log(typeof b);
Exercise

Write a constructor Timer(label) that sets this.label to label and this.seconds to 0. Then add a tick method to Timer.prototype that adds 1 to this.seconds and returns the new value. Put tick on the prototype (not inside the constructor) so every instance shares one copy.

function Timer(label) {
  // set this.label and this.seconds = 0
}

// add tick() to Timer.prototype
Exercise

d is meant to be a Dog with a name, but the call forgets new — so Dog runs as a plain function, this is not an instance, and d ends up undefined. Add the single keyword that turns the call into instance creation.

function Dog(name) {
  this.name = name;
  this.speak = function () { return "Woof"; };
}

const d = Dog("Rex");
Exercise

What does this print? describe lives on the prototype; model is set directly on the instance. Remember hasOwnProperty reports own properties only.

function Robot(model) { this.model = model; }
Robot.prototype.describe = function () { return `Model ${this.model}`; };

const r = new Robot("X1");
console.log(r.describe());
console.log(r.hasOwnProperty("model"));
console.log(r.hasOwnProperty("describe"));
Exercise

What does new actually do when you write new Dog("Rex")?

Recap

  • A constructor is a function that initialises a new object; launch it with new so an object is made, bound to this, and returned.
  • Constructors are capitalised (Dog) as a reminder to always pair them with new.
  • Store shared methods on Constructor.prototype; instances reach them through the prototype chain, so there is one copy, not one per object.
  • Own properties (set on the instance) are listed by Object.keys and reported by hasOwnProperty; inherited ones are found by reading but are not own.
  • Calling a constructor without new binds this to the global and returns undefined — a silent, nasty bug.

Next you'll meet class syntax — a cleaner coat of paint over this exact constructor-plus-prototype machinery, with extends and super for inheritance built in.

Checkpoint quiz

Where should a shared method like greet live so that 1000 instances use one copy?

What goes wrong with const d = Dog("Rex") (no new)?

Go deeper — technical resources