Prototypes In JavaScript

Prototypes In JavaScript

Every JavaScript Object Has a Story. It Starts with a Prototype.

Introduction

In JavaScript, everything is an object, and every object has a prototype. This prototype serves as a hidden link, allowing objects to inherit properties and methods from one another. Whether it's an array, a function, or even a primitive wrapped as an object, each entity in JavaScript is connected through this prototype chain.

JavaScript prototype is an object that contains properties and methods that are shared among all instances of a particular object. It was designed to implement inheritance in JavaScript, which follows a prototype-based rather than a class-based approach like other languages.
Every JavaScript Object has a prototype property, which contains the prototype of that object.

The Prototype is a reference to another object and is used when a property or method is called on an object.

In this article— we explain what a prototype is, how prototype chains work, and how a prototype for an object can be set.

Prototype Inheritance

Create an object literal; which looks like this

const myObject = {
  city: "Mawsynram",
  greet() {
    console.log(`Namaste from ${this.city} !`);
  },
};

myObject.greet(); // Namaste from Mawsynram !

This is an object with one property city and one method greet(). If you call object’s name followed by a period like myObject., you will get various properties available to this myObject.
There you will see city and greet() as well as a lot of other properties!.

If you try to access one of them.
For example:

myObject.toString(); // "[object Object]"

What are these extra properties, and where do they come from?

Well, as i said earlier every object has a built-in property which is called it's prototype.

The prototype is itself an object, so the prototype will have its own prototype, making what's called a prototype chain.
The chain breaks when a certain prototype has it’s prototype value to null.

Think of it like in this way: a blueprint of a car, if you can build multiple cars from the same blueprint they will have all the same features, like wheels and an engine. You can customize each car (object) without changing the original blueprint (prototype).

When you try to access a property in any object (in case of this myObject) JS engine first finds in object, if the property can't be found in the object itself, the prototype is searched for the property.
If the property still can’t be found the prototype’s prototype is searched and so on until either the property is found or the end of the chain is reached, in which case undefined is returned.

Quick recap: when we call for myObject.toString() the browser (if in console) or JS engine:

  • looks for object own properties

  • can’t find there, so looks in prototype of the object for toString

  • finds it there, and calls it.

Analyzing Prototype’s of Prototype

If you want to check prototype of any object or anything here your getPrototypeOf() is your best friend.

For example:

Object.getPrototypeOf(myObject); // Object { }

This is an object called Object.prototype, and it is the most basic prototype, that all objects have by default. The prototype of Object.prototype is null, so it's at the end of the prototype chain:

[[Prototype]] vs .prototype vs __proto__ (Dunder Proto)

Understanding prototyping

Consider this code below:

class Car {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }

  getName() {
    return `${this.make} ${this.model}`;
  }
}

const fordInstance = new Car('Ford', 'Mustang');

console.log(fordInstance.getName()); // Ford Mustang

Now, the visual easy-to-understand presentation:

The algorithm is next: when we define a class or function in our code the JS engine creates a new object (prototype object, empty be default) simultaneously and allocates memory for it.

You can check this by just typing this line in the console:

console.dir(function func() {})

When the JS interpreter reaches the moment when we call our function through the keyword ‘new’ (the same for regular funciton with ‘new’ as well). The JS engine manages this behaviour not as a usual function.
Classes in JS are a syntactic sugar for Functions, the logic is pretty much the same. So, after the class Car was invoked, it creates a new object with certain fields in it. We place that object in the fordInstance variable. Also, the engine sets a connection between instance and Car.prototype via the internal [[Prototype]] mechanism. The developer doesn’t have any access to it at all. But, we are able to manipulate prototypes with __proto__.

__proto__ is a getter/setter for [[Prototype]]. And where can we get it?
Simply: using the same prototype chain! Going through it, we can get a Object.prototype at the end, which has getter/setter __proto__.
We are able to call it straight from fordInstance and get our Car.prototype. Check the code below.

console.dir(fordInstance.__proto__) // Car.prototype
console.dir(Object.getPrototypeOf(fordInstance)) // The same, just modern way

Since this is not only getter but setter, we can change our prototype. And I recommend to do it rarely and wisely (You may brake a prototype chain!) [read this more about in chapter Modifying Prototypes].

And guess the most important thing is that each object in JS (everything in JS is an object) has __proto__ ! And always refers to the prototype of the class with which it was created. Remember this rule in ages. I will describe it in details on the next paragraph.

Okay, but what about .prototype?

The main thing about .prototype, is that it might be only in either functions or class. Try to remember this as well.

As i said earlier, its another object, which was attached to the fordInstance after it was created.

That object contains all methods of our car, and it’s a really convenient way to organize them, otherwise we would have all of these methods inside each instance, and this is the worst memory usage.

Quick recap: [[Prototype]] is an internal mechanism of prototype inheritance. You may treat it as a regular reference, but without access to it. On the other hand, browsers give us the ability to adjust prototype chain via __proto__. Almost in all cases, you may think that [[Prototype]] and __proto__ are equal.
These are only links to the prototype! And prototype is an object referenced by __proto__. If there is no method within instance, engine will check it in prototypes till the end using prototype inheritance.

