Objects and Prototypes in JavaScript

The Story

DesiEats starts with one restaurant object. Soon Priya adds two more restaurants. All restaurants need a name, a city, and an open() action.

Ananya can copy the open function into every object. That works, but it creates repeated functions and makes updates harder. Rahul shows her another JavaScript idea: objects can look for missing properties in a shared object called a prototype.

The restaurant objects keep their own data. They share common behaviour through one prototype. It is like three restaurant branches using one shared operations handbook while keeping their own menus and names.

What You Will Learn

  • How JavaScript objects store properties
  • How property lookup moves through the prototype chain
  • How Object.create builds an object with a chosen prototype
  • How constructor functions and .prototype work
  • The difference between own properties and inherited properties
  • Why direct prototype mutation and __proto__ should be avoided

The Big Idea

An object is a collection of properties. A property can hold data or a function.

Every ordinary JavaScript object also has an internal link to another object called its prototype. When JavaScript cannot find a property directly on the object, it checks the prototype, then that prototype's prototype, and so on. This sequence is the prototype chain.

Think of asking a restaurant worker for a recipe. The branch's own recipe book is checked first. If the recipe is not there, the worker checks the shared company handbook. If it is still missing, the search continues to the head office handbook.

Objects Store Data and Behaviour

You can create an object with an object literal. A property is a named value, and a method is a function stored on an object.

What just happened?

  • name and city are data properties.
  • open is a method.
  • this refers to the object before the dot at call time: restaurant.
  • The object owns these properties directly.

An object literal is a good choice when you need one object and do not need a reusable object-creation pattern.

Property Lookup Uses the Prototype Chain

An object can read a property from its prototype even when it does not own that property.

What just happened?

  • Object.create(restaurantRules) creates an object whose prototype is restaurantRules.
  • name is found directly on restaurant.
  • acceptsOrders is not found directly, so JavaScript finds it on restaurantRules.
  • Object.hasOwn checks only the object itself. It does not count inherited properties.
  • Inside the method, this is still the object used to call the method.

What Is the Prototype Chain?

The prototype chain is a linked lookup path. JavaScript follows it when reading a property.

For the previous example, the path is approximately:

restaurant
    |
    v
restaurantRules
    |
    v
Object.prototype
    |
    v
null

Object.prototype is a common ancestor for ordinary objects. It provides methods such as toString. The chain ends at null, meaning there is nowhere else to search.

You can inspect the prototype with Object.getPrototypeOf.

What just happened?

menu delegates missing property lookups to menuRules. menuRules is itself an ordinary object, so its prototype is Object.prototype.

The word delegation means allowing another object to answer a request on your behalf. Prototype inheritance is mainly delegation: the object does not copy the method; it looks for the method elsewhere.

Own Properties and Inherited Properties

An own property belongs directly to an object. An inherited property comes from somewhere on its prototype chain.

This difference matters when you loop over data or decide whether an object contains a value itself.

What just happened?

  • Object.hasOwn(branch, "name") is true because name belongs to branch.
  • category is inherited, so Object.hasOwn returns false.
  • The in operator checks the whole prototype chain, so it returns true.
  • Object.keys lists enumerable own properties, not inherited ones.
Use Object.hasOwn(value, key) when you need to know whether the object itself owns a property. Do not use value.hasOwnProperty(key) blindly because an object can have its own property with that name or a null prototype.

Creating Shared Behaviour with Constructor Functions

Before class syntax, JavaScript commonly used constructor functions. A constructor function is a normal function intended to create objects with new.

The function's .prototype object is used as the prototype of objects created with that constructor.

What just happened?

  1. new Restaurant(...) creates a new empty object.
  2. That object's prototype is set to Restaurant.prototype.
  3. The constructor runs with this pointing to the new object.
  4. name and city become own properties.
  5. describe is found on the shared prototype.
  6. Both objects use the same function instead of storing separate copies.

The .prototype property on a constructor function is different from the internal prototype link of each created object. The constructor's .prototype is the object used as the shared delegation target.

new and Constructor Functions

The new keyword performs important steps. It creates an object, connects it to the constructor's prototype, calls the constructor with that object as this, and returns the object unless the constructor explicitly returns another object.

A class uses this same prototype-based system under the hood. Class syntax is usually easier to read, but it does not replace prototypes with a different object model.

Changing a Property Can Shadow an Inherited One

If an object writes a property with the same name as an inherited property, the new own property takes priority. This is called shadowing.

The restaurant now has its own special property. The shared default is still unchanged. This is useful for defaults, but it can be confusing if code assumes it is changing the shared prototype.

Real-World Example: Restaurant Objects

Here is a small DesiEats model using a constructor function and a shared prototype. Each branch stores its own data, while all branches share behaviour.

The summary function is stored once on Branch.prototype. The rating and location remain different for each object. This is the practical value of prototypes: own data plus shared behaviour.

Classes Still Use Prototypes

The class version looks different, but its normal methods are still shared through the prototype.

This connects the two topics: classes are modern syntax, while prototypes are the mechanism used to share ordinary class methods.

