Usages of Prototype in Javascript

In JavaScript, prototypes are a key feature of the language’s object-oriented capabilities. JavaScript uses prototypal inheritance, which means that objects inherit properties and methods from other objects, rather than from classes as in classical inheritance. This is done through the object’s prototype.

What is a Prototype?

A prototype is an object from which other objects inherit properties. Every JavaScript object has a hidden internal property (called [[Prototype]]), which can be accessed via __proto__ or using the Object.getPrototypeOf() method. This prototype object serves as a blueprint, and objects can inherit methods and properties from it.

Prototype Chain

If you try to access a property on an object, JavaScript will first look at that object. If it doesn’t find the property, it will look at the object’s prototype, and then at the prototype’s prototype, and so on, forming a prototype chain. The search continues up the chain until the property is found or the chain ends at null.

Example of Prototype Chain:

let animal = {
  eats: true
};

let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal;  // rabbit's prototype is now animal

console.log(rabbit.eats);   // true (inherited from animal)
console.log(rabbit.jumps);  // true (exists in rabbit)

In the above example:

  • The rabbit object doesn’t have its own eats property, but it inherits it from the animal prototype through the prototype chain.

Object Prototypes

Every object in JavaScript has a prototype, either set explicitly or inherited. For example, if you create an object using a constructor function or a class, it automatically inherits from the prototype of its constructor.

Example: Prototypes with Constructor Functions

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

Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}`);
};

let john = new Person("John", 30);

john.sayHello();  // Output: "Hello, my name is John"

In this example:

  • Person is a constructor function.
  • We add a method sayHello to Person.prototype, so all instances of Person (like john) can access it via the prototype chain.

Prototype vs. __proto__ vs. prototype

It’s important to understand the difference between __proto__ and prototype:

  • __proto__: This is an internal property of an object that points to its prototype (the object it inherits from). All objects have __proto__, but it’s not recommended to use this directly (use Object.getPrototypeOf() instead).
  • prototype: This is a property of constructor functions (or classes) that points to the prototype object from which instances inherit methods and properties. This is where you define methods or properties that should be shared among all instances.

Example:

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

Car.prototype.start = function() {
  console.log(`${this.make} ${this.model} is starting...`);
};

let myCar = new Car("Toyota", "Corolla");

// Accessing prototype chain
console.log(myCar.__proto__ === Car.prototype);  // true

// Calling a method inherited from the prototype
myCar.start();  // Output: "Toyota Corolla is starting..."
  • Car.prototype refers to the object that instances of Car (like myCar) will inherit from.
  • myCar.__proto__ refers to the same object, i.e., Car.prototype.

Prototype Inheritance

Objects can inherit properties from other objects via their prototype, allowing for shared behavior across multiple instances.

Example:

let animal = {
  eats: true
};

let dog = Object.create(animal);  // dog inherits from animal
dog.barks = true;

console.log(dog.eats);  // true (inherited from animal)
console.log(dog.barks); // true (own property)

Here, dog is created with animal as its prototype using Object.create(). The dog object inherits properties from animal, allowing access to the eats property.

Prototype Methods

There are a number of useful built-in prototype methods in JavaScript for managing prototypes and inheritance:

  • Object.getPrototypeOf(obj): Returns the prototype of the given object obj.
  • Object.setPrototypeOf(obj, prototype): Sets the prototype of obj to the given prototype.
  • Object.create(proto): Creates a new object with the specified prototype.

Example:

let cat = {
  meows: true
};

let myCat = Object.create(cat);  // Create a new object with cat as the prototype

console.log(Object.getPrototypeOf(myCat) === cat);  // true

Built-in Object Prototypes

All built-in JavaScript objects, like Arrays, Strings, Numbers, and so on, have prototypes that allow us to access methods like .toString(), .push(), .slice(), etc. These methods are inherited from their respective prototype objects.

Example with Array Prototype:

let arr = [1, 2, 3];

console.log(arr.__proto__ === Array.prototype);  // true
console.log(arr.push(4));  // Adds 4 to the array

The array arr inherits from Array.prototype, giving it access to array methods like .push(), .slice(), etc.

Modifying Built-in Prototypes

JavaScript allows developers to modify built-in object prototypes, though this is generally considered bad practice because it can lead to unexpected behavior.

Example of Modifying a Built-in Prototype:

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

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

In this case, we added a custom method reverse() to the String.prototype, allowing all strings to use this method. However, modifying built-in prototypes can lead to conflicts, especially in large projects or when using third-party libraries.

Benefits of Prototypes

  1. Memory Efficiency: Methods defined on prototypes are shared across all instances, reducing memory consumption compared to defining methods on each instance individually.
  2. Inheritance: Prototypes allow for easy inheritance of properties and methods between objects, enabling code reuse and shared behavior.
  3. Dynamic Extensions: Prototypes can be modified at runtime, allowing dynamic changes in behavior.

Limitations and Caution

  • Modifying Built-in Prototypes: As mentioned earlier, modifying built-in prototypes (like Array.prototype or String.prototype) is generally discouraged because it can lead to hard-to-debug errors.
  • Shadowing: Properties defined directly on an object will “shadow” properties with the same name on the prototype. The object will use its own property and ignore the prototype’s property unless it is deleted.

Example of Shadowing:

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

Dog.prototype.bark = function() {
  console.log("Woof!");
};

let myDog = new Dog("Fido");
myDog.bark();  // Output: "Woof!"

myDog.bark = function() {
  console.log("Meow!");  // Overrides the prototype method
};

myDog.bark();  // Output: "Meow!" (shadowing the prototype method)

Summary

  • Prototypes in JavaScript allow objects to inherit properties and methods from other objects, forming a prototype chain.
  • Every object has a prototype, which is either inherited from another object or set explicitly.
  • __proto__ refers to the prototype of an object, while prototype is a property on constructor functions that defines the prototype for instances.
  • Prototypes enable JavaScript to implement inheritance and method sharing efficiently.
  • Although powerful, modifying built-in prototypes should be done cautiously to avoid potential conflicts.

Prototypes are a crucial concept to master in order to fully understand JavaScript’s object-oriented nature and inheritance system.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *