JavaScript Object-Oriented Programming: Prototypes and Inheritance Patterns
Programming Paradigms
Procedural programming approaches problem-solving through sequential decomposition. Solutions are implemented as step-by-step procedures where functions execute in a predetermined order to transform input into output.
Object-oriented programming organizes code around data structures (objects) rather than logic flows. This paradigm emphasizes:
- Encapsulation: Bundling data with methods that operate on that data
- Inheritance: Creating hierarchical relationships between objects
- Polymorphism: Allowing objects to be treated as instances of their parent types
Constructor Functions and Encapsulation
JavaScript implements encapsulation through constructor functions. These blueprints instantiate independent objects with isolated state:
function User(name, age) {
this.name = name;
this.age = age;
this.profile = function() {
return `${this.name} is ${this.age} years old`;
};
}
While constructors provide encapsulation, defining methods inside them creates redundant copies for each instance, consuming excessive memory.
The Prototype Object
JavaScript constructors possess a prototype property referencing a shared object. Attaching methods to this object eliminates per-instance duplication:
function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.display = function() {
console.log(`${this.name}: ${this.age}`);
};
Within both constructors and prototype methods, this references the instantiated instance.
The constructor Property
Prototype objects contain a constructor property pointing back to their originating function. When reassigning the entire prototype object, this reference breaks:
User.prototype = {
login: function() { console.log('authenticated'); },
logout: function() { console.log('session ended'); }
};
// User.prototype.constructor is now Object, not User
Restore the reference explicitly:
User.prototype = {
constructor: User,
login: function() { console.log('authenticated'); }
};
Instance Prototype Linkage
Every object instance contains an internal [[Prototype]] slot (accessible via __proto__ in legacy environments) linking to its constructor's prototype. This connection enables property delegation—when accessing instance.property, the lookup traverses:
- The enstance itself
- The linked prototype object
- The prototype's prototype (up the chain)
The constructor property on an instance's prototype points to the function that created the instance.
Prototypal Inheritance
JavaScript implements inheritance through prototype delegation rather than classical class extension. Establish a parenet-child relationship by assigning a parent instance to the child's prototype:
function Organism() {
this.cells = true;
this.metabolism = 'active';
}
function Mammal() {
this.warmBlooded = true;
}
Mammal.prototype = new Organism();
Mammal.prototype.constructor = Mammal;
function Bird() {
this.hasFeathers = true;
}
Bird.prototype = new Organism();
Bird.prototype.constructor = Bird;
Bird.prototype.fly = function() {
console.log('airborne');
};
This pattern allows Mammal and Bird instances to access Organism properties while maintaining separate constructor references and specialized methods.
The Prototype Chain
The prototype mechanism creates a linked chain terminating at Object.prototype. Property resolution follows this hierarchy:
- Search the local object properties
- Traverse to
[[Prototype]](the constructor's prototype) - Continue to
[[Prototype]]of that prototype - Terminate at
nullif undefined
The instanceof operator validates prototype chain membership:
function Vehicle() {}
const motorcycle = new Vehicle();
console.log(motorcycle instanceof Vehicle); // true
console.log(motorcycle instanceof Object); // true
console.log(Array instanceof Object); // true