前端需要掌握的設計模式

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提到設計模式,相信知道的同學都會脫口而出,五大基本原則(SOLID)和 23 種設計模式。SOLID 所指的五大基本原則分別是:單一功能原則、開放封閉原則、裏式替換原則、接口隔離原則和依賴反轉原則。逐字逐句詮釋這五大基本原則違背了寫這篇文章的初衷,引用社區大佬的理解,SOLID 可以簡單概括爲六個字,即“高內聚,低耦合”:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"高"},{"type":"text","text":"層模塊不依賴底層模塊,即爲依賴反轉原則。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"內"},{"type":"text","text":"部修改關閉,外部擴展開放,即爲開放封閉原則。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"聚"},{"type":"text","text":"合單一功能,即爲單一功能原則。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"低"},{"type":"text","text":"知識要求,對外接口簡單,即爲迪米特法則。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"耦"},{"type":"text","text":"合多個接口,不如獨立拆分,即爲接口隔離原則。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"合"},{"type":"text","text":"成複用,子類繼承可替換父類,即爲裏式替換原則。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"23 種設計模式分爲“創建型”、“行爲型”和“結構型”。具體類型如下圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/20\/20b4d1d8924844d17c118ae957ce7ff9.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"設計模式說白了就是“封裝變化”。比如“創建型”封裝了創建對象的變化過程,“結構型”將對象之間組合的變化封裝,“行爲型”則是抽離對象的變化行爲。接下來,本文將以“單一功能”和“開放封閉”這兩大原則爲主線,分別介紹“創建型”、“結構型”和“行爲型”中最具代表性的幾大設計模式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"創建型"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"工廠模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"工廠模式根據抽象程度可分爲三種,分別爲簡單工廠、工廠方法和抽象工廠。其核心在於將創建對象的過程封裝其他,然後通過同一個接口創建新的對象。 簡單工廠模式又叫靜態工廠方法,用來創建某一種產品對象的實例,用來創建單一對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\/\/ 簡單工廠\nclass Factory {\n  constructor (username, pwd, role) {\n   this.username = username;\n    this.pwd = pwd;\n    this.role = role;\n  }\n}\n\nclass CreateRoleFactory {\n static create (username, pwd, role) {\n   return new Factory(username, pwd, role);\n  }\n}\n\nconst admin = CreateRoleFactory.create('張三', '222', 'admin');"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在實際工作中,各用戶角色所具備的能力是不同的,因此簡單工廠是無法滿足的,這時候就可以考慮使用工廠方法來代替。工廠方法的本意是將實際創建對象的工作推遲到子類中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class User {\n constructor (name, menuAuth) {\n if (new.target === User) throw new Error('User 不能被實例化');\n this.name = name;\n this.menuAuth = menuAuth;\n }\n}\n\nclass UserFactory extends User {\n constructor (...props) {\n super(...props);\n }\n static create (role) {\n const roleCollection = new Map([\n ['admin', () => new UserFactory('管理員', ['首頁', '個人中心'])],\n ['user', () => new UserFactory('普通用戶', ['首頁'])]\n ])\n \n return roleCollection.get(role)();\n }\n}\n\nconst admin = UserFactory.create('admin');\nconsole.log(admin); \/\/ {name: \"管理員\", menuAuth: Array(2)}\nconst user = UserFactory.create('user');\nconsole.log(user); \/\/ {name: \"普通用戶\", menuAuth: Array(1)}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨着業務形態的變化,一個用戶可能在多個平臺上同時存在,顯然工廠方法也不再滿足了,這時候就要用到抽象工廠。抽象工廠模式是對類的工廠抽象用來創建產品類簇,不負責創建某一類產品的實例。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"class User {\n constructor (hospital) {\n if (new.target === User) throw new Error('抽象類不能實例化!');\n this.hospital = hospital;\n }\n}\n\/\/ 浙一\nclass ZheYiUser extends User {\n constructor(name, departmentsAuth) {\n super('zheyi_hospital');\n this.name = name;\n this.departmentsAuth = departmentsAuth;\n }\n}\n\/\/ 蕭山醫院\nclass XiaoShanUser extends User {\n constructor(name, departmentsAuth) {\n super('xiaoshan_hospital');\n this.name = name;\n this.departmentsAuth = departmentsAuth;\n }\n}\n\nconst getAbstractUserFactory = (hospital) => {\n switch (hospital) {\n case 'zheyi_hospital':\n return ZheYiUser;\n break;\n case 'xiaoshan_hospital':\n return XiaoShanUser;\n break;\n }\n}\n\nconst ZheYiUserClass = getAbstractUserFactory('zheyi_hospital');\nconst XiaoShanUserClass = getAbstractUserFactory('xiaoshan_hospital');\n\nconst user1 = new ZheYiUserClass('王醫生', ['外科', '骨科', '神經外科']);\nconsole.log(user1);\nconst user2 = newXiaoShanUserClass('王醫生', ['外科', '骨科']);\nconsole.log(user2);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"小結:"},{"type":"text","text":" 構造函數和創建對象分離,符合開放封閉原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"使用場景:"},{"type":"text","text":" 比如根據權限生成不同用戶。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"單例模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單例模式理解起來比較簡單,就是保證一個類只能存在一個實例,並提供一個訪問它的全局接口。單例模式又分懶漢式和餓漢式兩種,其區別在於懶漢式在調用的時候創建實例,而餓漢式則是在初始化就創建好實例,具體實現如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\/\/ 懶漢式\nclass Single {\n static getInstance () {\n if (!Single.instance) {\n Single.instance = new Single();\n }\n return Single.instance;\n }\n}\n\nconst test1 = Single.getInstance();\nconst test2 = Single.getInstance();\n\nconsole.log(test1 === test2); \/\/ true"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\/\/ 餓漢式\nclass Single {\n static instance = new Single();\n\n static getInstance () {\n return Single.instance;\n }\n}\n\nconst test1 = Single.getInstance();\nconst test2 = Single.getInstance();\n\nconsole.log(test1 === test2); \/\/ true"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"小結:"},{"type":"text","text":" 實例如果存在,直接返回已創建的,符合開放封閉原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"使用場景:"},{"type":"text","text":" Redux、Vuex 等狀態管理工具,還有我們常用的 window 對象、全局緩存等。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"原型模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於前端來說,原型模式在常見不過了。當新創建的對象和已有對象存在較大共性時,可以通過對象的複製來達到創建新的對象,這就是原型模式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ Object.create()實現原型模式\nconst user = {\n name: 'zhangsan',\n age: 18\n};\nlet userOne = Object.create(user);\nconsole.log(userOne.__proto__); \/\/ {name: \"zhangsan\", age: 18}\n\n\n\/\/ 原型鏈繼承實現原型模式\nclass User {\n constructor (name) {\n this.name = name;\n }\n getName () {\n return this.name;\n }\n}\n\nclass Admin extends User {\n constructor (name) {\n super(name);\n }\n setName (_name) {\n return this.name = _name;\n }\n}\n\nconst admin = new Admin('zhangsan');\nconsole.log(admin.getName());\nconsole.log(admin.setName('lisi'));"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"小結:"},{"type":"text","text":" 原型模式最簡單的實現方式---Object.create()。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"使用場景:"},{"type":"text","text":" 新創建對象和已有對象無較大差別時,可以使用原型模式來減少創建新對象的成本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結構型"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"裝飾器模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"講裝飾器模式之前,先聊聊高階函數。高階函數就是一個函數就可以接收另一個函數作爲參數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const add = (x, y, f) => {\n return f(x) + f(y);\n}\nconst num = add(2, -2, Math.abs);\nconsole.log(num); \/\/ 4\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"函數 add 就是一個簡單的高階函數,而 add 相對於 Math.abs 來說相當於一個裝飾器,因此這個例子也可以理解爲一個簡單的裝飾器模式。在 react 中,高階組件(HOC)也是裝飾器模式的一種體現,通常用來不改變原來組件的情況下添加一些屬性,達到組件複用的功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"import React from 'react';\n\nconst BgHOC = WrappedComponent => class extends React.Component {\n render () {\n   return (\n     
\n       \n      \n    );\n  }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"小結:"},{"type":"text","text":" 裝飾器模式將現有對象和裝飾器進行分離,兩者獨立存在,符合開放封閉原則和單一職責模式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"使用場景:"},{"type":"text","text":" es7 裝飾器、vue mixins、core-decorators 等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"適配器模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"適配器別名包裝器,其作用是解決兩個軟件實體間的接口不兼容的問題。以 axios 源碼爲例:"}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function getDefaultAdapter() {\n  var adapter;\n  \/\/ 判斷當前是否是 node 環境\n  if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {\n    \/\/ 如果是 node 環境,調用 node 專屬的 http 適配器\n    adapter = require('.\/adapters\/http');\n  } else if (typeof XMLHttpRequest !== 'undefined') {\n    \/\/ 如果是瀏覽器環境,調用基於 xhr 的適配器\n    adapter = require('.\/adapters\/xhr');\n  }\n  return adapter;\n}\n\n\/\/ http adapter\nmodule.exports = function httpAdapter(config) {\n  return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) {\n    ...\n  }\n}\n\/\/ xhr adapter\nmodule.exports = function xhrAdapter(config) {\n  return new Promise(function dispatchXhrRequest(resolve, reject) {\n    ...\n  }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其目的就是保證 node 和瀏覽器環境的入參 config 一致,出參 Promise 都是同一個。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"小結:"},{"type":"text","text":" 不改變原有接口的情況下,統一接口、統一入參、統一出參、統一規則,符合開發封閉原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"使用場景"},{"type":"text","text":" :擁抱變化,兼容代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"代理模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代理模式就是爲對象提供一個代理,用來控制對這個對象的訪問。在我們業務開發中最常見的有四種代理類型:事件代理,虛擬代理、緩存代理和保護代理。本文主要介紹虛擬代理和緩存代理兩類。 提到虛擬代理,其最具代表性的例子就是圖片預加載。預加載主要是爲了避免網絡延遲、或者圖片太大引起頁面長時間留白的問題。通常的解決方案是先給 img 標籤展示一個佔位圖,然後創建一個 Image 實例,讓這個實例的 src 指向真實的目標圖片地址,當其真實圖片加載完成之後,再將 DOM 上的 img 標籤的 src 屬性指向真實圖片地址。"}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"class ProxyImg {\n constructor (imgELe) {\n this.imgELe = imgELe;\n this.DEFAULT_URL = 'xxx';\n }\n setUrl (targetUrl) {\n this.imgEle.src = this.DEFAULT_URL;\n const image = new Image();\n \n image.onload = () => {\n this.imgEle.src = targetUrl;\n }\n image.src = targetUrl;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"緩存代理常用於一些計算量較大的場景。當計算的值已經被出現過的時候,不需要進行第二次重複計算。以傳參求和爲例:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const countSum = (...arg) => {\n console.log('count...');\n let result = 0;\n arg.forEach(v => result += v);\n return result;\n}\n\nconst proxyCountSum = (() => {\n const cache = {};\n return (...arg) => {\n const args = arg.join(',');\n if (args in cache) return cache[args];\n return cache[args] = countSum(...arg);\n };\n})()\n\nproxyCountSum(1,2,3,4); \/\/ count... 10\nproxyCountSum(1,2,3,4); \/\/ 10"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"小結:"},{"type":"text","text":" 通過修改代理類來增加功能,符合開放封閉模式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"使用場景:"},{"type":"text","text":" 圖片預加載、緩存服務器、處理跨域以及攔截器等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"行爲型"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"策略模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"介紹策略模式之前,簡單實現一個常見的促銷活動規則:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"預售活動,全場 9.5 折"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大促活動,全場 9 折"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"返場優惠,全場 8.5 折"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"限時優惠,全場 8 折"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"人人喊打的 if-else"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const activity = (type, price) => {\n if (type === 'pre') {\n   return price * 0.95;\n  } else if (type === 'onSale') {\n   return price * 0.9;\n  } else if (type === 'back') {\n   return price * 0.85;\n  } else if (type === 'limit') {\n   return price * 0.8;\n  }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上代碼存在肉眼可見的問題:大量 if-else、可擴展性差、違背開放封閉原則等。 我們再使用策略模式優化:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const activity = new Map([\n ['pre', (price) => price * 0.95],\n ['onSale', (price) => price * 0.9],\n ['back', (price) => price * 0.85],\n ['limit', (price) => price * 0.8]\n]);\n\nconst getActivityPrice = (type, price) => activity.get(type)(price);\n\n\/\/ 新增新手活動\nactivity.set('newcomer', (price) => price * 0.7);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"小結:"},{"type":"text","text":" 定義一系列算法,將其一一封裝起來,並且使它們可相互替換。符合開放封閉原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"使用場景:"},{"type":"text","text":" 表單驗證、存在大量 if-else 場景、各種重構等。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"觀察者模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"觀察者模式又叫發佈-訂閱模式,其用來定義對象之間的一對多依賴關係,以便當一個對象更改狀態時,將通知其所有依賴關係。通過“別名”可以知道,觀察者模式具備兩個角色,即“發佈者”和“訂閱者”。正如我們工作中的產品經理就是一個“發佈者”,而前後端、測試可以理解爲“訂閱者”。以產品經理建需求溝通羣爲例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ 定義發佈者類\nclass Publisher {\n constructor () {\n this.observers = [];\n this.prdState = null;\n }\n \/\/ 增加訂閱者\n add (observer) {\n this.observers.push(observer);\n }\n \/\/ 通知所有訂閱者\n notify () {\n this.observers.forEach((observer) => {\n observer.update(this);\n })\n }\n \/\/ 該方法用於獲取當前的 prdState\n getState () {\n return this.prdState;\n }\n\n \/\/ 該方法用於改變 prdState 的值\n setState (state) {\n \/\/ prd 的值發生改變\n this.prdState = state;\n \/\/ 需求文檔變更,立刻通知所有開發者\n this.notify();\n }\n}\n\n\/\/ 定義訂閱者類\nclass Observer {\n constructor () {\n this.prdState = {};\n }\n update (publisher) {\n \/\/ 更新需求文檔\n this.prdState = publisher.getState();\n \/\/ 調用工作函數\n this.work();\n }\n \/\/ work 方法,一個專門搬磚的方法\n work () {\n \/\/ 獲取需求文檔\n const prd = this.prdState;\n console.log(prd);\n }\n}\n\n\/\/ 創建訂閱者:前端開發小王\nconst wang = new Observer();\n\/\/ 創建訂閱者:後端開發小張\nconst zhang = new Observer();\n\/\/ 創建發佈者:產品經理小曾\nconst zeng = new Publisher();\n\/\/ 需求文檔\nconst prd = {\n url: 'xxxxxxx'\n};\n\/\/ 小曾開始拉人入羣\nzeng.add(wang);\nzeng.add(zhang);\n\/\/ 小曾發佈需求文檔並通知所有人\nzeng.setState(prd);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經常使用 Event Bus(Vue) 和 Event Emitter(node)會發現,發佈-訂閱模式和觀察者模式還是存在着細微差別,即所有事件的發佈\/訂閱都不能由發佈者和訂閱者“私下聯繫”,需要委託事件中心處理。以 Vue Event Bus 爲例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"import Vue from 'vue';\n\nconst EventBus = new Vue();\nVue.prototype.$bus = EventBus;\n\n\/\/ 訂閱事件\nthis.$bus.$on('testEvent', func);\n\/\/ 發佈\/觸發事件\nthis.$bus.$emit('testEvent', params);\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整個過程都是 this.$bus 這個“事件中心”在處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"小結:"},{"type":"text","text":" 爲解耦而生,爲事件而生,符合開放封閉原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"使用場景:"},{"type":"text","text":" 跨層級通信、事件綁定等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"迭代器模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"迭代器模式號稱“遍歷專家”,它提供一種方法順序訪問一個聚合對象中的各個元素,且不暴露該對象的內部表示。迭代器又分內部迭代器(jquery.each\/for...of)和外部迭代器(es6 yield)。 在 es6 之前,直接通過 forEach 遍歷 DOM NodeList 和函數的 arguments 對象,都會直接報錯,其原因都是因爲他們都是類數組對象。對此 jquery 很好的兼容了這一點。 在 es6 中,它約定只要數據類型具備 Symbol.iterator 屬性,就可以被 for...of 循環和迭代器的 next 方法遍歷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"(function (a, b, c) {\n const arg = arguments;\n const iterator = arg[Symbol.iterator]();\n \n console.log(iterator.next()); \/\/ {value: 1, done: false}\n console.log(iterator.next()); \/\/ {value: 2, done: false}\n console.log(iterator.next()); \/\/ {value: 3, done: false}\n console.log(iterator.next()); \/\/ {value: undefined, done: true}\n})(1, 2, 3)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過 es6 內置生成器 Generator 實現迭代器並沒什麼難度,這裏重點通 es5 實現迭代器:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"function iteratorGenerator (list) {\n var index = 0;\n \/\/ len 記錄傳入集合的長度\n var len = list.length;\n return {\n \/\/ 自定義 next 方法\n next: funciton () {\n \/\/ 如果索引還沒有超出集合長度,done 爲 false\n var done = index >= len;\n \/\/ 如果 done 爲 false,則可以繼續取值\n var value = !done ? list[index++] : undefined;\n\n \/\/ 將當前值與遍歷是否完畢(done)返回\n return {\n done: done,\n value: value\n };\n }\n }\n}\n\nvar iterator = iteratorGenerator([1, 2, 3]);\nconsole.log(iterator.next()); \/\/ {value: 1, done: false}\nconsole.log(iterator.next()); \/\/ {value: 2, done: false}\nconsole.log(iterator.next()); \/\/ {value: 3, done: false}\nconsole.log(iterator.next()); \/\/ {value: undefined, done: true}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"小結:"},{"type":"text","text":" 實現統一遍歷接口,符合單一功能和開放封閉原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"使用場景:"},{"type":"text","text":" 有遍歷的地方就有迭代器。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"寫到最後"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"設計模式的難,在於它的抽象和分散。抽象在於每一設計模式看例子都很好理解,真正使用起來卻不知所措;分散則是出現一個場景發現好幾種設計模式都能實現。而解決抽象的最好辦法就是動手實踐,在業務開發中探索使用它們的可能性。本文大致介紹了前端領域常見的 9 種設計模式,相信大家在理解的同時也不難發現,設計模式始終圍繞着“封裝變化”來提供代碼的可讀性、擴展性、易維護性。所以當我們工作生活中,始終保持“封裝變化”的思想的時候,就已經開始體會到設計模式精髓了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頭圖:Unsplash"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:王君"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:https:\/\/mp.weixin.qq.com\/s\/0W7yAU9sDkdn-zsaZ9Lv0Q"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:前端需要掌握的設計模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來源:微醫大前端技術 - 微信公衆號 [ID:wed_fed]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉載:著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章