設計模式就是前人總結出來的代碼模版
創建型
工廠模式
- 創建物件的工廠,使用者不必關心物件生成的程序,也就是不需要顯示的呼叫new 運算子,只需要呼叫物件工廠暴露出來的創建物件的方法,并傳入需要創建的物件的型別;缺點是擴展該工廠需要往工廠里不斷加入子類,會使代碼越來越臃腫
抽象工廠模式
- 在工廠模式的基礎上,有多個工廠,每個工廠負責創建同型別的物件, 抽象工廠實作了獲取每個工廠實體的介面,使用者可以呼叫對應的方法獲取對應型別工廠實體,使用該工廠可以創建物件;缺點和工廠模式一樣,擴展麻煩
單例模式
-
一個類只能被實體化一次,建構式私有化,在類內部實體化,有多種實作方法
-
/** java 建議寫法 類加載時就初始化,浪費記憶體 執行緒安全(對java來說) **/ class SingleTon { public static getInstance() {return this.instance} private instance = new SingleTon() private constructor() {} } // 使用 SingleTon.getInstance() -
/** js 建議寫法 使用時再初始化,節約記憶體 執行緒不安全(對java來說, js單執行緒) **/ class SingleTon { public static getInstance() { if (!instance) { this.instance = new SingleTon(); } return this.instance } private instance; private constructor() {} } // 使用 SingleTon.getInstance() -
/** js 閉包版本 getInstance 回傳的函式保存了對 instance 變數的參考 **/ class SingleTon { public static getInstance = (function () { let instance = null; return function () { if (!instance) { return new SingleTon(); } return instance; } })() private constructor() {} } // 使用 SingleTon.getInstance()
建造者模式
- 把簡單物件一步一步組裝成復雜物件
- 場景:簡單物件固定,簡單物件的組合是變化的
原型模式
-
快取物件,每次回傳物件深拷貝物件(java/C++)
-
(JS)在原型模式下,當我們想要創建一個物件時,會先找到一個物件作為原型,然后通過克隆原型的方式來創建出一個與原型一樣(共享一套資料/方法)的物件
-
在 JavaScript 里,
Object.create方法就是原型模式的天然實作——準確地說,只要我們還在借助Prototype來實作物件的創建和原型的繼承,那么我們就是在應用原型模式, -
前端常見的考點就是原型鏈和深拷貝
結構型
配接器模式
- 作為兩個不同介面的橋梁
- 比如Mac Pro 2019 只有四個雷電口,但我想插usb怎么辦,這就需要一個配接器來連接兩個介面
- 不能濫用,適用于要處理介面沖突,但不能重構老代碼的情況下
- 把變化留給自己,把統一留給用戶
裝飾器模式
-
允許向一個現有的物件添加新的功能,同時又不改變其結構
-
裝飾器和被裝飾的類實作了同一個介面,在裝飾器的建構式中把類的實體傳入,裝飾器實作該介面時先執行傳入類實體的方法,再執行一系列擴展的方法
-
ES7 @語法糖支持裝飾器
-
// 以下是 ts 語法 interface BaseFunction { move: () => void; } // 比如有個機器人 實作 基本功能 move class Robot implements BaseFunction { public move() { console.log('move'); } } // 然后有高級需求 機器人需要邊移動邊跳舞并大笑 // 基于開閉原則 我們不修改原來的 機器人類 interface AdvanceFunction { dance: () => void; laugh: () => void; } class Decorator implements BaseFunction, AdvanceFunction { private instance; constructor(instance: BaseFunction) { this.instance = instance; } public move() { this.instance.move(); this.dance(); this.laugh(); } public dance() { console.log('dance'); } public laugh() { console.log('laugh'); } } const robot = new Robot(); robot.move(); // move const robotDecorator = new Decorator(robot); robotDecorator.move(); // move dance laugh // 只要實作了move 的類的實體都可以當作裝飾器建構式的引數傳入以獲取高級功能 -
// ES7 裝飾器寫法 // ES7 裝飾器 分為類裝飾器,方法裝飾器 // 給move添加額外的動作,所以我們使用方法裝飾器 interface BaseFunction { move: () => void; } class Robot implements BaseFunction { @decorator // 裝飾move public move() { console.log('move'); } } /** @param target 類的原型物件 class.prototype @param name 修飾的目標屬性屬性名 @param descriptor 屬性描述物件 它是 JavaScript 提供的一個內部資料結構、一個物件,專門用來描述物件的屬性,它由各種各樣的屬性描述符組成,這些描述符又分為資料描述符和存取描述符: 資料描述符:包括 value(存放屬性值,默認為默認為 undefined)、writable(表示屬性值是否可改變,默認為true)、enumerable(表示屬性是否可列舉,默認為 true)、configurable(屬性是否可配置,默認為true), 存取描述符:包括 get 方法(訪問屬性時呼叫的方法,默認為 undefined),set(設定屬性時呼叫的方法,默認為 undefined ) **/ function decorator (target, name, descriptor) { // 保存裝飾的方法 let originalMethod = descriptor.value // 在這里擴展裝飾的方法 descriptor.value = https://www.cnblogs.com/LHLVS/archive/2020/11/14/function() { dance(); laugh(); return originalMethod.apply(this, arguments) } return descriptor function dance() { console.log('dance'); } function laugh() { console.log('laugh'); } } const robot = new Robot(); robot.move(); // dance laugh move // 裝飾器函式執行的時候,實體還并不存在,這是因為實體是在我們的代碼運行時動態生成的,而裝飾器函式則是在編譯階段就執行了,所以說裝飾器函式真正能觸及到的,就只有類這個層面上的物件, -
生產實踐: REACT的高階組件
代理模式
-
代理就像一個中介,處理你和target之間的通信,比如vpn
-
ES6為代理而生的代理器 —— Proxy
// 第一個引數是我們的目標物件,handler 也是一個物件,用來定義代理的行為,當我們通過 proxy 去訪問目標物件的時候,handler會對我們的行為作一層攔截,我們的每次訪問都需要經過 handler 這個第三方 const proxy = new Proxy(obj, handler) -
業務開發中最常見的四種代理型別:事件代理、虛擬代理、快取代理和保護代理
事件代理
基于事件的冒泡特性,在子元素上的點擊事件會向父級冒泡,所以我們只需要在父元素上系結一次事件,根據event.target來判斷實際觸發事件的元素,節省了很多系結事件的開銷
虛擬代理
// 常見案例為圖片的預加載 // 圖片的預加載指,避免用戶網路慢時或者圖片太大時,頁面長時間給用戶留白的尷尬 // 圖片URL先指向占位圖url, // 在后臺新建一個圖片實體,該圖片實體 的URL指向真實的圖片地址, // 當該圖片實體加載完畢時再把頁面圖片的地址指向真實的圖片地址, // 這樣頁面圖片就可以直接使用快取展示, // 因預加載使用的圖片實體的生命周期全程在后臺從未在渲染層面拋頭露面, // 因此這種模式被稱為“虛擬代理”模式 class LoadImage { constructor(imgNode: Element) { // 獲取真實的DOM節點 this.imgNode = imgNode } // 操作img節點的src屬性 setSrc(imgUrl) { this.imgNode.src = https://www.cnblogs.com/LHLVS/archive/2020/11/14/imgUrl } } class PreLoadProxy { // 占位圖的url地址 static LOADING_URL ='xxxxxx' private targetImage: LoadImage; constructor(targetImage: LoadImage) { this.targetImage = targetImage } // 該方法主要操作虛擬Image,完成加載 setSrc(targetUrl): Promise<boolean> { // 真實img節點初始化時展示的是一個占位圖 this.targetImage.setSrc(ProxyImage.LOADING_URL) // 創建一個幫我們加載圖片的虛擬Image實體 const virtualImage = new Image() return new Promise((resolve, reject) => { // 監聽目標圖片加載的情況,完成時再將DOM上的真實img節點的src屬性設定為目標圖片的url virtualImage.onload = () => { this.targetImage.setSrc(targetUrl); resolve(true); } virtualImage.onerror = () => { reject(false); } // 設定src屬性,虛擬Image實體開始加載圖片 virtualImage.src = https://www.cnblogs.com/LHLVS/archive/2020/11/14/targetUrl; }); } } const imageList: Element[] = Array.from(document.getElementByClassName('preload-image'))); Promise.all(imageList.map(image => new PreLoadProxy(new LoadImage(image)).setSrc('realUrl'))) .then() .catch()快取代理
interface Cal { addAll: (...args: number[]) => : number } // 快取上一次的結果 // 比如一個累加器 class Calculator implements Cal { addAll(...args: number[]): number { if (!args) { return 0; } return args.reduce((pre, next) => pre + next, 0) } } const calculator = new Calculator() // 連續執行兩次相同的累加函式會遍歷兩次 calculator.addAll(1, 2, 3, 5); calculator.addAll(1, 2, 3, 5); class CacheProxy implements Cal { private caches: {[key: string]: number} = {} private target: Cal; constructor(cal: Cal) { this.target = cal; } addAll(...args: number[]): number { const key = args.join(); if (this.caches[key] === undefined) { this.caches[key] = this.target.addAll(); } return this.caches[key]; } } const calculator = new Calculator(); const calculatorProxy = new CacheProxy(calculator); // 連續執行兩次相同的累加函式第二次會使用快取 calculatorProxy.addAll(1, 2, 3, 5); calculatorProxy.addAll(1, 2, 3, 5); // 寫到這里,我想這不就是給原來的累加器加了一個快取功能嗎? // 加額外的功能又不改變原來的結構這不就符合裝飾器模式的定義嗎 // 看看是否可以改造成一個快取裝飾器 // 沒印象的可以看上一小節 // 裝飾器使用閉包保存caches物件 function cacheDecorator(caches = {}) { return function decorator(target, name, descriptor) { // 保存裝飾的方法 let originalMethod = descriptor.value // 在這里擴展裝飾的方法 descriptor.value = https://www.cnblogs.com/LHLVS/archive/2020/11/14/function(...args) { const key = args.join(); if (caches[key] === undefined) { caches[key] = originalMethod.apply(this, args); } return caches[key]; } return descriptor } } // 使用快取裝飾器 class Calculator implements Cal { @cacheDecorator() addAll(...args: number[]): number { if (!args) { return 0; } return args.reduce((pre, next) => pre + next, 0) } } const calculator = new Calculator() // 連續執行兩次相同的累加函式同樣會快取 calculator.addAll(1, 2, 3, 5); calculator.addAll(1, 2, 3, 5);保護代理
就是ES6的Proxy, 劫持物件的屬性,VUE3.0的雙向系結實作原理
行為型
策略模式
消滅if else , 定義一系列策略,把它們封裝起來,并使它們可替換
// 比如你出去旅游 // 可以選擇以下交通工具:步行,自行車,火車,飛機 // 對應需要花的時間 // 步行 48h // 自行車 30h // 火車 8h // 飛機 1h enum Tool { WALK = 'walk', BIKE = 'bike', TRAIN = 'train', PLANE = 'plane' } /** * 計算花費的時間 * if else 一把梭 * @param tool */ function timeSpend(tool: Tool): number { if (tool === Tool.WALK) { return 48; } else if (tool === Tool.BIKE) { return 30; } else if (tool === Tool.TRAIN) { return 8; } else if (tool === Tool.PLANE) { return 1; } else { return NaN } } // 此時新增了一種交通工具 motoBike : 18h // 你就必須去改timeSpend函式,在里面加else if , // 然后你和測驗同學說幫忙回歸一下整套旅游時間花費邏輯 // 測驗同學嘴上說好的,心里說了一句草泥馬 // 策略模式重構 // 把策略抽出來并封裝成一個個函式 // 使用映射代替if else // 此時新增一種策略,只需要新增一個策略函式并把它放入映射中 // 這樣你就可以自信的和測驗同學說,我增加了一種旅行方式, // 你只要測新增的方式,老邏輯不需要回歸 // 于是你從人人喊打的if else 俠搖身一變成了測驗之友 const timeMap = { walk, bike, train, plane } function timeSpend(tool: Tool): number { return timeMap[tool]() || NaN; } function walk() { return 48; } function bike() { return 30; } function train() { return 8; } function plane() { return 1; }狀態模式
一個物件有多種狀態,每種狀態做不同的事情,狀態的改變是在狀態內部發生的, 物件不需要清楚狀態的改變,它只用呼叫狀態的方法就行,可以看看這個例子加深理解
觀察者模式
當物件間存在一對多關系時,則使用觀察者模式(Observer Pattern),比如,當一個物件被修改時,則會自動通知依賴它的物件,觀察者模式屬于行為型模式,
有兩個關鍵角色: 發布者,訂閱者
- 發布者添加訂閱者
- 發布者發生變化通知訂閱者
- 訂閱者執行相關函式
// 以vue的回應式更新視圖原理為例 // 資料發生變化,更新視圖 // observe方法遍歷并包裝物件屬性 function observe(target, cb) { // 若target是一個物件,則遍歷它 if(target && typeof target === 'object') { Object.keys(target).forEach((key)=> { // defineReactive方法會給目標屬性裝上“監聽器” defineReactive(target, key, target[key], cb) }) } } // 定義defineReactive方法 function defineReactive(target, key, val, cb) { // 屬性值也可能是object型別,這種情況下需要呼叫observe進行遞回遍歷 observe(val) // 為當前屬性安裝監聽器 Object.defineProperty(target, key, { // 可列舉 enumerable: true, // 不可配置 configurable: false, get: function () { return val; }, // 監聽器函式 set: function (value) { // 執行render函式 render(); val = value; } }); } class Vue { constructor(options) { this._data = https://www.cnblogs.com/LHLVS/archive/2020/11/14/options.data; //代理傳入的物件,資料發生變化,執行render函式 observe(this._data, options.render) } } let app = new Vue({ el:'#app', data: { text: 'text', text2: 'text2' }, render(){ console.log("render"); } })觀察者模式和發布-訂閱模式之間的區別,在于是否存在第三方、發布者能否直接感知訂閱者,
angular的ngrx, react的redux 和 vue的vuex,event-bus都是典型的發布訂閱模式
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/214897.html
標籤:其他
上一篇:走進 JAVASCRIPT 黑洞,涵蓋 es5 / es6 / es7 / es8 知識點
下一篇:CTF學習資源推薦
