×

Difference between __proto__, [[Prototype]] & .prototype in JavaScript

Introduction

JavaScript’s prototype-based inheritance can be confusing, especially when dealing with __proto__, [[Prototype]], and .prototype. While these terms may seem similar, they serve distinct roles in how objects inherit properties and methods.

At the core of JavaScript’s inheritance system is the [[Prototype]] internal slot, which determines an object’s prototype. The __proto__ property provides a way to access or modify this prototype, though it’s now considered outdated. Meanwhile, .prototype exists only on constructor functions and is used to define methods and properties that instances should inherit.

Understanding the difference between these three is crucial for writing efficient, maintainable code. In this article, we’ll break down their roles, how they interact, and best practices for using them correctly.

Difference between __proto__, [[prototype]] and .prototype in JavaScript

In JavaScript, __proto__, [[Prototype]], and .prototype are related but have distinct roles in the prototype chain. Lets discuss them in details.

1. __proto__ (Deprecated but still used)

  1. Every JavaScript object has an internal hidden property called [[Prototype]], which links it to another object and __proto__ is a property that refers to an object's prototype.
  2. Reference to [[Prototype]].
  3. Using __proto__ you can access [[Prototype]] of an instance.
  4. Though widely used, it is considered deprecated in favor of Object.getPrototypeOf() and Object.setPrototypeOf().
  5. __proto__ is a getter/setter for [[Prototype]] and allows access to the prototype chain.
  6. Exists on all objects (except null)
  7. It is used for prototype-based inheritance.

Example:

const obj = { name: "Alice" }; console.log(obj.__proto__ === Object.prototype); // true

1.1 What is the use of __proto__

Let's understand this with the help of an examples,

const animal = { eats: true, sleep() { return "Sleeping..."; } }; const dog = { barks: true }; // Setting animal as the prototype of dog dog.__proto__ = animal; console.log(dog.barks); // true (own property) console.log(dog.eats); // true (inherited from animal) console.log(dog.sleep()); // "Sleeping..." (inherited method)

Explanation:

Before setting dog prototype to animal, how dog's prototype looks like.

proto-image-1

After setting dog prototype to animal how dog's prototype looks like.

proto-image-2

  1. dog doesn’t have an eats property, but it inherits it from animal via __proto__.
  2. dog.sleep() works because sleep() is found in animal.
  3. JavaScript uses prototype chaining to look up properties.

1.2 So what is prototype chain/chaining.

Let's understand this using some real life scenarios.

const grandparent = { lastName: "Smith" }; const parent = { __proto__: grandparent }; //or we can also do parent.__proto__=grandparent const child = { __proto__: parent }; console.log(child.lastName); // "Smith" (inherited from grandparent)

Explanation: When accessing child.lastName, JavaScript follows the prototype chain to find the property:

Checks child.lastName → Not found in child.

Looks up child.[[Prototype]] (which is parent) → parent.lastName is also not found.

Moves up to parent.[[Prototype]] (which is grandparent) → Finds lastName: "Smith".

Returns "Smith" as the result.

Since JavaScript uses prototype inheritance, if a property isn’t found in an object, it searches its prototype and continues up the chain until it either finds the property or reaches null.

NOTE: Prototype Chain Stops at null.

console.log(grandparent.__proto__); // Object prototype console.log(grandparent.__proto__.__proto__); // null (end of the chain)

Changing __proto__ dynamically is not recommended for performance reasons. Instead, use Object.create(). Object.create(protoObj) is a cleaner way to set prototypes.

Example:

const animal = { eats: true, sleep() { return "Sleeping..."; } }; // Creating an object with animal as its prototype let dog = Object.create(animal); dog.bark=true console.log(dog.sleep()); // "Sleeping..." (inherited) //We can also do it like the below one, but there is a catch let dog2={bark:true} dog2.__proto__=Object.create(animal) console.log(dog2.sleep()) // "Sleeping..." (inherited)

You might think whats the difference between these 2 and which one should we use.

  1. When you use Object.create(animal), it does not return animal directly. Instead, it creates a new object with animal as its prototype.
  2. dog.__proto__ is now set to this new intermediate object, not animal directly.
  3. This adds an extra unnecessary prototype layer, which can cause confusion. It will still works but, we don't need to add any additional layer to chain.