Each object has __proto__ .

Each function contains .prototype and __proto__ as well.

Going Through From Some Examples

console.log({}.__proto__ === {}.prototype);

It’s false. When we use an object literal its the same as new Object(). Do you remember what’s going on when we call a function with new keyword. Right, it attaches instance ({} in our case) to the prototype (which is Object.prototype)! And as I mentioned, you can call .prototype only in functions, so the right part of the assignment is undefined. Hence, answer is falsy.

Next condition returns true:

console.log({}.__proto__ === Object.prototype); // true

What about this example:

console.log([].__proto__ === Object.prototype); ?

As you might guess, its false. Because []. __proto__ is equal to new Array(). __proto__. And new Array().__proto__ === Array.prototype because [] is instance of Array. On other hand, Array.prototype.__proto__ === Object.prototype, because Array.prototype is a regular object, and as a usual object it has a reference to the Object.prototype.

These conditions are both true:

console.log([].__proto__ === Array.prototype);
console.log([].__proto__.__proto__ === Object.prototype);

The same behaviour here:

console.log((10).__proto__ === Number.prototype); //  its true

We created 10 in a straightforward way, but under the hood JS will use new Number(10). number is a primitive value, but when we call it through the “dot”, JS converts it in object ( it’s called autoboxing). That’s why a prototype chain works with primitive types as well.
Like I provided some example below:

console.log((20).toString()) // 20 as a string type
console.log('string'.repeat(2)) // 'stringstring'

Classical Inheritance vs Prototype-Based Inheritance

FeatureClassical InheritancePrototype-Based Inheritance
DefinitionInheritance is based on classes, where objects are instantiated from a class blueprint.Inheritance is based on prototypes, where objects inherit directly from other objects.
StructureUses class hierarchies (e.g., Java, C++, Python).Uses prototype chains (e.g., JavaScript).
Object CreationObjects are created from classes using constructors.Objects are created from other objects using Object.create() or constructor functions.
Method SharingMethods are defined in a class and shared across instances.Methods are stored in the prototype and shared among objects.
Inheritance ModelUses a strict, hierarchical inheritance (parent-child relationship).Uses flexible, dynamic delegation (objects link to prototypes).
ModificationChanging a class affects all instances, but modifying an instance does not affect the class.Modifying the prototype affects all linked objects.
PerformanceCan be optimized for performance in languages like Java and C++.Prototype lookup can be slightly slower due to delegation.
Example (JS - Classical)class Animal {}class Dog extends Animal {}const dog = Object.create(animalPrototype);

Shadowing Properties

What happens if you define a property in an object, when a property with the same name is defined in the object's prototype?

Let’s see:

const myDate = new Date(1995, 11, 17);

console.log(myDate.getTime()); // 819129600000

myDate.getTime = function () {
  console.log("something else!");
};

myDate.getTime(); // 'something else!

This should be predictable, given the description of the prototype chain. When we call getTime() the browser first looks in myDate for a property with that name, and only checks the prototype if myDate does not define it. So when we add getTime() to myDate, then the version in myDate is called.

Think of it like in this way: Imagine in a school where teacher does not know your real name so, she calls you out with your nick name instead of your first name. Here your nick name is local property and then she wants to know your real name so, she checks your first name in official record which is prototype.

This is called "shadowing" the property.

It occurs when a property or a method in an object is overrides an inherited property or a method from it’s own prototype.

This means the object's own property takes precedence over the one in the prototype chain.

For example:

let parent = {
name : "Momo",
}

let child = Object.create(parent)
child.name = "Okarun, got balls"; // Shadows the 'name' property from the prototype

console.log(child.name); // "Okarun, got balls" (not "Momo")
console.log(parent.name); // "Momo" (remains unchanged)

Here, child.name shadows parent.name, meaning the child object’s property is used instead of the inherited one.

Modifying Prototypes

In JavaScript, prototypes define an object’s behavior, but what if you want to extend or modify that behavior?

You can modify, change and even overrides a prototype.

For example: Consider this code below:

const Member1 = {
    codeName: "Twilight",
    identification: "Loid Forger, A Psychiatrist",
    work: function () {
        return this.codeName + " A Spy"
    },
};

const Member2 = {
    codeName: "FullMetal Lady",
    identification: "WISE Commanding Officer",
    work: function () {
        return this.codeName + " Work: Handler"
    },
};


Member2.__proto__ = Member1;

It’s an intriguing example, if you run this code

console.log(Member2.work()); // FullMetal Lady Work: Handler

In this Member2.__proto__ = Member1 Member2 is an object which prototype’s is referring to Member1 prototype by using __proto__.

Since Member2 has its own work() function, it shadows Member1’s method.

So, When Member2.work is deleted, JavaScript looks up the prototype chain and finds work() in Member1.

// FullMetal Lady A Spy

