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
迴圈等進行探討,歡迎讀者閱讀。