1. 程式人生 > >一篇文章圖文並茂地帶你輕鬆學完 JavaScript 設計模式(一)

一篇文章圖文並茂地帶你輕鬆學完 JavaScript 設計模式(一)

## 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.