JavaScript Object-Oriented Programming: Factory, Constructor, Prototype, Hybrid, and Dynamic Prototype Patterns
In JavaScript, object-oriented design structures complex modules as reusable, encapsulated units with properties and behaviors. Below are practical implementations of five core patterns:
Factory Pattern
The factory pattern encapsulates object creation in a function to produce multiple similar structures. Since ECMAScript lacks native class definitions prior to ES6, this approach uses a wrapper to define properties and methods explicitly before returning the instance.
function buildProduct(title, sku) {
const item = new Object();
item.title = title;
item.sku = sku;
item.displaySku = function() {
console.log(this.sku);
};
return item;
}
const laptop = buildProduct('Gaming Laptop', 'LAP-GM-2024');
This pattern simplifies creating repeated objects but fails to distinguish instance types—all instances are generic Objects.
Constructor Pattern
Constructor functions create typed objects (similar to built-ins like Array or Date). Key conventions include capitalizing the function name and using new to instantiate.
function Product(title, sku) {
this.title = title;
this.sku = sku;
this.displayTitle = function() {
console.log(this.title);
};
}
const keyboard = new Product('Mechanical Keyboard', 'KB-MEC-108');
console.log(keyboard instanceof Product); // true
A major flaw is that methods are redefined for every new instance, wasting memory.
Prototype Pattern
Each JavaScript function has a prototype proeprty pointing to an object shared by all instances of that function type. This enables method and property reuse across instances.
function Product() {}
Product.prototype.title = 'Wireless Mouse';
Product.prototype.sku = 'MOUSE-WL-2024';
Product.prototype.colors = ['black', 'white', 'gray'];
Product.prototype.showDetails = function() {
console.log(`${this.title} (${this.sku}) - ${this.colors.join(', ')}`);
};
const mouse1 = new Product();
const mouse2 = new Product();
mouse1.colors.pop();
mouse1.title = 'Ergonomic Wireless Mouse';
mouse2.title = 'RGB Wireless Mouse';
mouse1.showDetails(); // Ergonomic Wireless Mouse (MOUSE-WL-2024) - black, white
mouse2.showDetails(); // RGB Wireless Mouse (MOUSE-WL-2024) - black, white
Shared reference types (like arrays) are modified across all instances, and initialization parameters are omitted by default.
Hybrid Pattern (Constructor + Prototype)
This widely adopted pattern combines constructer initialization for unique instance properties with prototype definitions for shared methods and constants.
function Product(title, sku, colors) {
this.title = title;
this.sku = sku;
this.colors = colors;
}
Product.prototype.showDetails = function() {
console.log(`${this.title} (${this.sku}) - ${this.colors.join(', ')}`);
};
const monitor = new Product('4K Monitor', 'MON-4K-27', ['silver', 'black']);
const speaker = new Product('Bluetooth Speaker', 'SPK-BT-5.3', ['blue', 'red']);
monitor.colors.pop();
monitor.showDetails(); // 4K Monitor (MON-4K-27) - silver
speaker.showDetails(); // Bluetooth Speaker (SPK-BT-5.3) - blue, red
It balances memory efficiency, encapsulation, and initialization flexibility.
Dynamic Prototype Pattern
This pattern encapsulates all logic in a single constructor function, initializing the prototype only once when the first instance is created.
function Product(title, sku) {
this.title = title;
this.sku = sku;
if (typeof this.showDetails !== 'function') {
console.log('Initializing prototype');
Product.prototype.showDetails = function() {
console.log(`${this.title} (${this.sku})`);
};
}
}
const webcam = new Product('HD Webcam', 'CAM-HD-1080');
const headset = new Product('Noise Cancelling Headset', 'HEAD-NC-2024');
The prototype initialization logic runs exactly once (for webcam), making subsequent instances (like headset) reuse the existing definition.