- Explain how a property lookup walks the prototype chain up to
Object.prototypeandnull - Create objects with
Object.createand with constructor functions called withnew - Show that
classis built on prototypes and tell own properties from inherited ones
Every array has a map method and every string has toUpperCase — yet you never wrote these methods into your arrays. Where do they come from? In JavaScript every object has a hidden link to another object — its prototype. When a property is not found on the object itself, JavaScript looks at the prototype. Built-in methods, inheritance and classes are all built on this simple mechanism.
The prototype chain
You can read an object's prototype with Object.getPrototypeOf(obj) (the old __proto__ accessor also works but is not recommended). When obj.x is read, JavaScript first checks the object's own properties, then its prototype, then the prototype's prototype and so on — until it reaches null. If the property is nowhere, the result is undefined. Writing works differently: obj.x = ... always creates or changes an own property and never touches the prototype.
const animal = {
eats: true,
describe() {
return `${this.name} eats: ${this.eats}`;
},
};
const cat = Object.create(animal);
cat.name = 'Tom';
console.log(cat.describe());
console.log(Object.getPrototypeOf(cat) === animal);
console.log(Object.hasOwn(cat, 'name'), Object.hasOwn(cat, 'eats'));
cat.eats = false;
console.log(cat.eats, animal.eats);
console.log(Object.getPrototypeOf(animal) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));▸ Expected output
Tom eats: true true true false false true true null
cat.eats = false created an own property that “shadows” the inherited one — animal did not change. The chain: cat → animal → Object.prototype → null.Built-in objects work the same way. An array's chain is: the array → Array.prototype → Object.prototype → null. map lives not in the array itself but in Array.prototype. The in operator checks the whole chain, while Object.hasOwn checks only own properties. Array.prototype has its own toString, which shadows Object.prototype.toString.
const numbers = [1, 2, 3];
console.log(Object.getPrototypeOf(numbers) === Array.prototype);
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype);
console.log(Object.hasOwn(numbers, 'map'), 'map' in numbers);
console.log(numbers.toString === Object.prototype.toString);
console.log(numbers.toString());▸ Expected output
true true false true false 1,2,3
Constructor functions and new
Before class existed, objects with the same shape were created with constructor functions. When you call any regular function with new, JavaScript takes four steps:
- creates an empty object;
- sets its prototype to
Fn.prototype; - runs the function with
thisset to the new object; - returns the new object, unless the function returns another object.
Methods are put on Fn.prototype: then all instances share one copy of each method instead of every object carrying its own. In a program with a thousand objects this saves real memory.
function Student(name, grade) {
this.name = name;
this.grade = grade;
}
Student.prototype.introduce = function () {
return `${this.name}, grade ${this.grade}`;
};
const leyla = new Student('Leyla', 9);
const elvin = new Student('Elvin', 10);
console.log(leyla.introduce(), '|', elvin.introduce());
console.log(leyla.introduce === elvin.introduce);
console.log(Object.getPrototypeOf(leyla) === Student.prototype);
console.log(leyla);▸ Expected output
Leyla, grade 9 | Elvin, grade 10
true
true
Student { name: 'Leyla', grade: 9 }Classes are syntactic sugar
class (ES2015) lets you write the same structure more readably: a class is really a function, its methods land on ClassName.prototype, and extends links the prototypes together. There are differences too: class code is always in strict mode, a class cannot be called without new, methods don't show up in for...in, and #private fields exist only in classes.
class Shape {
constructor(name) { this.name = name; }
describe() { return `I am a ${this.name}`; }
}
class Circle extends Shape {
constructor(radius) {
super('circle');
this.radius = radius;
}
area() { return +(Math.PI * this.radius ** 2).toFixed(2); }
}
const c = new Circle(2);
console.log(typeof Shape, c.describe(), c.area());
console.log(Object.getPrototypeOf(Circle.prototype) === Shape.prototype);
console.log(Object.getOwnPropertyNames(Circle.prototype));
console.log(Object.hasOwn(c, 'area'), 'area' in c);
try {
Shape('square');
} catch (error) {
console.log(error.name);
}▸ Expected output
function I am a circle 12.57 true [ 'constructor', 'area' ] false true TypeError
| Class syntax | Behind the scenes |
|---|---|
class A { m() {} } | function A() {} + A.prototype.m = function () {} |
class B extends A {} | Object.setPrototypeOf(B.prototype, A.prototype) |
super.m() | A.prototype.m.call(this) |
static s() {} | A.s = function () {} |
extends also links B to A so that static methods are inherited.Use Object.create(vehicle) to create two objects: car (brand: 'Toyota') and bike (brand: 'BMX' and its own property wheels: 2). The describe() method must come from the prototype in both. The lines below print the result.
const vehicle = {
wheels: 4,
describe() {
return `${this.brand} has ${this.wheels} wheels`;
},
};
// create car and bike here
console.log(car.describe());
console.log(bike.describe());
console.log(Object.hasOwn(car, 'wheels'), Object.hasOwn(bike, 'wheels'));▸ Expected output
Toyota has 4 wheels BMX has 2 wheels false true
Add an increment() method to Counter.prototype: it increases value by 1 and returns this so calls can be chained. The method must be shared by all instances, not stored as an own property of each object.
function Counter(start) {
this.value = start;
}
// add increment() to Counter.prototype
const a = new Counter(5);
const b = new Counter(0);
a.increment().increment();
console.log(a.value, b.value);
console.log(a.increment === b.increment);
console.log(Object.hasOwn(a, 'increment'));▸ Expected output
7 0 true false
Key points
- Every object has a hidden link to a prototype; a missing property is searched along the chain up to
Object.prototypeand thennull. - Writing a property always creates an own property that shadows the inherited one; the prototype stays unchanged.
Object.create(proto)makes an object with the given prototype;new Fn()links the new object toFn.prototype.- Methods on the prototype are shared by all instances;
Object.hasOwnchecks own properties,inchecks the whole chain. classis built on prototypes and adds extras: strict mode, a requirednewand private fields.
Check yourself
10 questions. Every correct answer earns XP.
const a = { x: 1 }; const b = Object.create(a); b.x = 2;, what is a.x?