The problem with this approach is it is breaking DRY (Don't Repeat Yourself) principle.

Likewise, the work method is duplicated both in Member1 and Member2 . Instead of redefining work() in every object, you should define it once and let both objects inherit it.

Adding Method to A Prototype

One of the biggest advantages of prototypes is that they allow multiple instances to share the same functionality without allocating extra memory. Instead of attaching methods directly to each object, we attach them to the prototype, ensuring all instances inherit the behavior.

function Person(name) {
  this.name = name;
}

// Adding a method to Person’s prototype
Person.prototype.greet = function () {
  console.log(`Hello, my name is ${this.name}.`);
};

const Okarun= new Person("Okarun");
Okarun.greet(); // "Hello, my name is Okarun."

Here greet() is once defined in Person’s prototype, so every instance of person can use it without creating a new whole object or a separate copy.

Reshaping Built-in Prototypes

It’s a double edges sword; a superpower but also a potential nightmare if used incorrectly.

For example: We can change Arrays and Objects prototypes. Likewise, consider this code below which is adding reverse method to Strings.

String.prototype.reverse = function () {
  return this.split("").reverse().join("");
};

console.log("hello".reverse()); // "olleh"

It is similar to a polyfill in this manner.(but they don’t override methods)
I recommend to do this merely. (As may it leads Security Vulnerabilities!). Also it affects every instance of that type.

Functions and Classes

Functions in JS are tricky. They may have __proto__ and prototype at the same time. If you call a function without ‘new’ keyword, you DON’T need to think about prototype at all. Simply because you don’t create a new instance, you treat that function as a usual instruction to do something. And all functions, except arrow functions, have its own prototype.

Lets talk about __proto__ in functions. Functions in JS are objects too, therefore they can get a __proto__. Here’s some code and 2 images for better understanding:

function usualFunction() {}
console.log(usualFunction.__proto__ === Function.prototype); // true
console.log(usualFunction.call === Function.call); // true
console.log(usualFunction.valueOf === Object.prototype.valueOf); // true

Let’s spend a few minutes and discuss about __proto__ in built-in classes.

There are a lot of built-in classes in JS, such as String, Number, Promise, Function, Object, Array, Date etc. All of these classes are functions. And they may have a __proto__ and a .prototype simultaneously. If we call them with new, we stick their prototypes to the new instance. But what about __proto__ ?

[[Prototype]] or just __proto__ (or dunder proto) in functions is completely another thing than .prototype. You should remember, that __proto__ always refers to the prototype of the class with which it was created. But how are other classes/functions created? Of course, with the new() Function. In JS only functions can create other functions. All of these built-in classes were created via function. That’s why each __proto__ of these classes refers to the Function.prototype.

You may ensure:

console.log(String.__proto__ === Function.prototype); // true
console.log(Number.__proto__ === Function.prototype); // true
console.log(Promise.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Array.__proto__ === Function.prototype); // true
console.log(Date.__proto__ === Function.prototype); // true

Lets also describe this part of code:

class Animal {
  walk() {}
}
class Cat extends Animal {
  meow() {};
}
const catInstance = new Cat();

Here we have class Animal. Since this is a class it has its own prototype and a proto (Animal.__proto__ === Function.prototype), because class Animal itself was created with new Function() under the hood. The more interesting thing is with class Cat. It has prototype too, which is connected to the parent prototype (Animal.prototype). But what about proto? We need to check, how class Cat was created. And in this case it was born from class Animal (extends keyword). Hence, Cat.__proto__ === Animal.

Let me show you the scheme:

It looks complex at first glance. Don’t be afraid. Red lines are the main prototype chain. Green lines — the reference from class to its own prototype. Yellow — the reference to the constructor, which created this instance. Eventually you can check it your browser and play with it.

Like I mentioned before, the end of the prototype chain is always Object.prototype. Parent of each class/function is Function.prototype.

Weird behaviour with prototype

I’m gonna show you the really weird behaviour with prototypes. Try to put this code in your console:

console.log(Function.prototype, typeof Function.prototype === 'function'); // true
console.log(Array.prototype, Array.isArray(Array.prototype)); // true

You may ask: Hey, you said that prototype is always an object, why do we see a function and an array respectively? I know it looks quite strange. But what if I say that you should treat them as regular objects! It was done by ECMA due to backward compatibility. You may read about it here and here.


Conclusion

  • proto is a getter/setter for [[Prototype]]

  • prototype is always object

  • The object's own property takes precedence over the one in the prototype chain is known as shadowing property.

  • All objects have __proto__

  • Only functions have prototype, except array functions, async functions and some others

  • When you call function without new keyword you don’t need its prototype

  • .prototype of function doesn’t affect on function itself, its only a prototype object for new instances

  • Try to avoid manipulations with prototypes manually

  • End of each prototype chain is Object.prototype

  • Despite prototypes and ‘this’ mostly be next to each other, its completely different independent parts.

  • And the most important: try to play with above examples in your browser. Use console.dir for convenient revealing each prototype.

Want more?

Read my more blogs here !
Connect with me at X(twitter),
Github