一篇文章圖文並茂地帶你輕鬆學完 JavaScript 設計模式(一)
阿新 • • 發佈:2021-02-07
## JavaScript 設計模式(一)
本文需要讀者至少擁有基礎的 `ES6` 知識,包括 `Proxy`, `Reflect` 以及 `Generator` 函式等。
至於這次為什麼分了兩篇文章,有損傳統以及標題的正確性,是這樣的。
其實放在一篇文章裡也可以,但是希望讀者能夠更加輕鬆點,文章太長也會導致陷入閱讀疲倦中。
因此希望讀者理解。
### 1. 工廠模式
`JavaScript` 寄生模式就是一種 工廠模式,具體可以參考我的關於 [JavaScript 繼承 ](https://www.cnblogs.com/huro/p/14372289.html)這篇文章,這裡不再細談寄生模式。
工廠模式是用工廠方法代替 `new` 的一種設計模式。
先看一個工廠模式的具體例子
```js
class Product {
constructor(name) {
this.name = name;
}
}
class Factory {
static create(name) {
return new Product(name);
}
}
Factory.create("product1");
Factory.create("product2");
```
通過這種設計模式,我們可以少寫一個 `new`
在 `jQuery` 原始碼中,這種設計模式也有體現
```js
$('#div'); // 我們會這樣傳入一個 selector 返回一個 jQuery.fn.init 物件
```
下面我們具體看原始碼中的內容,以下是我簡化過的原始碼
```js
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` 上定義方法的,所以隱隱約約的,實現了屬性的獨立和方法的共享,節省了記憶體空間。
![](https://img2020.cnblogs.com/blog/2286610/202102/2286610-20210207172921537-356785628.png)
### 2. 單例模式
`JavaScript` 中沒有很好的單例模式的實現,究其原因,是因為沒有 `private` 關鍵字保護建構函式,現在最新的語法提案已經提出利用 `#` 字代表私有屬性或方法,可能幾年後就有了。如:
```js
class Person {
#name // 代表是一個私有屬性
}
```
目前單例模式我們一般這樣實現
```js
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!
```
這種設計模式在登入框或是註冊框,只要是單一使用的場景,可以應用。
```js
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) 去訂閱。被觀察者(釋出者)要釋出的時候也找那個中間商。只有中間商知道誰釋出了誰訂閱了,並及時推送資訊。
這裡借用柳樹的一張圖片,如果侵權,請聯絡我,我將立馬刪除。
![](https://img2020.cnblogs.com/blog/2286610/202102/2286610-20210207172852088-1882722329.jpg)
具體觀察者模式實現如下
```js
// 觀察者模式
// 被觀察者
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. 釋出訂閱模式
```js
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 設計模式(二)](https://www.cnblogs.com/huro/p/14386028.