Here's a small mystery. You create an empty array, const list = [], and you can immediately call list.push(), list.map() and list.includes(). But you never added those methods to list. Where do they come from? The answer is the prototype.
Every JavaScript object has a hidden link to another object called its prototype. When you read a property that the object doesn't have, JavaScript follows the link and looks on the prototype, then on the prototype's prototype, and so on. This series of links is the prototype chain. All arrays share one prototype, Array.prototype, which holds push, map and the rest, so a million arrays don't each need their own copy of every method.
This system is called prototypal inheritance, and it's how JavaScript does object-oriented programming. Even classes are built on top of it. In this lesson you'll inspect prototypes, follow the chain, create objects with Object.create, write the constructor functions older code uses, and see exactly what class and extends do behind the scenes.
The prototype chain
const animal = { breathes: true };
const dog = Object.create(animal); // dog's prototype is animal
dog.barks = true;
dog.barks; // true: found on dog itself
dog.breathes; // true: found on the prototype
dog.flies; // undefined: not found anywhere in the chainWhen you read dog.breathes, JavaScript checks dog, doesn't find it, then checks animal and finds it. The chain ends at Object.prototype, whose prototype is null. That's where methods every object has, such as toString and hasOwnProperty, live.
Writing is different from reading: dog.breathes = false creates a new property on dog itself and leaves animal untouched.
Inspecting prototypes
| Code | Gives |
|---|---|
Object.getPrototypeOf(obj) | The object's prototype (the modern way) |
obj.__proto__ | The same, an older accessor you'll see in code and DevTools |
Object.setPrototypeOf(obj, proto) | Changes the prototype (slow, rarely needed) |
Object.hasOwn(obj, "key") | true only if the property is on the object itself |
proto.isPrototypeOf(obj) | Is proto anywhere in obj's chain? |
obj instanceof Fn | Is Fn.prototype in obj's chain? |
In DevTools, expand any object and look for the Prototype entry to walk up its chain.
Constructor functions
Before class existed, objects were created with constructor functions and new:
function Pet(name) {
this.name = name; // own property, per object
}
Pet.prototype.speak = function () {
return `${this.name} makes a sound`; // shared by all pets
};
const rex = new Pet("Rex");new creates an empty object, sets its prototype to Pet.prototype, runs the function with this set to the new object, and returns it. Methods go on Pet.prototype so every pet shares one copy, while data such as name lives on each object.
Classes are prototypes underneath
class Pet {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class Dog extends Pet {
speak() { return `${this.name} barks`; }
}This does the same as the constructor version: speak is stored on Pet.prototype, and extends sets Dog.prototype's prototype to Pet.prototype. So a dog looks up methods on Dog.prototype first, then Pet.prototype, then Object.prototype. Classes are clearer to write, so use them in new code, but knowing the prototype underneath explains how instanceof, method overriding and super really work.
JavaScript Prototypes Syntax
Prototypes
const child = Object.create(parent);
Object.getPrototypeOf(child) === parent; // true
Object.hasOwn(child, "x");
function Car(make) { this.make = make; }
Car.prototype.drive = function () { return `${this.make} drives`; };
const c = new Car("Mini");
c instanceof Car; // trueJavaScript Prototypes Summary
- Every object has a prototype; missing properties are looked up along the prototype chain.
- Built-in methods like
pushandtoUpperCaselive on shared prototypes. Object.create(proto)makes an object with a chosen prototype.- Constructor functions put shared methods on
Fn.prototype;newlinks new objects to it. classandextendsare a clearer syntax built on prototypes.
JavaScript Prototypes Examples
Each example runs in the page. Edit the code and press Run to see what changes.
Following the prototype chain
const list = [3, 1, 2];
console.log(Object.hasOwn(list, "map"));
console.log(Object.getPrototypeOf(list) === Array.prototype);
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));
const base = { greet() { return `Hi, I'm ${this.name}`; } };
const ana = Object.create(base);
ana.name = "Ana";
console.log(ana.greet());
console.log("Own keys:", Object.keys(ana));
console.log(base.isPrototypeOf(ana));Line-by-line explanation
- Line 3
mapisn't on the array itself. - Line 4It comes from
Array.prototype, the array's prototype. - Lines 5 to 6The chain continues to
Object.prototypeand ends atnull. - Lines 8 to 10
anagetsgreetfrom its prototype, whilenameis her own. - Line 11Only own properties are listed by
Object.keys.
Constructor functions and shared methods
function Counter(start) {
this.value = start;
}
Counter.prototype.increment = function () {
this.value++;
return this.value;
};
const a = new Counter(0);
const b = new Counter(10);
a.increment();
console.log(a.value, b.increment());
console.log("Shared method:", a.increment === b.increment);
console.log("Own data:", Object.hasOwn(a, "value"), Object.hasOwn(a, "increment"));
console.log(a instanceof Counter, Object.getPrototypeOf(a) === Counter.prototype);Line-by-line explanation
- Line 2Each counter gets its own
value. - Lines 4 to 7
incrementis stored once on the prototype. - Line 14Both counters use the very same function.
- Line 15
valueis own data;incrementis inherited. - Line 16
instanceofchecks the prototype chain.
What class and extends really build
class Shape {
constructor(name) { this.name = name; }
describe() { return `${this.name} with area ${this.area().toFixed(1)}`; }
}
class Circle extends Shape {
constructor(r) { super("Circle"); this.r = r; }
area() { return Math.PI * this.r ** 2; }
}
const c = new Circle(2);
console.log(c.describe());
console.log(typeof Shape);
console.log(Object.getPrototypeOf(Circle.prototype) === Shape.prototype);
console.log(Object.hasOwn(Circle.prototype, "area"), Object.hasOwn(Shape.prototype, "describe"));Line-by-line explanation
- Line 3
describeis stored onShape.prototype. - Line 8
areais stored onCircle.prototype. - Line 12
describeis found by walking up fromCircle.prototypetoShape.prototype. - Line 13A class is really a special kind of function.
- Line 14
extendslinks the two prototypes together.
Common JavaScript Prototypes Mistakes
Methods inside the constructor
function User(name) {
this.name = name;
this.hello = function () { return "Hi " + this.name; }; // a new copy for every user
}function User(name) { this.name = name; }
User.prototype.hello = function () { return "Hi " + this.name; };Forgetting new
const u = User("Sam"); // this is not a new object; u is undefinedconst u = new User("Sam");Classes throw an error if you forget new, which is another reason to prefer them.
Using arrow functions on prototypes
User.prototype.hello = () => "Hi " + this.name; // this is not the userUser.prototype.hello = function () { return "Hi " + this.name; };JavaScript Prototypes in Real Projects
Prototypes in practice
Every built-in type, from arrays and strings to DOM elements, gets its methods from prototypes: a <button> element's chain runs through HTMLButtonElement, HTMLElement, Element and Node. Older libraries such as jQuery were built with constructor functions and prototypes. Polyfills add missing methods to prototypes in old browsers, and interview questions about this, new and inheritance all come back to the prototype chain.
JavaScript Prototypes Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Create cat with animal as its prototype.
const animal = { eats: true };
const cat = Object.(animal);
console.log(cat.eats);
Object.create(proto) sets the new object's prototype.What is logged?
const base = { colour: "red" };
const item = Object.create(base);
item.colour = "blue";
console.log(item.colour, base.colour, Object.keys(item).length);Where do array methods like map come from?
What does new do first when calling a constructor function?
Every Robot should share one greet method from the prototype, and greet should use the robot's own name. Fix it.
function Robot(name) {
this.name = name;
}
Robot.greet = () => `Beep, I am ${this.name}`;
const r1 = new Robot("R1");
const r2 = new Robot("R2");
console.log(r1.greet?.());- Robots can greet
- The method is shared
- The method lives on the prototype
JavaScript Prototypes Quiz
5 questions to check what you learned.
What is at the very end of a prototype chain?
- null
- window
- Array.prototype
- undefined
What does Object.getPrototypeOf(obj) return?
- A copy of obj
- The object obj inherits from
- obj's constructor function
- The keys of obj
What does class Dog extends Animal set up?
- Dog.prototype's prototype is Animal.prototype
- A copy of every Animal method on Dog
- A global Animal variable
- Nothing until super is called
Why put methods on the prototype rather than in the constructor?
- They run faster once
- Constructors can't contain functions
- It's required by new
- All objects share one copy instead of each getting its own
What does obj instanceof Fn actually check?
- Whether obj was created on the same line
- Whether obj has the same keys as Fn
- Whether Fn.prototype appears anywhere in obj's prototype chain
- Whether typeof obj is "Fn"
JavaScript Prototypes Challenge
Build an inheritance chain without class
Using Object.create:
- Create
vehiclewith a methoddescribe()returning`${this.name} has ${this.wheels} wheels` - Create
bikewithvehicleas its prototype andwheels: 2 - Create
myBikewithbikeas its prototype andname: "Roadster" myBike.describe()should return"Roadster has 2 wheels", and onlynameshould be an own property ofmyBike
- describe works through the chain
- The chain is myBike, bike, vehicle
- Only name is own