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
A constructor and new
By convention a constructor is written with a capital first letter — Dog, 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.
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
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 false — greet 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
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"));
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);
newmakes an object, runs Box with this = that object, and returns it.b.size is whatever the constructor set.
The type of any object, however it was made, is "object".
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
Inside the constructor: this.label = label; this.seconds = 0;
Attach the method with Timer.prototype.tick = function () { ... };
tick should do this.seconds += 1 and return this.seconds.
function Timer(label) {
this.label = label;
this.seconds = 0;
}
Timer.prototype.tick = function () {
this.seconds += 1;
return this.seconds;
};
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");
Without
new, the constructor returns undefined andthisis the global.Prefix the call with
newso an object is built and bound tothis.Constructors are capitalised precisely to remind you to use
new.
function Dog(name) {
this.name = name;
this.speak = function () { return "Woof"; };
}
const d = new Dog("Rex");
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"));
describe is found through the prototype chain, so the call works.
model was written onto the instance by the constructor, so it is own.
describe is inherited, so hasOwnProperty('describe') is false.
What does new actually do when you write new Dog("Rex")?
new makes a blank object, calls the constructor with this pointing at that object, and (unless the constructor returns its own object) hands the new object back to you.
Recap
- A constructor is a function that initialises a new object; launch it with
newso an object is made, bound tothis, and returned. - Constructors are capitalised (
Dog) as a reminder to always pair them withnew. - 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.keysand reported byhasOwnProperty; inherited ones are found by reading but are not own. - Calling a constructor without
newbindsthisto the global and returnsundefined— 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.