Design Patterns in Front-End JavaScript Development
This article explains how to apply common design patterns such as Singleton and Observer in front‑end JavaScript, demonstrating practical extensions using Function.prototype, discussing the Open‑Closed Principle, and providing code examples for flexible, maintainable code without invasive modifications.
In back‑end languages design patterns are widely used (e.g., factory, decorator, singleton), but front‑end code often hides them or avoids them. The article starts with a practical requirement: extend the page.init function with additional behavior without breaking existing logic.
let page = {
init: () => {
// many business codes
},
...
};A straightforward approach adds new code directly inside page.init , which is invasive and fragile, especially when dealing with minified or third‑party code. To solve this, the author introduces a prototype‑based extension method that appends a function after the original one.
Function.prototype.fnAfter = function (fn) {
var _self = this;
return function () {
_self.apply(this, arguments);
fn.apply(this, arguments);
};
};
page.init = (page.init || function () {}).fnAfter(function () {
console.log('我们要追加的功能成功啦~');
});
page.init();To make the extension chainable like jQuery, the method is refined to return the original result, enabling multiple successive fnAfter calls.
Function.prototype.fnAfter = function (fn) {
var _self = this;
return function () {
var fnOrigin = _self.apply(this, arguments);
fn.apply(this, arguments);
return fnOrigin;
};
};The article then connects this technique to the Open‑Closed Principle (OCP): software entities should be open for extension but closed for modification. Several classic front‑end design patterns are presented.
Singleton Pattern
var fn = function () { this.instance = null; };
fn.getInstance = function () {
if (!this.instance) {
this.instance = new fn();
}
return this.instance;
};
var fnA = fn.getInstance();
var fnB = fn.getInstance();
console.log(fnA === fnB); // trueA lazy singleton example shows how to create a modal dialog that is instantiated only when needed, avoiding unnecessary DOM elements.
var createModal = (function () {
var modal = null;
return function () {
if (!modal) {
modal = document.createElement('div');
modal.style.display = 'none';
document.getElementById('container').append(modal);
}
return modal;
};
})();
document.getElementById('showModal').click(function () {
var modal = createModal();
modal.style.display = 'block';
});Observer (Publish‑Subscribe) Pattern
var observal = {
eventObj: {},
listen: function (key, fn) {
this.eventObj[key] = this.eventObj[key] || [];
this.eventObj[key].push(fn);
},
trigger: function (key) {
var list = this.eventObj[key];
if (!list) return;
for (var i = 0; i < list.length; i++) {
list[i].apply(this, arguments);
}
}
};
observal.listen('command1', function () { console.log('黑夜给了我夜色的眼睛~'); });
observal.listen('command1', function () { console.log('我却用它寻找光明~'); });
observal.listen('command2', function () { console.log('一花一世界~'); });
observal.listen('command2', function () { console.log('一码一人生~'); });
observal.trigger('command1');
observal.trigger('command2');Using this pattern decouples publishers from subscribers, improving flexibility and robustness.
The article warns against premature optimization, suggesting that patterns should be introduced when the codebase shows signs of rigidity or when specific scenarios demand them (e.g., using Redux for complex state sharing). It concludes with a light‑hearted joke and references for further reading.
Hujiang Technology
We focus on the real-world challenges developers face, delivering authentic, practical content and a direct platform for technical networking among developers.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.