Register to get access to free programming courses with interactive exercises

Prototypes JS: Introduction to Object Oriented Programming (OOP)

This lesson will give a brief introduction to the topic of prototypes. It's impossible to say everything about it in one lesson. Not even a whole course is enough. Prototypes are learned only through practice combined with theoretical training. So don't worry if you don't understand everything the first time, it's normal.

Prototypes are the mechanism with the biggest impact on how objects work in JavaScript. They're rarely used directly in code (and usually only in libraries), but knowing them is important for understanding code behavior and debugging. Particularly when working with classes, which we'll cover later in the course. We'll merely go over the fundamentals in this lesson. We have another course listed in the supplementary materials, which will help you understand prototypes in depth.

In JavaScript, each object has a prototype associated with it. A prototype is a regular object stored in a special [[prototype]] service field (this field cannot be directly accessed). It can be extracted like this:

const date = new Date();
// This function retrieves the object prototype from the object itself
const proto = Object.getPrototypeOf(date); // Date {}

// The prototype doesn't store the constructor
// What's stored there? Let's find out
proto === Date; // false

const numbers = [1, 2];
Object.getPrototypeOf(numbers); // [] – mapping is different, but it's an array

// Prototypes are also available for constructors, which we define ourselves
function Company(name) {
  this.name = name;
}

const company = new Company();
Object.getPrototypeOf(company); // Company {}

What is it for? To address this topic, we must first understand how JavaScript property calls work. Until now, it was simple: if an object has a property, you can call it and get a value; if there is no property, you get undefined when you access it. That's the truth, but not the whole truth. JavaScript is a little trickier and doesn't just look for a property in the object itself. If no property is defined for an object, JavaScript looks at the prototype and tries to figure out if the prototype has the necessary property. If there is, its value is returned.

In reality, the process is even more complicated. If the property is not found in the prototype, JavaScript looks at the prototype's prototype and so on until it reaches the end of the chain, and it does indeed have an end: the last prototype in the prototype chain is always null. Inheritance is implemented via this mechanism. This topic is beyond the scope of the current lesson.

Even normal JavaScript objects have a prototype:

Object.getPrototypeOf({}); // {} is an Object

This is the reason why even empty objects contain properties and methods (remember, properties and methods are in the prototype):

const obj = {}; // You can do the same thing like this: const obj = new Object();
// This is the constructor function that the current object was obtained from, in this case 
obj.constructor; // [Function: Object]
// obj doesn't have its own constructor property, it came from a prototype
Object.hasOwn(obj, 'constructor'); // false
Object.hasOwn(obj, 'name'); // false
obj.name = 'hexlet';
// The name is in the object itself, because we just added it
Object.hasOwn(obj, 'name'); // true

You can access the prototype not only from objects, but also from the prototype property of the constructor that creates those objects:

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

// The same thing, obtained by different methods
// Company.prototype === Object.getPrototypeOf(new Company())

Now we can answer this question, “where does the prototype come from?” The prototype is the object in the prototype property of the constructor function, not the constructor itself. It's easy enough to test the prototypes by modifying them.

// Add the getName property (make it a method)
Company.prototype.getName = function getName() {
  // this still depends on the context in which it's called
  return this.name;
}

const company = new Company('Hexlet');
// The property is available!
console.log(company.getName()); // => Hexlet

Nothing is stopping you from replacing the value of the getName property in a specific object. This won't affect other objects in any way, since they retrieve getName from the prototype:

const company1 = new Company('Hexlet');
const company2 = new Company('Google');
company2.getName = function getName() {
  return 'Alphabet';
}

// This call will take the property from the object itself
company2.getName(); // Alphabet
// This call will take the value of the property from the prototype
company1.getName(); // Hexlet

Creating properties through a prototype is the proper way to create your abstractions in JavaScript. Any additional abstraction that we add to the code should resemble a constructor and prototype and have the appropriate features.

What is the benefit of this mechanism?

The simplest benefit is that extends the language kernel and libraries without direct access to the source code. Prototypes are an incredibly flexible mechanism that allows you to change anything from anywhere in the program (during runtime). For example, we can add or replace any methods in any objects of the language itself.

const numbers1 = [1, 3];

// As soon as this code is executed, all arrays,
// including those that have already been created, will have the last method
Array.prototype.last = function last() {
  // This accessing is legitimate because it's a reference to the object itself,
  // which in our case is an array
  return this[this.length - 1];
}

numbers1.last(); // 3

const numbers2 = [10, 0, -2];
numbers2.last(); // -2

// Example of replacement
// a forbidden technique, never do that in real code
Array.prototype.map = function map() {
  return 'Ehu!';
}

numbers1.map(); // "Ehu!"

Like any other powerful mechanism, prototypes are incredibly dangerous. You can make such a mess that programming becomes a living hell.

// Very evil code that breaks the push method
Array.prototype.push = function push(value) {
  return this.unshift(value);
}

const numbers = [1, 2];
numbers.push(3);
console.log(numbers); // => [3, 1, 2] !!!

But if you use this mechanism wisely, you can get a lot of good things. For example, prototypes are actively used in testing, they help to spoof unwanted method calls that perform HTTP requests. Another popular option is polyfills. This is code that adds features from newer versions to older versions of JavaScrip. Not all of them, of course; only those that appear as properties and methods.

P.S. Now you know why in the documentation all function names are described like that: Number.prototype.toLocaleString().


Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

Bookmate
Health Samurai
Dualboot
ABBYY
Suggested learning programs
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.