Common Mistakes and Gotchas

1. Confusing .prototype with an object's prototype

function Menu() {}
const menu = new Menu();

// Wrong idea: these are the same property on the same value.
console.log(Menu.prototype === menu.prototype); // -> false

// Fixed: inspect the object's internal prototype.
console.log(Object.getPrototypeOf(menu) === Menu.prototype); // -> true

Menu.prototype belongs to the constructor function. Object.getPrototypeOf(menu) returns the prototype linked to the created object.

2. Defining methods inside the constructor unnecessarily

function Order(number) {
  this.number = number;

  // Wrong for shared behaviour: a new function is created per order.
  this.summary = function () {
    return `Order #${this.number}`;
  };
}

Put shared methods on Order.prototype so every order can reuse one function.

3. Using for...in without checking ownership

const defaults = { category: "food" };
const item = Object.create(defaults);
item.name = "Dosa";

// Risky: inherited enumerable properties can appear.
for (const key in item) {
  console.log(key);
}

// Fixed: check ownership before using the key.
for (const key in item) {
  if (Object.hasOwn(item, key)) console.log(key);
}

Use Object.keys when you want own enumerable keys directly.

4. Mutating a shared prototype at runtime

Changing a prototype after many objects have been created can make behaviour difficult to trace. Define shared methods in one place before creating objects when possible.

5. Using __proto__ in new code

__proto__ is a legacy accessor. Prefer Object.create, Object.getPrototypeOf, and Object.setPrototypeOf when you truly need prototype operations. In most application code, create the right prototype once rather than changing it later.

Code Review Corner

Review this code before revealing the answers:

function Delivery(name) {
  this.name = name;
  this.describe = () => `${this.name} delivery`;
}

Delivery.prototype.cancel = () => `${this.name} cancelled`;

const first = new Delivery("Lunch");
const second = new Delivery("Dinner");

console.log(first.describe === second.describe);
console.log(first.cancel());
Reveal the review

There are two issues:

  1. describe is created separately for every instance. Put it on Delivery.prototype as a normal function to share it.
  2. The arrow function assigned to cancel does not get a dynamic this. Its this is captured from the surrounding scope, not from first. Use a normal function:
Delivery.prototype.cancel = function () {
  return `${this.name} cancelled`;
};

Then first.cancel() uses first as this.

Interview Questions

What is a prototype?

A prototype is an object that another object can use as a fallback during property lookup. It provides delegation and shared behaviour.

What is the prototype chain?

It is the sequence of prototype links JavaScript follows when a property is not found directly on an object. The chain eventually ends at null.

What is the output?

const parent = { role: "manager" };
const child = Object.create(parent);
child.name = "Ananya";

console.log(child.role);
console.log(Object.hasOwn(child, "role"));

Answer: manager, then false. The value is inherited, not owned by child.

Why are methods often placed on a prototype?

All instances can reuse one function instead of storing a separate function on every object. This reduces duplication and expresses shared behaviour.

A class is syntax built on JavaScript's prototype system. Normal class methods are placed on the class prototype and are shared by instances.

What does new do?

It creates an object, links it to the constructor's prototype, calls the constructor with that object as this, and returns the new object.

Practice Challenges

Easy: Shared restaurant method

Create two restaurant objects with a shared describe method using Object.create.

const restaurantRules = {
  describe() {
    // Return a sentence using this.name and this.city.
  }
};

const restaurant = Object.create(restaurantRules);
restaurant.name = "Priya's Kitchen";
restaurant.city = "Lucknow";
console.log(restaurant.describe());

Hint: The method should be found on restaurantRules, not copied onto restaurant.

Medium: Constructor and prototype

Create a Product constructor with name and price data. Add a shared label method to Product.prototype.

function Product(name, price) {
  // Store the product data.
}

// Add a shared label method.

const product = new Product("Masala Dosa", 120);
console.log(product.label()); // -> Masala Dosa: INR 120

Hint: Use a normal function on the prototype so this is the product.

Hard: Safe own-property copy

Write copyOwnProperties(source) to return a new object containing only the source object's own enumerable properties, not inherited ones.

const defaults = { cuisine: "Indian" };
const dish = Object.create(defaults);
dish.name = "Dosa";
dish.price = 120;

function copyOwnProperties(source) {
  // Return a copy containing only own properties.
}

console.log(copyOwnProperties(dish));
// -> { name: 'Dosa', price: 120 }

Hint: Object.keys gives you the keys you need.

Quick Recap

  • Objects store properties and methods.
  • Every ordinary object has a prototype link.
  • Missing properties are searched for along the prototype chain.
  • Object.create creates an object with a chosen prototype.
  • Constructor functions use their .prototype object for shared methods.
  • Own properties belong directly to an object; inherited properties do not.
  • Classes provide modern syntax for the same prototype-based object model.
  • Prefer standard prototype APIs over the legacy __proto__ accessor.

What's Next

Prototype lookup explains where object behaviour comes from. Next, we will look more closely at execution contexts, this, and how JavaScript creates and runs functions.