Learn How JavaScript Prototypes Work with Inheritance


Like many programming languages, JavaScript supports object-oriented programming (OOP). In this article, we will use inheritance, one of the basic OOP concepts, to explain prototypes.
What Are Prototypes?
JavaScript is a prototype-based language. Every object has a link to another object called its prototype. This allows objects to inherit properties and methods from each other.
Prototypes allow you to define methods and properties that are shared across all instances of a particular type of object. This makes your code more efficient and easier to manage.
Creating a Simple Object
To understand prototypes, we are going to use a console. Find out how to open the console in your browser.
Open the console and create a simple object:
person = {
firstName: 'Marko',
lastName: 'Markovic',
yearOfBirth: 1981,
placeOfBirth: {
city: 'Belgrade',
state: 'Serbia'
}
};
This is a typical JavaScript object. If we call the console.log(person)
and pass the person
object as the argument, the console will return the same object properties and its values.
Accessing the toString() Function via the Prototype
JavaScript objects inherit built-in functions from Object.prototype
. One of these functions is toString()
which returns a string representation of an object.
To see the prototype of the person
object, type the following code:
console.log(person.__proto__);
You will get the list of the following functions:
{
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(), //<------------------- toString()
valueOf: ƒ valueOf(),
__defineGetter__: ƒ __defineGetter__(),
__defineSetter__: ƒ __defineSetter__(),
__lookupGetter__: ƒ __lookupGetter__(),
__lookupSetter__: ƒ __lookupSetter__(),
__proto__: null
Overriding toString() in an Object
JavaScript follows a prototype chain. When the function is called on an object:
It first checks if the function is defined within the object.
If not found, it checks the object's prototype.
It scans the prototype chain until the function is found or the chain ends.
Currently, the object person
does not have any functions. It has only a few properties and values. But, what will happen if we define the toString()
function inside it?
person.toString = function() {
return `${this.firstName} ${this.lastName}`;
};
console.log(person.toString()); // Marko Markovic
In this case, JavaScript will execute the code above and return the hardcoded values from the person
object. This is called shadowing: our function "shadows" the one from the prototype.
Redefining Prototype’s toString() Function
When the function is defined in the object itself, JavaScript uses that version instead of the one inherited from the prototype. But what if we don’t have the custom toString()
function within the person
object? Let’s redefine this prototype’s built-in function and observe the result:
Approaching the thetoString()
function in the prototype and adding a new value:
Object.prototype.toString = function() {
return 'Hello world';
};
Now, calling person.toString()
or even anotherObject.toString()
would return 'Hello world', because all objects inherit from Object.prototype
.
console.log(person.toString()); // Hello World
Conclusion
When overriding the prototype's toString()
function, be careful! It affects everything globally. Any object that inherits from Object.prototype
(which is practically all objects in JavaScript) will be affected by this change.
Comparison: Locally vs. Globally
Approch | Impact | Reccomendation |
person.toString = ... | Only on the person object | ✅ Recommended |
Object.prototype.toString = ... | On all objects | ❌ Risky |
If you want to avoid unexpected errors in your web application, pay attention to this problem and try to redefine thetoString()
locally. This way, the change is scoped only to the specific object and doesn’t affect others.
Subscribe to my newsletter
Read articles from Milos Babic directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Milos Babic
Milos Babic
Hey, I'm Milos. I write about code so future me (and maybe you) won’t forget how it works. I am a certified full-stack developer and a tech writer who loves breaking down complex things into something more readable than error logs.