proto-image-3

proto-image-4

Why is __proto__ Deprecated?

Using __proto__ is slow and can cause performance issues because, JavaScript engines optimize prototype lookups, but modifying __proto__ dynamically breaks these optimizations.

It’s better to use Object.create() to explicitly set prototypes. This achieves the same result without modifying __proto__ directly.

What if there is an existing object with some properties in it, like below.

const animal = { eats: true, sleep() { return "Sleeping..."; } }; let dog={ bark:true } // Setting up prototype inheritance Object.setPrototypeOf(dog, animal); //barks remains in dog because it's an own property, even after setting its prototype to animal.

proto-image-5

Then which one to use: Object.create()

  1. Creates a new object instead of modifying an existing one.
  2. More efficient
  3. Safer and more widely recommended in modern JavaScript.
  4. You must manually assign properties if you want to copy an existing object.
  5. .constructor property lost using this method (for constructor function)

Object.setPrototypeOf()

  1. Useful if you already have an object and need to change its prototype.
  2. Slower performance (modifies prototype dynamically, which isn't optimized in JavaScript engines).
  3. Can lead to unexpected behaviors if the object is already in use.
  4. If the object already had a constructor, it remains intact.

2. [[Prototype]] (Internal)

  1. [[Prototype]] is the actual internal prototype reference of an object.
  2. It is not directly accessible but can be retrieved using Object.getPrototypeOf() or using __proto__.
  3. [[Prototype]] is a hidden reference inside every object that points to another object.
  4. It determines where JavaScript looks when trying to access a property or method on an object.

NOTE: Every object in JavaScript (except null) has a [[Prototype]].

Example

const obj = { name: "Bob" }; console.log(Object.getPrototypeOf(obj) === obj.__proto__); // true

How to Access [[Prototype]]?

JavaScript provides two ways to access the [[Prototype]] of an object:

  1. Using the deprecated __proto__ property (avoid in modern code):
const obj = {}; console.log(obj.__proto__); // Logs Object.prototype
  1. Using Object.getPrototypeOf() (recommended):
const obj = {}; console.log(Object.getPrototypeOf(obj)); // Logs Object.prototype

How [[Prototype]] Works?

When you try to access a property or method on an object, JavaScript first looks for the property inside the object itself. If not found, it searches in that object's [[Prototype]]. If still not found, it keeps searching up the prototype chain until it reaches null which we have seen above also.

Setting [[Prototype]]

  1. Using Object.create() (Recommended)
const animal = { makeSound: function () { return "Some generic sound"; } }; const dog = Object.create(animal); // Creates `dog` with `animal` as its prototype dog.breed = "Labrador"; console.log(dog.makeSound()); // "Some generic sound" (Inherited property) console.log(dog.breed); // "Labrador" (own property) console.log(Object.getPrototypeOf(dog) === animal); // true
  1. Using __proto__ (Deprecated)
const animal = { eats: true }; const dog = { __proto__: animal }; // Avoid using `__proto__` in production console.log(dog.eats); // true
  1. Using Object.setPrototypeOf()
const animal = { eats: true }; const dog = {}; Object.setPrototypeOf(dog, animal); // Sets `animal` as `dog`'s prototype console.log(dog.eats); // true

3. .prototype (Used in Constructor Functions)

  1. .prototype is a property of both constructor functions (not regular objects) and ES6 classes.
  2. It is used when creating objects via the new keyword.
  3. Objects created using a constructor function inherit properties from .prototype.
  4. .prototype is an object that exists on every function (except arrow functions).
  5. When you create a new object using new keyword, that object’s [[Prototype]] / (__proto__) is set to the constructor’s .prototype.

Read more about Constructor function here Read about how prototype works internally in constructor function Read about how prototype works internally in classes

Example

function Person(name) { this.name = name; } //adding greet function in the Person's prototype Person.prototype.greet = function () { return `Hello, ${this.name}`; }; //creating instance of the Person const alice = new Person("Alice"); console.log(alice.__proto__ === Person.prototype); // true console.log(Person.prototype.__proto__ === Object.prototype); // true

Key points:

  1. Every function has a .prototype property, which is an object.
  2. All objects inherit from Object.prototype unless explicitly changed.
  3. Since Person.prototype is an object, it inherits from Object.prototype, making Person.prototype.__proto__ === Object.prototype true.

What is the benefit of using .prototype

Examples:

function Person(name) { this.name = name; this.greet1=function(){ return `Hello from greet1` } greet2=function(){ return `Hello from greet2` } } Person.prototype.greet3 = function () { return `Hello from greet3`; }; const alice = new Person("Alice"); const bob = new Person("Bob"); console.log(alice.__proto__==Person.prototype); //true console.log(Person.prototype.__proto__ === Object.prototype); // true

How the instances will look like: proto-image-6

In this above example greet1 is instance method means every instance gets a separate copy of greet1, which is inefficient. It behaves similarly to defining a method inside an object literal. Also meaning that changes to one instance’s method do not affect other instances.

greet2 is local method meaning greet2 is not attached to this, meaning it is just a local function inside the constructor.It is not added to the instance. It will not be accessible from alice or bob.

& greet3 is prototype method means greet3 method is added to the prototype of the Person constructor function meaning every instances will share the same function and make it more memory efficient. greet3 is stored once in the prototype instead of being duplicated for each instance. Here Changing method affects all instances.

prototype in ES6 Classes

class Person { constructor(name, age) { this.name = name; this.age = age; } // This is stored in Person.prototype greet() { return `Hello, my name is ${this.name} and I am ${this.age} years old.`; } } const alice = new Person("Alice", 25); console.log(alice.greet()); // "Hello, my name is Alice and I am 25 years old." const bob = new Person("Bob"); console.log(alice.greet === bob.greet); // true (Shared method from prototype) console.log(alice.hasOwnProperty("greet")); // false (Not an instance property) console.log(Person.prototype.hasOwnProperty("greet")); // ✅ true (Stored in prototype) console.log(alice.__proto__==Person.prototype); //true console.log(Person.prototype.__proto__ === Object.prototype); // true

Read more about ES6 Classes here

How instance of a class looks like: proto-image-7

Even though ES6 class methods look like instance methods, they are actually prototype methods by default.

When Is a Method an "Instance Method" in classes?

If you define a method inside the constructor, then it becomes an instance method (separate for each object):

class Person { constructor(name) { this.name = name; this.greet = function() { // Instance method (not on prototype) return `Hello, ${this.name}`; }; } } const alice = new Person("Alice"); const bob = new Person("Bob"); console.log(alice.greet === bob.greet); // false (Different function instances) console.log(alice.hasOwnProperty("greet")); // true (Stored on each instance)

How alice instance looks like: proto-image-8

Overriding .prototype

You can completely replace .prototype, but this breaks the default constructor reference.

function Bird() {} Bird.prototype = { fly: function () { return "I can fly!"; } }; const sparrow = new Bird(); console.log(sparrow.fly()); // "I can fly!" console.log(sparrow.constructor === Bird); // false (constructor is lost) console.log(sparrow.constructor === Object); // true (now points to Object)

Problem: The .constructor reference is lost because we replaced Bird.prototype.

Fixing the constructor:

Bird.prototype.constructor = Bird; console.log(sparrow.constructor === Bird); // true

Best Practice: Instead of replacing .prototype, extend it using Object.create().

JavaScript's built-in objects (Array, String, Object, etc.) also use prototypes.

Extending Built-in Prototypes

Array.prototype.first = function () { return this[0]; }; const numbers = [10, 20, 30]; console.log(numbers.first()); // 10

NOTE: Modifying built-in prototypes is not recommended, as it can break existing code.

NOTE: JavaScript does not support multiple inheritance directly through the extends keyword. However, multiple inheritance can be simulated using mixins or composition.

Questions you might ask

Is Object a constructor function ?

Object is a constructor function, and Object.prototype is the default prototype for all objects in JavaScript.

List of Built-in Constructor Functions in JavaScript

JavaScript provides several built-in constructor functions that are used to create objects of different types.

1. Object Constructors

ConstructorDescriptionExample
ObjectCreates a generic object.const obj = new Object();
ArrayCreates an array object.const arr = new Array(1, 2, 3);
FunctionCreates a function dynamically.const fn = new Function('a', 'b', 'return a + b');
BooleanCreates a boolean object.const bool = new Boolean(false);
NumberCreates a number object.const num = new Number(100);
StringCreates a string object.const str = new String("Hello");
SymbolCreates a new unique symbol.const sym = Symbol("id");

2. Error Constructors

ConstructorDescriptionExample
ErrorCreates a generic error object.const err = new Error("Something went wrong");
TypeErrorRepresents an incorrect type.const err = new TypeError("Invalid type!");
ReferenceErrorRepresents an invalid reference.const err = new ReferenceError("x is not defined");
SyntaxErrorRepresents syntax errors.const err = new SyntaxError("Unexpected token");
RangeErrorRepresents an out-of-range value.const err = new RangeError("Value out of range!");
EvalError(Legacy) Represents an error in eval().const err = new EvalError("eval() error");

3. Collection Constructors

ConstructorDescriptionExample
MapCreates a map (key-value pairs).const map = new Map();
SetCreates a set (unique values).const set = new Set();
WeakMapCreates a weak map (keys are objects).const weakMap = new WeakMap();
WeakSetCreates a weak set (values are objects).const weakSet = new WeakSet();

4. Date & Utility Constructors

ConstructorDescriptionExample
DateCreates a date object.const date = new Date();
RegExpCreates a regular expression.const regex = new RegExp("\\d+");
PromiseCreates a promise object.const promise = new Promise((resolve) => resolve("Done!"));

5. Buffer & Typed Array Constructors

ConstructorDescriptionExample
ArrayBufferCreates a buffer for raw binary data.const buffer = new ArrayBuffer(16);
DataViewViews data in an ArrayBuffer.const view = new DataView(buffer);
Int8ArrayCreates an array of 8-bit signed integers.const int8 = new Int8Array([1, 2, 3]);
Uint8ArrayCreates an array of 8-bit unsigned integers.const uint8 = new Uint8Array([1, 2, 3]);
Int16ArrayCreates an array of 16-bit signed integers.const int16 = new Int16Array([1, 2, 3]);
Float32ArrayCreates an array of 32-bit floating-point numbers.const float32 = new Float32Array([1.1, 2.2]);

6. Web API Constructors (Environment-Specific)

These constructors are available in browser environments, not in pure JavaScript engines.

ConstructorDescriptionExample
EventCreates a DOM event.const event = new Event("click");
XMLHttpRequestHandles HTTP requests.const xhr = new XMLHttpRequest();
WebSocketCreates a WebSocket connection.const ws = new WebSocket("ws://example.com");
FileRepresents a file object.const file = new File(["content"], "example.txt");
BlobRepresents raw file-like data.const blob = new Blob(["Hello"], { type: "text/plain" });

So for these constructor functions, the methods like map filter etc. are added using Array.prototype.map ??

Yes! Methods like .map(), .filter(), .push() for Array are not directly added to individual arrays. Instead, they are stored in Array.prototype, and all arrays inherit from it.


How It Works?

Example with Array.prototype.map

const arr = [1, 2, 3]; console.log(arr.map); // [Function: map] (Exists on Array.prototype) console.log(arr.__proto__ === Array.prototype); // true // Using the method const newArr = arr.map((num) => num * 2); console.log(newArr); // [2, 4, 6]

The .map() method is not inside the arr object itself, but inside Array.prototype.


How Prototype Lookup Works?

When you call arr.map(), JavaScript follows this lookup chain:

  1. First, it checks if arr has a map method (it doesn't).
  2. Then, it looks in Array.prototype, where map is defined.
  3. Since it finds .map(), it executes it using arr as this.

This process is called Prototype Chain Lookup

Summary

In JavaScript, understanding __proto__, .prototype, and [[Prototype]] is essential for working with inheritance and the prototype chain. The [[Prototype]] is an internal hidden reference that every object (except null) has, pointing to its prototype, which is used for property and method lookup. Although [[Prototype]] is not directly accessible, it can be modified using Object.getPrototypeOf() and Object.setPrototypeOf(). The __proto__ property, while often used to access or change an object's prototype, is now considered outdated and should be replaced with the modern Object methods. On the other hand, .prototype is a property specific to constructor functions and ES6 classes. It is used to define methods and properties that are shared across all instances created by that constructor. When an object is created using new, its [[Prototype]] is automatically set to the constructor function's .prototype, enabling inheritance and shared behavior. Understanding these concepts is crucial for writing efficient, maintainable JavaScript code.