Language: EN

prototipos-en-javascript

Prototypes in JavaScript

JavaScript is a prototype-based object-oriented language, which means that inheritance and code reuse are managed through a prototype system.

The prototype allows objects to inherit properties and methods from other objects. This inheritance mechanism is called prototypical inheritance.

This allows objects to share methods and properties, which optimizes memory usage.

This is a different approach from other languages that use a class structure, and is a result of JavaScript’s dynamic typing nature (and it has its advantages and disadvantages).

Moreover, it is one of the most challenging points to understand in the language, so let’s take a closer look 👇.

What is a prototype

The prototype is simply an object that all objects have as an internal property, and to which they can access.

All objects in JavaScript have the property [[Prototype]], which in turn is a reference to another “parent” object.

This sequence of objects connected by prototypes is called the prototype chain.

When you try to access a property or method that does not exist on the object, JavaScript will look for it in the object’s prototype.

It will continue searching through the prototype chain until it finds the property or reaches a null prototype (which generally means it has reached a base object).

Creating objects and prototypes

Let’s see at what point in the creation of an object the prototype is associated with the object.

Creation with Object Literals

When you create an object using literal notation, JavaScript automatically assigns Object.prototype as the prototype of the new object:

const person = {
  name: "Carlos",
  age: 30
};

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

Creation with Object.create()

You can also create a new object with a specific prototype using Object.create(proto):

const animal = {
  makeSound() {
    console.log("Sound");
  }
};

const dog = Object.create(animal);
dog.makeSound(); // "Sound"

In this case, dog has animal as its prototype, so it inherits the makeSound method.

Constructor Function and Prototype

When we use a constructor function with the new operator, a new object is created that inherits from that constructor function’s prototype.

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

Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name} and I am ${this.age} years old!`);
};

// Create a new instance of Person
let person1 = new Person("Luis", 30);

// Call the `greet` method from the prototype
person1.greet();  // Output: Hello, my name is Luis and I am 30 years old!

In this example:

  • Person is a constructor function that takes name and age as parameters.
  • Person.prototype is an object where we can add methods and properties that will be shared by all instances of Person.
  • The greet method is added to the prototype of Person, so all instances of Person can access it.

Property proto

Generally, the prototype of an object is publicly available through the __proto__ property.

console.log(juan.__proto__ === Person.prototype); // true

Direct access to the __proto__ property is not recommended. Instead of using __proto__, it is preferable to use the static methods provided by the Object object to work with prototypes.

Modifying Prototypes

We can add methods to a prototype after instances have been created, which is useful for extending functionality.

Person.prototype.birthday = function() {
    this.age++;
    console.log(`Congratulations ${this.name}, now you are ${this.age} years old.`);
};

juan.birthday(); // Output: "Congratulations Luis, now you are 31 years old."

Modifying the prototype of native objects

One of the powerful features of JavaScript is that we can modify the prototypes of native objects, such as Array, String, or Object. This allows us to add custom methods to basic types.

Array.prototype.printFirstElement = function() {
  console.log(this[0]);
};

let myArray = [10, 20, 30];
myArray.printFirstElement();  // Shows: 10

In this example, we added the method printFirstElement to the prototype of Array. Now, all arrays in JavaScript will have this method available.

Although it is possible to modify the prototypes of native objects, it is generally not a good idea (in fact, it is often quite a bad idea)

Modifying the prototypes of native objects can generate unexpected conflicts, especially in large projects or when working with external libraries.

Prototypical Inheritance in JavaScript

Prototypical inheritance is what allows one object to inherit properties and methods from another object. This feature is at the core of JavaScript’s inheritance system.

For example, consider the following example, where we create two constructor functions: Animal and Dog. The Dog function will inherit from Animal via the prototype:

// Create a base object: Animal
let Animal = {
  greet: function() {
    console.log(`Hello, I am a ${this.type}`);
  }
};

// Create a new object that inherits from Animal
let Dog = Object.create(Animal);

// Define specific properties for Dog
Dog.type = "dog";
Dog.bark = function() {
  console.log("Woof!");
};

// Use the Dog object
Dog.greet(); // "Hello, I am a dog"
Dog.bark();  // "Woof!"

In this example:

  • Animal is a base object with a greet method.
  • Dog is created using Object.create(Animal), which sets Animal as its prototype.
  • Dog has its own properties (type = "dog") and additional methods (bark).
  • Since Dog inherits from Animal, it can use the greet method defined in Animal.

Property Shadowing

If a property is defined on an object and also on its prototype, the object’s property “shadows” the prototype’s property.

This means that when accessing the property, the value from the object is obtained, not from the prototype.