摘要:本文已同步到中常見的設計模式如果感覺寫的還可以,就給個小星星吧,歡迎和收藏。本文中關于各種設計模式定義都是引用書中的,部分引用自百度百科已標出。下面把我整理出的常用設計模式按類型做個表格整理。
本文已同步到Github JavaScript中常見的設計模式,如果感覺寫的還可以,就給個小星星吧,歡迎star和收藏。
最近拜讀了曾探大神的《JavaScript設計模式與開發實踐》,真是醍醐灌頂,猶如打通任督二脈的感覺,讓我對JavaScript的理解加深了很多。
本文中關于各種設計模式定義都是引用書中的,部分引用自百度百科已標出。另外,本文中所舉例子大多是書中的,自已做了一些修改和補充,用ES6(書中都是ES5的方式)的方式實現,以加深自己對“類”的理解,并不是自己來講解設計模式,主要是做一些筆記以方便自己過后復習與加深理解,同時也希望把書中典型的例子整理出來和大家分享,共同探討和進步。
一提起設計模式,相信大家都會脫口而出,23種設計模式,五大設計原則。這里就不說了,奈何我功力遠遠不夠啊。下面把我整理出的常用JavaScript設計模式按類型做個表格整理。本文較長,如果閱讀起來不方便,可鏈接到我的github中,多帶帶查看每一種設計模式。先整理這些,后續會繼續補充,感興趣的同學可以關注。
模式分類 | 名稱 |
---|---|
創建型 | 工廠模式 |
單例模式 | |
原型模式 | |
結構型 | 適配器模式 |
代理模式 | |
行為型 | 策略模式 |
迭代器模式 | |
觀察者模式(發布-訂閱模式) | |
命令模式 | |
狀態模式 |
工廠模式中,我們在創建對象時不會對客戶端暴露創建邏輯,并且是通過使用一個共同的接口來指向新創建的對象,用工廠方法代替new操作的一種模式。
class Creator { create(name) { return new Animal(name) } } class Animal { constructor(name) { this.name = name } } var creator = new Creator() var duck = creator.create("Duck") console.log(duck.name) // Duck var chicken = creator.create("Chicken") console.log(chicken.name) // Chicken
小結:
構造函數和創建者分離,對new操作進行封裝
符合開放封閉原則
單例模式舉一個書中登錄框的例子,代碼如下:
小結:
1.單例模式的主要思想就是,實例如果已經創建,則直接返回
function creatSingleton() { var obj = null // 實例如已經創建過,直接返回 if (!obj) { obj = xxx } return obj }
2.符合開放封閉原則
原型模式用原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象。--百度百科
在JavaScript中,實現原型模式是在ECMAScript5中,提出的Object.create方法,使用現有的對象來提供新創建的對象的__proto__。
var prototype = { name: "Jack", getName: function() { return this.name } } var obj = Object.create(prototype, { job: { value: "IT" } }) console.log(obj.getName()) // Jack console.log(obj.job) // IT console.log(obj.__proto__ === prototype) //true
更多關于prototype的知識可以看我之前的JavaScript中的面向對象、原型、原型鏈、繼承,下面列一下關于prototype的一些使用方法
1. 方法繼承
var Parent = function() {} Parent.prototype.show = function() {} var Child = function() {} // Child繼承Parent的所有原型方法 Child.prototype = new Parent()
2. 所有函數默認繼承Object
var Foo = function() {} console.log(Foo.prototype.__proto__ === Object.prototype) // true
3. Object.create
var proto = {a: 1} var propertiesObject = { b: { value: 2 } } var obj = Object.create(proto, propertiesObject) console.log(obj.__proto__ === proto) // true
4. isPrototypeOf
prototypeObj是否在obj的原型鏈上
prototypeObj.isPrototypeOf(obj)
5. instanceof
contructor.prototype是否出現在obj的原型鏈上
obj instanceof contructor
6. getPrototypeOf
Object.getPrototypeOf(obj) 方法返回指定對象obj的原型(內部[[Prototype]]屬性的值)
Object.getPrototypeOf(obj)
7. setPrototypeOf
設置一個指定的對象的原型 ( 即, 內部[[Prototype]]屬性)到另一個對象或 null
var obj = {} var prototypeObj = {} Object.setPrototypeOf(obj, prototypeObj) console.log(obj.__proto__ === prototypeObj) // true結構型模式 適配器模式
舉一個書中渲染地圖的例子
class GooleMap { show() { console.log("渲染谷歌地圖") } } class BaiduMap { show() { console.log("渲染百度地圖") } } function render(map) { if (map.show instanceof Function) { map.show() } } render(new GooleMap()) // 渲染谷歌地圖 render(new BaiduMap()) // 渲染百度地圖
但是假如BaiduMap類的原型方法不叫show,而是叫display,這時候就可以使用適配器模式了,因為我們不能輕易的改變第三方的內容。在BaiduMap的基礎上封裝一層,對外暴露show方法。
class GooleMap { show() { console.log("渲染谷歌地圖") } } class BaiduMap { display() { console.log("渲染百度地圖") } } // 定義適配器類, 對BaiduMap類進行封裝 class BaiduMapAdapter { show() { var baiduMap = new BaiduMap() return baiduMap.display() } } function render(map) { if (map.show instanceof Function) { map.show() } } render(new GooleMap()) // 渲染谷歌地圖 render(new BaiduMapAdapter()) // 渲染百度地圖
小結:
適配器模式主要解決兩個接口之間不匹配的問題,不會改變原有的接口,而是由一個對象對另一個對象的包裝。
適配器模式符合開放封閉原則
代理模式本文舉一個使用代理對象加載圖片的例子來理解代理模式,當網絡不好的時候,圖片的加載需要一段時間,這就會產生空白,影響用戶體驗,這時候我們可在圖片真正加載完之前,使用一張loading占位圖片,等圖片真正加載完再給圖片設置src屬性。
class MyImage { constructor() { this.img = new Image() document.body.appendChild(this.img) } setSrc(src) { this.img.src = src } } class ProxyImage { constructor() { this.proxyImage = new Image() } setSrc(src) { let myImageObj = new MyImage() myImageObj.img.src = "file://xxx.png" //為本地圖片url this.proxyImage.src = src this.proxyImage.onload = function() { myImageObj.img.src = src } } } var proxyImage = new ProxyImage() proxyImage.setSrc("http://xxx.png") //服務器資源url
本例中,本體類中有自己的setSrc方法,如果有一天網絡速度已經不需要預加載了,我們可以直接使用本體對象的setSrc方法,,并且不需要改動本體類的代碼,而且可以刪除代理類。
// 依舊可以滿足需求 var myImage = new MyImage() myImage.setSrc("http://qiniu.sunzhaoye.com/CORS.png")
小結:
代理模式符合開放封閉原則
本體對象和代理對象擁有相同的方法,在用戶看來并不知道請求的本體對象還是代理對象。
行為型模式 策略模式定義一系列的算法,把它們一個個封裝起來,并使它們可以替換
var fnA = function(val) { return val * 1 } var fnB = function(val) { return val * 2 } var fnC = function (val) { return val * 3 } var calculate = function(fn, val) { return fn(val) } console.log(calculate(fnA, 100))// 100 console.log(calculate(fnB, 100))// 200 console.log(calculate(fnC, 100))// 300迭代器模式
直接上代碼, 實現一個簡單的迭代器
class Creater { constructor(list) { this.list = list } // 創建一個迭代器,也叫遍歷器 createIterator() { return new Iterator(this) } } class Iterator { constructor(creater) { this.list = creater.list this.index = 0 } // 判斷是否遍歷完數據 isDone() { if (this.index >= this.list.length) { return true } return false } next() { return this.list[this.index++] } } var arr = [1, 2, 3, 4] var creater = new Creater(arr) var iterator = creater.createIterator() console.log(iterator.list) // [1, 2, 3, 4] while (!iterator.isDone()) { console.log(iterator.next()) // 1 // 2 // 3 // 4 }
ES6中的迭代器:
JavaScript中的有序數據集合包括:
Array
Map
Set
String
typeArray
arguments
NodeList
注意: Object不是有序數據集合
以上有序數據集合都部署了Symbol.iterator屬性,屬性值為一個函數,執行這個函數,返回一個迭代器,迭代器部署了next方法,調用迭代器的next方法可以按順序訪問子元素
以數組為例測試一下,在瀏覽器控制臺中打印測試如下:
var arr = [1, 2, 3, 4] var iterator = arr[Symbol.iterator]() console.log(iterator.next()) // {value: 1, done: false} console.log(iterator.next()) // {value: 2, done: false} console.log(iterator.next()) // {value: 3, done: false} console.log(iterator.next()) // {value: 4, done: false} console.log(iterator.next()) // {value: undefined, done: true}
小結:
JavaScript中的有序數據集合有Array,Map,Set,String,typeArray,arguments,NodeList,不包括Object
任何部署了[Symbol.iterator]接口的數據都可以使用for...of循環遍歷
迭代器模式使目標對象和迭代器對象分離,符合開放封閉原則
觀察者模式(訂閱-發布模式)先實現一個簡單的發布-訂閱模式,代碼如下:
class Event { constructor() { this.eventTypeObj = {} } on(eventType, fn) { if (!this.eventTypeObj[eventType]) { // 按照不同的訂閱事件類型,存儲不同的訂閱回調 this.eventTypeObj[eventType] = [] } this.eventTypeObj[eventType].push(fn) } emit() { // 可以理解為arguments借用shift方法 var eventType = Array.prototype.shift.call(arguments) var eventList = this.eventTypeObj[eventType] for (var i = 0; i < eventList.length; i++) { eventList[i].apply(eventList[i], arguments) } } remove(eventType, fn) { // 如果使用remove方法,fn為函數名稱,不能是匿名函數 var eventTypeList = this.eventTypeObj[eventType] if (!eventTypeList) { // 如果沒有被人訂閱改事件,直接返回 return false } if (!fn) { // 如果沒有傳入取消訂閱的回調函數,則改訂閱類型的事件全部取消 eventTypeList && (eventTypeList.length = 0) } else { for (var i = 0; i < eventTypeList.length; i++) { if (eventTypeList[i] === fn) { eventTypeList.splice(i, 1) // 刪除之后,i--保證下輪循環不會漏掉沒有被遍歷到的函數名 i--; } } } } } var handleFn = function(data) { console.log(data) } var event = new Event() event.on("click", handleFn) event.emit("click", "1") // 1 event.remove("click", handleFn) event.emit("click", "2") // 不打印
以上代碼可以滿足先訂閱后發布,但是如果先發布消息,后訂閱就不滿足了。這時候我們可以稍微修改一下即可滿足先發布后訂閱,在發布消息時,把事件緩存起來,等有訂閱者時再執行。代碼如下:
class Event { constructor() { this.eventTypeObj = {} this.cacheObj = {} } on(eventType, fn) { if (!this.eventTypeObj[eventType]) { // 按照不同的訂閱事件類型,存儲不同的訂閱回調 this.eventTypeObj[eventType] = [] } this.eventTypeObj[eventType].push(fn) // 如果是先發布,則在訂閱者訂閱后,則根據發布后緩存的事件類型和參數,執行訂閱者的回調 if (this.cacheObj[eventType]) { var cacheList = this.cacheObj[eventType] for (var i = 0; i < cacheList.length; i++) { cacheList[i]() } } } emit() { // 可以理解為arguments借用shift方法 var eventType = Array.prototype.shift.call(arguments) var args = arguments var that = this function cache() { if (that.eventTypeObj[eventType]) { var eventList = that.eventTypeObj[eventType] for (var i = 0; i < eventList.length; i++) { eventList[i].apply(eventList[i], args) } } } if (!this.cacheObj[eventType]) { this.cacheObj[eventType] = [] } // 如果先訂閱,則直接訂閱后發布 cache(args) // 如果先發布后訂閱,則把發布的事件類型與參數保存起來,等到有訂閱后執行訂閱 this.cacheObj[eventType].push(cache) } }
小結:
發布訂閱模式可以使代碼解耦,滿足開放封閉原則
當過多的使用發布訂閱模式,如果訂閱消息始終都沒有觸發,則訂閱者一直保存在內存中。
命令模式--百度百科
在命令的發布者和接收者之間,定義一個命令對象,命令對象暴露出一個統一的接口給命令的發布者,而命令的發布者不用去管接收者是如何執行命令的,做到命令發布者和接收者的解耦。
舉一個如果頁面中有3個按鈕,給不同按鈕添加不同功能的例子,代碼如下:
狀態模式cmd-demo
舉一個關于開關控制電燈的例子,電燈只有一個開關,第一次按下打開弱光,第二次按下打開強光,第三次按下關閉。
state-demo
如果這時候需要增加一個超強光,則只需增加一個超強光的類,并添加pressBtn方法,改變強光狀態下,點擊開關需要把狀態更改為超強光,超強光狀態下,點擊開關把狀態改為關閉即可,其他代碼都不需要改動。
class StrongLightState { constructor(light) { this.light = light } pressBtn() { this.light.setState(this.light.superLightState) console.log("開啟超強光") } } class SuperLightState { constructor(light) { this.light = light } pressBtn() { this.light.setState(this.light.offLightState) console.log("關閉電燈") } } class Light { constructor() { this.offLightState = new OffLightState(this) this.weekLightState = new WeekLightState(this) this.strongLightState = new StrongLightState(this) this.superLightState = new SuperLightState(this) this.currentState = null } setState(newState) { this.currentState = newState } init() { this.currentState = this.offLightState } }
小結:
通過定義不同的狀態類,根據狀態的改變而改變對象的行為,二不必把大量的邏輯都寫在被操作對象的類中,而且容易增加新的狀態
符合開放封閉原則
終于到最后可,歷時多日地閱讀與理解,并記錄與整理筆記,目前整理出10中JavaScript中常見的設計模式,后續會對筆記繼續整理,然后加以補充。由于筆者功力比較淺,如有問題,還望大家多多指正,謝謝。
參考文章:
JavaScript設計模式與開發實踐
深入理解JavaScript系列/設計模式--湯姆大叔的博客
設計模式--菜鳥教程
JavaScript 中常見設計模式整理
ES6入門--阮一峰
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100758.html
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:原文鏈接常用設計模式設計模式設計模式是一種在長時間的經驗與錯誤中總結出來可服用的解決方案。用來模擬接口的相關操作我很帥通過適配器函數來調用目的我很帥學習資料聽飛狐聊設計模式系列設計模式湯姆大叔 原文鏈接: JavaScript常用設計模式 設計模式 設計模式是一種在長時間的經驗與錯誤中總結出來可服用的解決方案。 設計模式主要分為3類: 創建型設計模式:專注于處理對象的創建 Const...
摘要:打個比方源碼使用了模式,解決了問題,但是,在選擇模式解決問題的背后又有多少思考 showImg(https://segmentfault.com/img/bVbupTE?w=480&h=260); 【前言】 最近閱讀了《JavaScript設計模式與開發實踐》,收獲頗豐,于是想寫一點總結及感想 showImg(https://segmentfault.com/img/bVbupUE?w...
摘要:前言設計模式幾十種,閱讀了設計模式與開發實踐這本書后,個人感覺就是圍繞對象來設計的,發現日常寫代碼能用上的并不多,下面是常用的幾種設計模式。前端服務端可以參考我的另一個倉庫地址,一個簡單的實時聊天參考來自設計模式與開發實踐讀書筆記 前言 設計模式幾十種,閱讀了《JavaScript設計模式與開發實踐》這本書后,個人感覺js就是圍繞對象來設計的,發現日常寫代碼能用上的并不多,下面是常用的...
摘要:前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。背后的故事本文是對于年之間世界發生的大事件的詳細介紹,闡述了從提出到角力到流產的前世今生。 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為新聞熱點、開發教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目。歡迎...
摘要:學編程真的不是一件容易的事不管你多喜歡或是多會編程,在學習和解決問題上總會碰到障礙。熟練掌握核心內容,特別是和多線程初步具備面向對象設計和編程的能力掌握基本的優化策略。 學Java編程真的不是一件容易的事,不管你多喜歡或是多會Java編程,在學習和解決問題上總會碰到障礙。工作的時間越久就越能明白這個道理。不過這倒是一個讓人進步的機會,因為你要一直不斷的學習才能很好的解決你面前的難題...
閱讀 1032·2021-11-25 09:43
閱讀 1413·2021-11-18 10:02
閱讀 1814·2021-11-02 14:41
閱讀 2366·2019-08-30 15:55
閱讀 1067·2019-08-29 16:18
閱讀 2552·2019-08-29 14:15
閱讀 1390·2019-08-26 18:13
閱讀 733·2019-08-26 10:27