一篇文章圖文並茂地帶你輕鬆學完 JavaScript 設計模式(一)
- 2021 年 2 月 7 日
- 筆記
JavaScript 設計模式(一)
本文需要讀者至少擁有基礎的 ES6 知識,包括 Proxy, Reflect 以及 Generator 函數等。
至於這次為什麼分了兩篇文章,有損傳統以及標題的正確性,是這樣的。
其實放在一篇文章里也可以,但是希望讀者能夠更加輕鬆點,文章太長也會導致陷入閱讀疲倦中。
因此希望讀者理解。
1. 工廠模式
JavaScript 寄生模式就是一種 工廠模式,具體可以參考我的關於 JavaScript 繼承 這篇文章,這裡不再細談寄生模式。
工廠模式是用工廠方法代替 new 的一種設計模式。
先看一個工廠模式的具體例子
class Product {
constructor(name) {
this.name = name;
}
}
class Factory {
static create(name) {
return new Product(name);
}
}
Factory.create("product1");
Factory.create("product2");
通過這種設計模式,我們可以少寫一個 new
在 jQuery 源碼中,這種設計模式也有體現
$('#div'); // 我們會這樣傳入一個 selector 返回一個 jQuery.fn.init 對象
下面我們具體看源碼中的內容,以下是我簡化過的源碼
function jQuery(selector) {
return new jQuery.fn.init(selector)
}
jQuery.fn = jQuery.prototype; // 簡化 原型方法 書寫
jQuery.fn.eat = function() {
console.log(`${this.name} eat!`);
return this;
}
const init = jQuery.fn.init = function(selector) {
this.name = selector;
}
// 使得 jQuery.fn.init.prototype 與 jQuery.prototype 保持一致
// 用以使用 jQuery.prototype 即 jQuery.fn 上定義的方法或屬性
init.prototype = jQuery.prototype;
// 工廠模式
window.$ = function(selector) {
return new jQuery(selector);
}
console.log($("huro").eat())
在 jQuery 實現的源碼中,還是比較繞的,這種繞,其實這樣隱隱約約的實現了組合寄生繼承,分離了屬性和方法。
因為這個時候屬性例如 this.name 會在實例 new jQuery.fn.init() 上,
而這個實例的 __proto__ 指向 jQuery.prototype ,而我們是在 jQuery.prototype 上定義方法的,所以隱隱約約的,實現了屬性的獨立和方法的共享,節省了記憶體空間。

2. 單例模式
JavaScript 中沒有很好的單例模式的實現,究其原因,是因為沒有 private 關鍵字保護構造函數,現在最新的語法提案已經提出利用 # 字代表私有屬性或方法,可能幾年後就有了。如:
class Person {
#name // 代表是一個私有屬性
}
目前單例模式我們一般這樣實現
class Singleton {
eat() {
console.log("huro eat!");
}
}
Singleton.getInstance = (() => {
let instance = null;
return () => {
if (instance === null) {
instance = new Singleton();
}
return instance;
};
})();
const obj1 = Singleton.getInstance();
const obj2 = Singleton.getInstance();
console.log(obj1 === obj2);
obj1.eat(); // huro eat!
這種設計模式在登錄框或是註冊框,只要是單一使用的場景,可以應用。
class LoginForm {
constructor() {
this.display = "none";
}
show() {
if (this.display === "block") {
console.log("already show!");
}
else {
this.display = "block";
}
}
hide() {
if (this.display === "none") {
console.log("already hide!");
}
else {
this.display = "none";
}
}
}
LoginForm.getInstance = (() => {
let instance = null;
return () => {
if (instance === null) {
instance = new LoginForm();
}
return instance;
}
})();
const login1 = LoginForm.getInstance();
const login2 = LoginForm.getInstance();
console.log(login1 === login2);
login1.show();
login2.show(); // already show!
3. 觀察者模式
類似於發布訂閱,實際上就是當被觀察者改變的時候通知觀察者。
但是觀察者模式是,觀察者主動去調用被觀察者的函數去觀察。
發布訂閱模式是,觀察者(訂閱者)去找一個中間商 (Bus) 去訂閱。被觀察者(發布者)要發布的時候也找那個中間商。只有中間商知道誰發布了誰訂閱了,並及時推送資訊。
這裡借用柳樹的一張圖片,如果侵權,請聯繫我,我將立馬刪除。

具體觀察者模式實現如下
// 觀察者模式
// 被觀察者
class Subject {
constructor() {
this.state = 0;
this.observers = [];
}
change(fn) {
fn();
this.notifyAll();
}
increase(num) {
this.change(() => {
this.state += num;
})
}
multiply(num) {
this.change(() => {
this.state *= num;
})
}
notifyAll() {
this.observers.forEach(observer => {
observer();
})
}
observe(fn) {
this.observers.push(fn);
}
}
class Observer {
constructor({
subject,
name,
fn
}) {
subject.observe(fn);
this.name = name;
}
}
const subject = new Subject();
const ob1 = new Observer({
name: 'ob1',
subject,
fn: () => console.log("ob1 observe object")
})
const ob2 = new Observer({
name: 'ob2',
subject,
fn: () => console.log("ob2 observe object")
})
subject.increase(2);
4. 發布訂閱模式
class Emitter {
constructor() {
this.map = new Map();
}
on(name, fn) {
if (!this.map.has(name)) {
this.map.set(name, []);
}
const origin = this.map.get(name);
this.map.set(name, [...origin, fn]);
}
emit(name) {
const events = this.map.get(name);
if (events === undefined) {
return;
}
events.forEach(fn => {
fn();
})
}
}
const emitter = new Emitter();
emitter.on('click', () => {
console.log("huro");
})
emitter.on('click', () => {
console.log("huro");
})
emitter.on('mousemove', () => {
console.log("huro");
})
emitter.emit('click'); // huro huro
感覺有那味道了,好像這種實現有點類似於瀏覽器的 addEventListener 只不過 emit 是由用戶的 click 等事件去觸發的。
總結
本文共介紹了四種設計模式,並在源碼層面上給與了實現,部分設計模式也給出了相應的例子,下篇文章中,會繼續探討四種設計模式,分別是 代理模式,迭代器模式,裝飾器模式以及狀態模式,並結合 Promise 實現, 對象的 for of 循環等進行探討,歡迎讀者閱讀。
一篇文章圖文並茂地帶你輕鬆學完 JavaScript 設計模式(二)。

