Javascript设计模式
生活随笔
收集整理的這篇文章主要介紹了
Javascript设计模式
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
設計原則(SOLID)
單一職責模式(S)
- 一個程序只做好一件事
- 如果功能過于復雜就拆分開,每個部分保持獨立
里式替換原則(L)
- 子類能覆蓋父類
- 父類能出現的地方子類就能出現
- JS中使用較少(弱類型&繼承使用較少)
開放封閉原則(O)
- 對擴展開放對修改封閉
- 增加需求時,擴展新代碼,而非修改已有代碼
- 軟件設計的終極目標
接口隔離原則(I)
- 保持接口的單一獨立,避免出現"胖接口"
- JS中沒有接口(typescript例外),使用較少
- 類似于單一職責所在,這里更關注接口
依賴倒置原則(D)
- 面向接口編程,依賴于抽象而不依賴于具體
- 使用方只關注接口而不關注具體類的實現
- JS中使用較少(沒有接口&弱類型)
設計模式
工廠模式
- 將new操作單獨封裝
- 遇到new時,就要考慮是否該使用工廠模式了
示例
你去購買漢堡,直接點餐、取餐,不會自己親手做
商店要“封裝”做漢堡的工作,做好直接給買者
UML類圖:
代碼示例:
class Product {constructor(name) {this.name = name;}init() {console.log('init')}fn1() {console.log('fn1')}fn2() {console.log('fn2')} }class Creator {create(name) {return new Product(name)} }let create = new Creator(); let p = create.create('p') p.init() p.fn1() p.fn2()應用場景
- jQuery:
$('div')和new $('div')有何區別?
- 第一:書寫麻煩,jQuery的鏈式操作將成為噩夢
- 第二:一旦jQuery名字變化,將是災難性的
- React.crateElement:
- Vue的異步組件:
設計原則驗證:
- 構造函數和創建者分離
- 符合開放封閉原則
單例模式
- 系統中被唯一使用
- 一個類中只有一個實例
?
實例:
登錄框、購物車
傳統UML圖
說明
- 單例模式需要用到java的特性(private)
- ES6中沒有(typescript除外)
- 只能用java代碼來演示UML圖的內容(最后用js變相實現)
代碼演示
java版的單例模式演示 public class SingleObject{//注意:私有化構造函數,外部不能new,只能內部new!!!!private SingleObject(){}//唯一被new出來的對象private SingleObject getInstance(){if(instance == null){//只new一次instance = new SingleObject();}return instance;}//對象方法public void login(username,password){System.out.println("login...")}}public class SingletonPatternDemo{public static void main(String[] args){//不合法的構造函數//編譯時報錯:構造函數 SingleObject()是不可見的!!!//SingleObject object = new SingleObject();//獲取唯一可用的對象SingleObject object = SingleObject.getInstance();} } Javascript版的單例模式演示 class SingleObject {login() {console.log('login...')} }//靜態方法 SingleObject.getInstance = (function() {let instancereturn function() {if (!instance) {instance = new SingleObject();}return instance;} })()var login = SingleObject.getInstance().login(); javascript的單例模式缺點:如果強制new也不會報錯:
場景
- jQuery 只有一個'$'
- vuex 和 redux中的store
- 購物車、登錄框
設計原則驗證
- 符合單一職責原則,只實例化唯一的對象
- 沒法具體開放封閉原則,但是絕對不違反開放封閉原則
適配器模式
- 舊接口格式和使用者不兼容
- 中間加一個適配轉換接口
示例
macbookpro適配器轉換
電源插座國家不統一需要轉換頭
UML圖
演示
class Adaptee {specificRequest() {return '德國標準插頭'} }class Target {constructor() {this.Adaptee = new Adaptee()}request() {let info = this.Adaptee.specificRequest()return `${info} - 轉換器 - 中國標準插頭`} }let target = new Target() let res = target.request() console.log(res)應用場景
- 封裝舊接口
- vue computed
設計原則驗證
- 將舊接口和使用者進行分離
- 符合開放封閉原則
裝飾器模式
- 為對象添加新功能
- 不改變其原有的結構和功能
示例:
手機殼
UML類圖
代碼演示
class Circle {draw() {console.log('畫一個圓形')} }class Decorator {constructor(circle) {this.circle = circle}draw() {this.circle.draw()this.setRedBorder(circle)}setRedBorder(circle) {console.log('設置紅色邊框')} }let circle = new Circle(); circle.draw()let decorator = new Decorator(circle) decorator.draw()使用場景
- ES7裝飾器
- core-decorators
- 第三方開源lib
- 提供常用的裝飾器
設計原則驗證
- 將現有對象和裝飾器進行分離,兩者獨立存在
- 符合開放封閉原則
代理模式
- 使用者無權訪問目標對象
- 中間加代理,通過代理做授權和控制
示例:
- 科學上網
- 明星經紀人
UML
代碼演示
class RealImg {constructor(fileName) {this.fileName = fileName;this.loadFromDisk() //初始化即從硬盤中加載,模擬}display() {console.log('display...' + this.fileName)}loadFromDisk() {console.log('loading...' + this.fileName)} }class ProxyImg {constructor(fileName) {this.realImg = new RealImg(fileName)}display() {this.realImg.display()} }let proxyImg = new ProxyImg('1.png') proxyImg.display()場景
- 網頁事件代理
- jQuery $.proxy
- ES6 Proxy
設計原則驗證
- 代理類和目標類分離,隔離開目標類和使用者
- 符合開放封閉原則
代理模式VS適配器模式
- 適配器模式:提供一個不同的接口(如不同版本的插頭,無法使用)
- 代理模式:提供一模一樣的接口(無權使用)
代理模式VS裝飾器模式
- 裝飾器模式:擴展功能,原有功能不變且可直接使用
- 代理模式:直接針對(顯示)原有功能,但是經過限制或者閹割之后的
外觀模式
- 為子系統中的一組接口提供了一個高層接口
- 使用者使用這個高層接口
示例:
去醫院看病,接待員去掛號、門診、劃價、取藥
UML類圖
代碼演示
function bindEvent(elem,type,selector,fn){if(fn == null){fn = selectorselector = null} }//調用 bindEvent(elem,'click','#div1',fn) bindEvent(elem,'click',fn)設計原則驗證
- 不符合單一職責原則和開放封閉原則,因此謹慎使用,不可濫用
觀察者模式
- 發布&訂閱
- 一對多(N)
示例
- 點咖啡,點好之后坐等被叫
UML類圖
前端設計最重要的一種模式
代碼演示
//保存狀態,狀態變化之后觸發所有觀察者 class Subject {constructor() {this.state = 0this.observers = []}getState() {return this.state}setState(state) {this.state = statethis.notifyAllObervers()}notifyAllObervers() {this.observers.forEach(observer => {observer.update()})}attach(observer) {this.observers.push(observer)} }//觀察者 class Observer {constructor(name, subject) {this.name = namethis.subject = subjectthis.subject.attach(this)}update() {console.log(`${this.name} update,state:${this.subject.getState()}`)} }let subject = new Subject(); let obs1 = new Observer('o1', subject); let obs2 = new Observer('o2', subject); let obs3 = new Observer('o3', subject);subject.setState(1) subject.setState(2)應用場景
- 網頁事件綁定
所有的事件監聽用的都是觀察者模式
<button id="btn1">btn</button><script>$('#btn1').click(function () {console.log(1)})$('#btn1').click(function () {console.log(2)})$('#btn1').click(function () {console.log(2)}) </script>- Promise
- jQuery callbacks
- nodejs自定義事件
- nodejs中:處理http請求;多進程通訊
- vue和React組件生命周期觸發
- vue watch
設計原則驗證
- 主題和觀察者分離,不是主動觸發而是被動監聽,兩者解耦
- 符合開放封閉原則
迭代器模式
- 順序訪問一個集合
- 使用者無需知道集合的內部結構(封裝)
示例
- 沒有合適的示例,jQuery演示一下
UML類圖
代碼演示
class Iterator {constructor(container) {this.list = container.list;this.index = 0;}next() {if (this.hasNext()) {return this.list[this.index++]}}hasNext() {if (this.index >= this.list.length) {return false;}return true;} }class Container {constructor(list) {this.list = list}//生成遍歷器getIterator() {return new Iterator(this)} }let arr = [1, 2, 3, 4, 5, 6] let container = new Container(arr) let iterator = container.getIterator() while (iterator.hasNext()) {console.log(iterator.next()) }應用場景
- jQuery each
- ES6 Iterator
- ES6語法中,有序集合的數據類型已經有很多
- Array、Map、Set、String、TypedArray、argument、 NodeList
- 以上數據類型都有[Symbol.Iterator]屬性
- 屬性值是函數,執行函數返回一個迭代器
- 這個迭代器就有next方法可順序迭代子元素
- 可運行Array.prototype[Symbol.iterator]來測試
- for...of 消費 iterator
- ES6 Iterator與Generator
- iterator的價值不限于尚書幾個類型的遍歷,還有Generator函數的使用
- 即只要返回的數據符合Iterator接口的要求
- 即可使用Iterator語法,這就是迭代器模式
設計原則驗證
- 迭代器對象和目標對象分離
- 迭代器將使用者與目標對象隔離開
- 符合開放封閉原則
狀態模式
- 一個對象有狀態變化
- 每次狀態變化都會觸發一個邏輯
- 不能總是用if...else來控制
示例
交通信號燈不同顏色的變化UML類圖
代碼演示
//狀態 class State {constructor(state) {this.state = state}getState() {return this.state}handle(context) {console.log(`turn to ${this.state} light`)context.setState(this)} } //主體 class Context {constructor() {this.state = null}getState() {return this.state}setState(state) {this.state = state} }let context = new Context()let green = new State('green') let yellow = new State('yellow') let red = new State('red')green.handle(context); console.log(context.getState())yellow.handle(context); console.log(context.getState())red.handle(context); console.log(context.getState())應用場景
- 有限狀態機
- 有限個狀態,以及在這些狀態之間的變化,如交通信號燈
- 使用開源lib:javascript-state-machine
- 寫一個簡單的Promise
- Promise是一個一個有限狀態機
- Promise有三種狀態:pending、fullfilled、rejected
- pending -> fullfilled 或者 pending -> rejected,不可逆向變化
設計模式驗證
- 將狀態對象和主題對象分離,狀態的變化邏輯單獨處理
- 符合開放封閉原則
其他設計模式
- 不常用
- 對應不到經典場景
創建型:
- 原型模式
- 橋接模式
- 組合模式
- 享元模式
- 策略模式
- 模板方法模式
- 職責鏈模式
- 命令模式
- 備忘錄模式
- 中介者模式
- 訪問者模式
- 解釋器模式
原型模式
- clone自己,生成新對象(new開銷比較大)
- java默認有clone接口,不用自己實現
使用場景
Object.create- Object.create用到了原型模式的思想(雖然不是java中的clone)
對比JS中的原型prototype
- prototype 可以理解為ES6 class的一種底層原理
- class是實現面向對象的基礎,并不是服務于某個模式
橋接模式
- 用于把抽象化與實現化解耦
- 使得兩者可以獨立變化
- 在一些業務中比較常用
應用場景
//普通實現 class ColorShape {yellowCircle() {//...畫黃圓}redCircle() {//...畫紅圓}yellowTriangle() {//...畫黃三角形}redTriangle() {//...畫紅三角形} } //測試 let cs = new ColorShape() cs.yellowCircle() cs.redCircle() cs.yellowTriangle() cs.redTriangle()
//橋接模式 class Color {constructor(color) {this.color = color;} } class Shape {constructor(name, color) {this.name = name;this.color = color;}draw() {//畫圖...} } //測試代碼 let red = new Color("red") let yellow = new Color("yellow") let circle = new Shape('circle', red) circle.draw() let triangle = new Shape('triangle', yellow) triangle.draw()
顏色和圖形自由組合,復雜性少很多,后面增加圖形也很好處理
設計原則驗證
- 抽象和實現分離,解耦
- 符合開放封閉原則
組合模式
- 生成樹形結構,表示“整體-部分”關系
- 讓整體和部分都具有一致的操作方式
示例:
應用場景
- 虛擬DOM中的vnode是這種形式,但數據類型簡單
- 用JS實現一個菜單文件夾管理,不算經典應用,與業務相關
- 整體和單個節點的操作是一致的
- 整體和單個節點的數據結構也保持一致
- 設計原則驗證
- 將整體和單個節點的操作抽象出來
- 符合開放封閉原則
享元模式
- 共享內存(主要考慮內存,而非效率)
- 相同的數據,共享使用
JS中不用太多考慮內存開銷
演示
//無限下拉列表,將事件代理到高層次節點上 //如果都綁定到'<a>'標簽,對內存開銷大<div id="div1"><a href="#">a1</a><a href="#">a2</a><a href="#">a3</a><a href="#">a4</a><!--無限下拉列表--> </div>< script >var div1 = document.getElementById('div1')div1.addEventListener('click', function(e) {var target = e.targetif (e.nodeName === 'A' {alert(target.innerHtml)})}) </script>設計原則驗證
- 將相同的部分抽象出來
- 符合開放封閉原則
策略模式
- 不同策略分開處理
- 避免出現大量if...else或者switch...case
演示
class User {constructor(type) {this.type = type}buy() {if (this.type === 'oridinary') {console.log('普通用戶購買')} else if (this.type === 'member') {console.log('會員用戶購買')} else if (this.type === 'vip') {console.log('vip用戶購買')}} }//測試代碼 var u1 = new User('oridinary') u1.buy() var u2 = new User('member') u2.buy() var u3 = new User('vip') u3.buy()改成下面這種形式:
class OrdinaryUser {buy() {console.log('普通用戶購買')} } class MemberUser {buy() {console.log('會員用戶購買')} } class VipUser {buy() {console.log('vip用戶購買')} }var u1 = new OrdinaryUser() u1.buy()var u2 = new MemberUser() u2.buy()var u3 = new VipUser() u3.buy()設計原則驗證
- 不同策略,分開處理,而不是混合在一起
- 符合開放封閉原則
模板方法模式和職責鏈模式
模板方法模式:
class Action {handle() {handle1();handle2();handle3();}handle1() {console.log('1')}handle2() {console.log('2')}handle3() {console.log('3')} }職責鏈模式
- 一步操作可能分為多個職責角色來完成
- 把這些角色都分開,然后用一個鏈串起來
- 將發起者和各個處理者進行隔離
演示:
//請假審批,需要組長審批、經理審批、最后總監審批 class Action {constructor(name) {this.name = name;this.nextAction = null}setNextAction(action) {this.nextAction = action}handle() {console.log(`${this.name} 審批`)if (this.nextAction != null) {this.nextAction.handle()}} }let a1 = new Action('組長') let a2 = new Action('經理') let a3 = new Action('總監') a1.setNextAction(a2) a2.setNextAction(a3) a1.handle()應用場景
JS中的鏈式操作- 職責鏈和業務結合較多,JS中能聯想到鏈式操作
- jQuery的鏈式操作,Promise.then的鏈式操作
設計原則驗證
- 發起者和各個處理者進行隔離
- 符合開放封閉原則
命令模式
- 執行命令時,發布者和執行者分開
- 中間加入命令對象,作為中轉站
class Receiver {exec() {console.log('執行')} }class Command {constructor(receiver) {this.receiver = receiver}cmd() {console.log('觸發命令')this.receiver.exec()} }class Invoke {constructor(command) {this.command = command;}invoke() {console.log('開始')this.command.cmd();} }let soldier = new Receiver() let trumpeter = new Command(soldier) let general = new Invoke(trumpeter) general.invoke()
應用場景
- 網頁富文本編輯器操作,瀏覽器封裝了一個命令對象
- document.execCommand("bold")
- document.execCommand("undo")
設計原則驗證
- 命令對象與執行對象分開,解耦
- 符合開放封閉原則
備忘錄模式
- 隨時記錄一個對象的狀態變化
- 隨時可以恢復之前的某個狀態(如撤銷功能)
演示
一個編輯器
//備忘類 class Memento {constructor(content) {this.content = content;}getContent() {return this.content;} }//備忘列表 class CareTaker {constructor() {this.list = [];}add(memento) {this.list.push(memento)}get(index) {return this.list[index]} }//編輯器 class Editor {constructor() {this.content = null}setContent(content) {this.content = content}getContent(content) {return this.content}saveContentToMemento() {return new Memento(this.content)}getContentFromMenmeto(memento) {this.content = memento.getContent()} } //測試代碼 let editor = new Editor() let careTaker = new CareTaker() editor.setContent('111') editor.setContent('222') careTaker.add(editor.saveContentToMemento()) //存儲備忘錄 editor.setContent('333') careTaker.add(editor.saveContentToMemento()) //存儲備忘錄 editor.setContent('444') console.log(editor.getContent()) editor.getContentFromMenmeto(careTaker.get(1)) //撤銷 console.log(editor.getContent()) editor.getContentFromMenmeto(careTaker.get(0)) //撤銷 console.log(editor.getContent())設計原則驗證
- 狀態對象與使用者分開,解耦
- 符合開放封閉原則
中介者模式
演示
class Mediator {constructor(a, b) {this.a = a;this.b = b;}setA() {let number = this.b.number;this.a.setNumber(number * 100);}setB() {let number = this.a.number;this.b.setNumber(number / 100);} }class A {constructor() {this.number = 0;}setNumber(num, m) {this.number = num;if (m) {m.setB()}} }class B {constructor() {this.number = 0;}setNumber(num, m) {this.number = num;if (m) {m.setA();}} }let a = new A(); let b = new B(); let m = new Mediator(a, b);a.setNumber(100, m); console.log(a.number, b.number); b.setNumber(300, m); console.log(a.number, b.number)設計原則驗證
- 將各關聯對象通過中介者隔離
- 符合開放封閉原則
訪問者模式
- 將數據操作和數據結構分離
- 使用場景不多
解釋器模式
- 描述語言語法如何定義,如何解釋和編譯
- 用于專業場景
綜合應用
關于面試
- 能說出課程重點講解的設計模式即可
日常使用
- 了解重點設計模式,要強制自己模仿、掌握
- 非常用的設計模式,視業務場景選擇性使用
總結
以上是生活随笔為你收集整理的Javascript设计模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中兴小方糖智能路由器今日开启预售,到手价
- 下一篇: 关于罕见病以下说法正确的是?蚂蚁庄园2.