The Prototype Pattern is a fundamental creational design pattern in JavaScript that leverages the language's built-in prototypal inheritance mechanism. It enables you to create new objects that inherit properties and methods from a pre-existing prototype object. This approach fosters code reusability, memory efficiency, and a clear object-oriented structure in your applications.
__proto__
(or prototype
in older browsers). This prototype acts as a template from which the object inherits properties and methods. If a property or method is not directly found on the object itself, JavaScript searches the prototype chain to locate it.new
keyword with a constructor function, the object inherits properties and methods from its prototype. This inheritance hierarchy is known as the prototype chain.this
keyword to assign properties to the object being created.Define the Prototype Object: Create an object that holds the shared properties and methods you want to inherit across objects of the same type.
function Person(name, age) {
this.name = name;
this.age = age;
}
// Prototype object
Person.prototype = {
greet: function() {
console.log("Hello, my name is " + this.name + ".");
}
};
Create New Objects: Use object literals or the new
keyword with the constructor function to create new objects. These objects inherit the properties and methods from the prototype.
var person1 = new Person("Alice", 30);
var person2 = new Person("Bob", 25);
person1.greet(); // Output: Hello, my name is Alice.
person2.greet(); // Output: Hello, my name is Bob.
Person
constructor function takes name
and age
as arguments and assigns them to the new object’s properties.Person.prototype
object defines the greet
method, which logs a greeting message using the object’s name
property.person1
and person2
instances, they inherit the greet
method from the Person.prototype
.Constructor Functions vs. ES6 Classes: While constructor functions with prototypes are the foundation, ES6 introduced classes for a more class-like syntax. However, classes are still syntactic sugar built upon prototypes under the hood.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log("Hello, my name is " + this.name + ".");
}
}
Inheritance Chaining: Objects can inherit from multiple prototypes, forming a chain of inheritance. Consider scenarios where you have different types of employees who all share basic Person
properties but also have specific employee-related properties.
function Employee(name, age, department) {
Person.call(this, name, age); // Inherit from Person
this.department = department;
}
Employee.prototype = Object.create(Person.prototype); // Inherit prototype from Person
Employee.prototype.constructor = Employee; // Reset constructor to avoid confusion
var employee1 = new Employee("Charlie", 40, "Engineering");
employee1.greet(); // Hello, my name is Charlie.
console.log(employee1.department); // Output: Engineering
The basic Prototype Pattern exposes all properties and methods defined in the prototype object publicly. However, in some cases, you might want to restrict direct access to certain methods while still allowing them to be inherited. This is where the Revealing Prototype Pattern comes in.
function Car(make, model) {
this.make = make;
this.model = model;
// Private function for internal calculations (not directly accessible)
function calculateHorsepower() {
// ... calculation logic here
}
// Publicly exposed method using the private function
this.getHorsepower = function() {
return calculateHorsepower(); // Access private function internally
};
}
// Prototype definition with only the public method exposed
Car.prototype = {
getHorsepower: this.getHorsepower // Reference to the public method closure
};
var car1 = new Car("Ford", "Mustang");
car1.getHorsepower(); // This works (public access)
car1.calculateHorsepower(); // This throws an error (not exposed publicly)
Car
constructor defines private methods like calculateHorsepower
within a function closure.getHorsepower
, is defined that can access and utilize the private method.Car.prototype
object only exposes the getHorsepower
method, making it publicly accessible on instances.The Object.create()
method provides a more flexible way to create new objects with a specified prototype. It allows you to dynamically set the prototype at object creation time.
var personPrototype = {
greet: function() {
console.log("Hello!");
}
};
var employeePrototype = Object.create(personPrototype); // Inherit from personPrototype
employeePrototype.work = function() {
console.log("Doing some work...");
};
var employee1 = Object.create(employeePrototype);
employee1.greet(); // Output: Hello! (inherited)
employee1.work(); // Output: Doing some work... (specific to employee)
personPrototype
object defines the base greet
method.employeePrototype
is created using Object.create()
, inheriting from personPrototype
. It adds the work
method specific to employees.employee1
object is created using Object.create()
, inheriting from employeePrototype
.Mixins are objects that contain reusable functionalities that can be “mixed in” to other objects using inheritance or object composition. This promotes modularity and code reuse for common functionalities across various object types.
var addressMixin = {
getAddress: function() {
return this.street + ", " + this.city + ", " + this.state;
}
};
function Person(name, age) {
this.name = name;
this.age = age;
}
Object.assign(Person.prototype, addressMixin); // Mixin inheritance
function Company(name, location) {
this.name = name;
this.location = location;
}
Object.assign(Company.prototype, addressMixin); // Mixin inheritance
var person1 = new Person("Alice", 30);
person1.street = "123 Main St";
person1.city = "Anytown";
person1.state = "CA";
console.log(person1.getAddress()); // Output: 123 Main St, Anytown, CA
var company1 = new Company("Acme Corp", "New York");
company1.street = "456 Wall St";
console.log(company1.getAddress()); // Output: 456 Wall St, New York, undefined (state not defined)
addressMixin
defines a reusable getAddress
method.Person
and Company
constructors inherit the mixin using Object.assign
, gaining access to the getAddress
method.person1
set address properties, they can utilize the inherited getAddress
method.The Prototype Pattern is a cornerstone of object-oriented programming in JavaScript. It offers significant advantages:Code reusability through shared properties and methods in the prototype. Memory efficiency by minimizing duplicate data storage across objects. Flexibility for adding or modifying properties and methods in the prototype for inheritance. By mastering the core concepts, advanced topics, and variations of the Prototype Pattern, you'll be well-equipped to create robust and maintainable object-oriented applications in JavaScript. Happy coding !❤️