DMarlon Studio

Lesson 3 · 3 of 20

Prototypes and the chain · JavaScript

See how JavaScript inheritance really works: prototypes, Object.create, constructor functions and the chain that ends in null.

JavaScript · Prototypes and the chain in DMarlon Studio: the example's code and its output in the terminal

In JavaScript every object has a hidden link to another object: its prototype. When you ask for a property the object does not have, the language looks in the prototype, then in that object's prototype, and so on. That lookup path is the prototype chain.

Object.create(product) creates laptop with product as its prototype. laptop only has name and price; describe is not its own (Object.hasOwn returns false), but it is found by walking up the chain. Inside describe, this is still laptop, so it prints the laptop's name and price.

Before classes existed, people used constructor functions. new User(...) creates an empty object, sets User.prototype as its prototype and runs the function with this pointing at it. Methods are added to User.prototype, so every user shares the same function: ana.greet === luis.greet is true, and it is not duplicated in memory.

If an object defines a property with the same name, it shadows the prototype's one: luis.greet has its own version while ana keeps using the shared one. The final loop walks the chain with Object.getPrototypeOf until it reaches null. Object.prototype is where methods such as toString and hasOwnProperty, available on every object, come from.

Code · main.js

const product = {  describe() {    return `${this.name} - ${this.price.toFixed(2)} EUR`;  },};// laptop's prototype is product, so it inherits describe().const laptop = Object.create(product);laptop.name = "Laptop";laptop.price = 899;console.log(laptop.describe());console.log("Own describe?", Object.hasOwn(laptop, "describe"));console.log("Prototype is product?", Object.getPrototypeOf(laptop) === product);// Constructor functions share methods through .prototype.function User(name, role) {  this.name = name;  this.role = role;}User.prototype.greet = function () {  return `Hi, I am ${this.name} (${this.role})`;};const ana = new User("Ana", "admin");const luis = new User("Luis", "editor");console.log(ana.greet());console.log("Same function in both?", ana.greet === luis.greet);luis.greet = () => "Luis says hello his own way"; // own property shadows the prototypeconsole.log(luis.greet(), "|", ana.greet());// The chain ends in Object.prototype, then null.const names = new Map([[User.prototype, "User.prototype"], [Object.prototype, "Object.prototype"]]);const chain = [];for (let p = Object.getPrototypeOf(ana); p; p = Object.getPrototypeOf(p)) chain.push(names.get(p));console.log("ana ->", chain.join(" -> "), "-> null");

Output · node main.js

Laptop - 899.00 EUR
Own describe? false
Prototype is product? true
Hi, I am Ana (admin)
Same function in both? true
Luis says hello his own way | Hi, I am Ana (admin)
ana -> User.prototype -> Object.prototype -> null

Key points

  • Every object has a prototype, and missing properties are looked up there.
  • Object.create(proto) creates an object with the prototype you choose.
  • Methods on Constructor.prototype are shared by all instances.
  • An own property shadows a prototype property with the same name.
  • The chain ends at Object.prototype and then null.

Exercise

Add an isAdmin() method to User.prototype that returns true when role is admin, and use Object.hasOwn to check that the method lives on the prototype, not on ana.