DEV Community

Cover image for Difference between __proto__, [[Prototype]] & .prototype in JavaScript
capscode
capscode

Posted on • Originally published at capscode.in

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)

Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  1. Using Object.getPrototypeOf() (recommended):
const obj = {};
console.log(Object.getPrototypeOf(obj)); // Logs Object.prototype
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  1. Using __proto__ (Deprecated)
const animal = { eats: true };
const dog = { __proto__: animal }; // Avoid using `__proto__` in production

console.log(dog.eats); // true
Enter fullscreen mode Exit fullscreen mode
  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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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

Fixing the constructor:

Bird.prototype.constructor = Bird;
console.log(sparrow.constructor === Bird); // true
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Constructor Description Example
Object Creates a generic object. const obj = new Object();
Array Creates an array object. const arr = new Array(1, 2, 3);
Function Creates a function dynamically. const fn = new Function('a', 'b', 'return a + b');
Boolean Creates a boolean object. const bool = new Boolean(false);
Number Creates a number object. const num = new Number(100);
String Creates a string object. const str = new String("Hello");
Symbol Creates a new unique symbol. const sym = Symbol("id");

2. Error Constructors

Constructor Description Example
Error Creates a generic error object. const err = new Error("Something went wrong");
TypeError Represents an incorrect type. const err = new TypeError("Invalid type!");
ReferenceError Represents an invalid reference. const err = new ReferenceError("x is not defined");
SyntaxError Represents syntax errors. const err = new SyntaxError("Unexpected token");
RangeError Represents 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

Constructor Description Example
Map Creates a map (key-value pairs). const map = new Map();
Set Creates a set (unique values). const set = new Set();
WeakMap Creates a weak map (keys are objects). const weakMap = new WeakMap();
WeakSet Creates a weak set (values are objects). const weakSet = new WeakSet();

4. Date & Utility Constructors

Constructor Description Example
Date Creates a date object. const date = new Date();
RegExp Creates a regular expression. const regex = new RegExp("\\d+");
Promise Creates a promise object. const promise = new Promise((resolve) => resolve("Done!"));

5. Buffer & Typed Array Constructors

Constructor Description Example
ArrayBuffer Creates a buffer for raw binary data. const buffer = new ArrayBuffer(16);
DataView Views data in an ArrayBuffer. const view = new DataView(buffer);
Int8Array Creates an array of 8-bit signed integers. const int8 = new Int8Array([1, 2, 3]);
Uint8Array Creates an array of 8-bit unsigned integers. const uint8 = new Uint8Array([1, 2, 3]);
Int16Array Creates an array of 16-bit signed integers. const int16 = new Int16Array([1, 2, 3]);
Float32Array Creates 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.

Constructor Description Example
Event Creates a DOM event. const event = new Event("click");
XMLHttpRequest Handles HTTP requests. const xhr = new XMLHttpRequest();
WebSocket Creates a WebSocket connection. const ws = new WebSocket("ws://example.com");
File Represents a file object. const file = new File(["content"], "example.txt");
Blob Represents 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]
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (4)

Collapse
 
nevodavid profile image
Nevo David

Pretty cool seeing all the inner parts laid out like that. Every time I think I get prototypes, there's some new edge case - keeps me humble.

Collapse
 
capscode profile image
capscode

Thanks @nevodavid

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

Took me forever to really get how all this prototype stuff hooks up, so this is super helpful.

Collapse
 
capscode profile image
capscode