Object Creation and Inheritance Patterns in JavaScript
Defining Classes in ES5
In the ES5 standard, classes are implemented using constructor functions. There are several patterns for assigning properties and methods.
Instance-Level Definitions
Methods and properties can be defined directly inside the constructor function. While this allows for easy parameterization, it causes memory overhead because every new instance receives its own copy of the methods.
function Gadget(model, price) {
this.model = model;
this.price = price;
this.type = 'Hardware';
this.startup = function () {
console.log(this.model + ' is booting up...');
};
}
Prototype-Level Definitions
To optimize memory, shared methods and static-like properties are typically attached to the prototype. This ensures that all instances point to the same memory location for these functions.
function Gadget(model) {
this.model = model;
}
Gadget.prototype.type = 'Hardware';
Gadget.prototype.displayInfo = function () {
console.log('Model: ' + this.model);
};
// Alternative literal syntax for prototype assignment
Gadget.prototype = {
constructor: Gadget,
type: 'Hardware',
displayInfo: function () {
console.log('Model: ' + this.model);
}
};
In practice, developers combine these approaches: instance-specific data (unique attributes) is placed in the constructor, while shared behavior (methods) is placed on the prototype.
Modern ES6 Class Syntax
ES6 introduced a more declarative syntax that mirrors traditional class-based languages, though it remains based on prototypes under the hood.
class Vehicle {
constructor(make, year) {
this.make = make;
this.year = year;
this.engineStatus = 'off';
}
identify() {
console.log(`This is a ${this.year} ${this.make}.`);
}
}
class Sedan extends Vehicle {
constructor(make, year, doors) {
// super() invokes the parent constructor
super(make, year);
this.doors = doors;
}
honk() {
console.log('Beep beep!');
}
}
const myCar = new Sedan('Toyota', 2022, 4);
myCar.identify(); // Output: This is a 2022 Toyota.
myCar.honk(); // Output: Beep beep!
Inheritance Patterns in ES5
Prototype Chain Inheritance
This pattern involves setting the child's prototype to a new instance of the parent.
function Parent() {
this.traits = ['patient', 'kind'];
}
Parent.prototype.sayHello = function() {
console.log('Hello');
};
function Child() {}
Child.prototype = new Parent();
const kid = new Child();
kid.sayHello(); // Inherited from Parent prototype
- Pros: Simple to implement; inherits from both the instance and the prototype.
- Cons: Cannot pass arguments to the parent during child instantiation; all child instances share the same reference-type properties (like arrays).
Constructor Stealing (Functional Inheritance)
By using .call() or .apply(), the child executes the parent's constructor in its own context.
function Base(id) {
this.id = id;
}
function Extended(id, label) {
Base.call(this, id); // Inherit instance properties
this.label = label;
}
const obj = new Extended(101, 'Main');
console.log(obj.id); // 101
- Pros: Supports multiple inheritance and argument passing.
- Cons: Only inherits instance properties; methods defined on the parent's prototype are inaccessible.
Parasitic Combination Inheritance
This is widely considered the most efficient inheritance pattern in ES5. It avoids calling the parent constructor twice while maintaining a clean prototype chain.
function Animal(species) {
this.species = species;
}
Animal.prototype.move = function() {
console.log(this.species + ' is moving.');
};
function Bird(species, flySpeed) {
Animal.call(this, species); // Step 1: Inherit properties
this.flySpeed = flySpeed;
}
// Step 2: Inherit methods via a clean prototype link
(function() {
var Surrogate = function() {};
Surrogate.prototype = Animal.prototype;
Bird.prototype = new Surrogate();
Bird.prototype.constructor = Bird;
})();
const eagle = new Bird('Eagle', '50mph');
eagle.move(); // Eagle is moving.
This method isolates the prototype linking from the execution of the parent constructor logic, ensuring instances are initialized correctly without redundant property overhead.