Javascript十六種常用設計模式
單例模式
何為單例模式,就是無論執行多少次函式,都只會生成一個物件哈哈,看一個簡單的demo
function Instance(name) { this.name = name; } Instance.prototype.fire = function () { console.log(this.name); } var singleton = (function () { var instance = null; return function (name) { if (!instance) instance = new Instance(name); return instance; } })(); singleton('dqhan').fire(); singleton('dqhan1').fire();
通過閉包形式儲存一個物件instance,然後每次判斷該物件是否存在,如果存在就直接返回該物件
如果我們要改變這個物件的內部屬性呢,新增一個擴充套件方法即可
function Instance(name) { this.name = name; } Instance.prototype.fire = function () { console.log(this.name); } Instance.prototype.set = function (name) { this.name = name; } var singleton = (function () { var instance = null; return function (name) { if (!instance) instance = new Instance(name); else instance.set(name); return instance; } })(); singleton('dqhan').fire(); singleton('dqhan1').fire();
但是並不滿足設計模式中的單一職責原則,物件與單例模式過於耦合,我們考慮僅提供單例模式建立物件,具體物件由外部設定
var singleton = function (fn) { var instance = null; return function () { if (!instance) instance = fn.apply(this, arguments) return instance; } } //對外暴露部分 var setSingleton = singleton(function () { var instance = new Instance('dqhan'); return instance; }) function Instance(name) { this.name = name; } Instance.prototype.fire = function () { console.log(this.name); } Instance.prototype.set = function (name) { this.name = name; }
工廠模式
工廠模式是指創造一些目標的方法模式,工廠模式分為簡單工廠模式,複雜工廠模式,抽象工廠模式
簡單工廠
function Factory(name) { this.name = name; } Factory.prototype.changeName = function (name) { this.name = name; } var demo = new Factory('dqhan'); var demo1 = new Factory('dqhan1');
看上去是不是一個建構函式呢,沒錯建構函式也是工廠模式,當然這只是個工廠模式的雛形,下面我們看一個嚴格意義上的簡單工廠模式
function Factory(type) { function Cat() { this.name = 'cat'; } Cat.prototype.say = function () { console.log('miaomiao~'); } function Dog() { this.name = 'dog'; } Dog.prototype.say = function () { console.log('wangwang~'); } var alr = { cat: function () { return new Cat(); }, dog: function () { return new Dog(); } }; this.__proto__ = alr[type](); } var cat = new Factory('cat'); cat.say(); var dog = new Factory('dog'); dog.say();
根據type生成目標物件,我們在回想一下工廠模式得核心思想,工廠模式要求建立物件得活交給字類去做,我們是不是可以在改進一下
function Factory(type) { return new this[type](); } Factory.prototype = { Cat: function () { this.name = 'cat'; this.__proto__.say = function () { console.log('miaomiao~'); } }, Dog: function () { this.name = 'dog'; this.__proto__.say = function () { console.log('wangwang~'); } } } var cat = new Factory('Cat'); cat.say(); var dog = new Factory('Dog'); dog.say();
我們講建立子類得方法新增到工廠方法得原型上,讓子類單獨成為了一個建構函式,到目前為止,我們可以再想想,每次建立物件我們都需要new一個工廠方法,這樣對外暴是不是很不友好,我們可以不可以直接呼叫工廠方法呢,沒錯,我們採用
安全模式建立例項
function Factory(type) { if (this instanceof Factory) return new this[type](); else return new Factory(type); } Factory.prototype = { Cat: function () { this.name = 'cat'; this.__proto__.say = function () { console.log('miaomiao~'); } }, Dog: function () { this.name = 'dog'; this.__proto__.say = function () { console.log('wangwang~'); } } } var cat = Factory('Cat'); cat.say(); var dog = new Factory('Dog'); dog.say();
這樣做有什麼好處呢,我們可以相容new一個物件或者直接呼叫方法建立一個物件,於是乎我們聯想到了什麼,是不是大名鼎鼎得jquery呢,jquery就是一個非常典型得工廠模式啊,我們來採取jquery原始碼得方式實現一個
function Factory(type) { return new Factory.fn.init(type); } var fn = { init: function (type) { //當前this指向fn物件 var target = this[type](); return target; } } Factory.fn = fn; Factory.prototype.init = function (type) { var target = this[type](); return target; } Factory.prototype = { Cat: function () { this.name = 'cat'; this.__proto__.say = function () { console.log('miaomiao~'); } }, Dog: function () { this.name = 'dog'; this.__proto__.say = function () { console.log('wangwang~'); } } } /** * 改變上面init中this指向問題使this指向Factory */ Factory.fn.init.prototype = Factory.prototype; var cat = Factory('Cat'); cat.say(); var dog = new Factory('Dog'); dog.say();
簡單工廠模式跟常規工廠模式都是直接建立例項,但是如果遇到複雜場景,這裡就需要採用抽象工廠模式,抽象工廠模式不是建立一個例項,而是建立一個集合,這個集合包含很多情況,每個情況可以使用具體例項
本質上抽象工廠就是子類繼承父類的方法
抽象工廠
function AbstractFactory(fType) { if (this instanceof AbstractFactory) { ChildrenFn.prototype = new this[fType](); } else return new AbstractFactory(fType, ChildrenFn); } AbstractFactory.prototype.Cat = function () { this.name = 'cat'; } AbstractFactory.prototype.Cat.prototype = { say: function () { console.log('miaomiao~'); } } AbstractFactory.prototype.Dog = function () { this.name = 'dog'; } AbstractFactory.prototype.Dog.prototype = { say: function () { console.log('wangwang~') } } //抽象工廠方法提供得介面不允許呼叫,需要字類重寫,所以我們要加個限制 function AbstractFactory(fType, ChildrenFn) { if (this instanceof AbstractFactory) { ChildrenFn.prototype = new this[fType](); } else return new AbstractFactory(fType, ChildrenFn); } AbstractFactory.prototype.Cat = function () { this.name = 'cat'; } AbstractFactory.prototype.Cat.prototype = { say: function () { throw new Error('不允許效用,僅允許重寫。') console.log('miaomiao~'); } } AbstractFactory.prototype.Dog = function () { this.name = 'dog'; } AbstractFactory.prototype.Dog.prototype = { say: function () { throw new Error('不允許效用,僅允許重寫。') console.log('wangwang~') } } function 美短Cat(name) { this.type = '美短'; this.name = name; } AbstractFactory('Cat', 美短Cat) var CatA = new 美短Cat('A'); // CatA.say(); AbstractFactory('Cat', 暹羅Cat) function 暹羅Cat(name) { this.type = '暹羅'; this.name = name this.__proto__.say = function () { console.log('重寫cat say'); } } var CatB = new 暹羅Cat('B'); CatB.say();
好了,到目前位置,是不是瞭解了工廠模式呢
策略模式
什麼是策略模式,就是分情況處理嘛,符合A得呼叫處理A得方法,符合B得呼叫B處理方法
我們平時寫程式碼經常寫的總歸是if else嘛。簡單寫個demo
function fn(type) { if (type === 1) (function () { console.log('1'); })(); if (type === 2) (function () { console.log('2'); })() if (type === 3) (function () { console.log('3') })() } fn(1); fn(2); fn(3);
這樣寫導致什麼問題呢,是不是當我們增加新的情況得時候,要不停得新增if判斷,或者在好點,我們乾脆就用switch好了,是不是給人得感覺狠亂,很不直觀,如果採用策略模式呢
var map = { 1: fn1, 2: fn2, 3: fn3 } function fn1() { console.log(1); } function fn2() { console.log(2); } function fn3() { console.log(3); } function fn(type) { map[type](); }
這樣寫出來是不是就很直觀了,當有複雜情況得時候我們可以提供一個額外得command方法來增加
function extendCommend(type, fn) { map[type] = fn; }
好了這就是策略模式
代理模式
什麼是代理模式,簡單得來講,就是增加一個額外得殼子,代理分了三種,保護代理,虛擬代理,快取代理 下面我們看下這三種代理究竟是怎麼回事兒吧
保護代理
function targetAction(props) { console.dir(props); } //現在我們要求不是所有的情況都可以條用這個方法,新增保護代理 function proxyTargetAction(props) { if (typeof props === 'string') console.log('拒絕使用目標函式') targetAction(props); } proxyTargetAction({ name: 'dqhan' }); proxyTargetAction('str');
我們在原方法上新增一個殼子,對外暴露得是這個殼子方法,對傳入引數進行保護
虛擬代理
其實虛擬代理我們平時是總會用到的,那就是函式節流與防抖了~下面我就就實現一個吧
function debounce(fn, delay) { var self = this, timer; return function () { clearInterval(timer); timer = setTimeout(function () { fn.apply(self, arguments); }, delay) } }
快取代理
function add() { var arg = [].slice.call(arguments); return arg.reduce(function (a, b) { return a + b; }); } // 代理 var proxyAdd = (function () { var cache = {}; return function () { var arg = [].slice.call(arguments).join(','); // 如果有,則直接從快取返回 if (cache[arg]) { return cache[arg]; } else { var result = add.apply(this, arguments); cache[arg] = result; return result; } }; })(); proxyAdd(1, 2, 3, 4, 5); proxyAdd(1, 2, 3, 4, 5);//直接從快取中輸出
我們利用閉包形式快取一快空間,然後當引數相同一致得時候便不再執行函式,而是快取讀取這個值
觀察者模式
觀察者模式又稱釋出訂閱模式,這種設計模式最大得特點就是整個程式採用了事件驅動得方式來執行
想象一下,React中兩個平級元件如何通訊呢,是不是通過父級元件呢,如果是兩個模組呢,沒有了父元件怎麼辦呢,我們就可以採用這種設計模式來實現
var observer = (function () { var events = {}; return function () { return { register: function (eventName, callback) { events[eventName] = callback; }, fire: function (eventName) { if (toString.call(events[eventName]) !== '[object Function]') throw new Error('error'); else return events[eventName](); }, remove: function (eventName) { if (toString.call(events[eventName]) !== '[object Function]') throw new Error('error'); else delete events[eventName]; } } } })(); var ob = observer(); ob.register('say', function () { console.log('demo'); }); ob.fire('say'); // ob.remove('say');
裝飾者模式
以動態方式對某個物件新增一些額外得職能,但是不影響該物件以及物件得衍生物
function Demo() { } Demo.prototype.fire = function () { console.log('fire'); } var demo = new Demo(); //裝飾器 function Decorator(demo) { this.demo = demo; } Decorator.prototype.fire = function () { this.demo.fire(); } var cat = new Decorator(demo); cat.fire();
其實我們很多時候可以利用這種設計模式,比如架構層面得當我們使用了第三方得控制元件以滿足我們自己得需求時候,這也算巨集觀得裝飾者模式
再比如我們自己封裝一個控制元件得時候,如果我們想要多個web框架進行遷移或者相容得時候我們也可以這麼做,看個我自己封裝得控制元件demo吧
控制元件主體部分
(function (global, $, $$, factory, plugin) { if (typeof global[plugin] !== "object") global[plugin] = {}; $.extend(true, global[plugin], factory.call(global, $, $$)) })(window, $, $$, function ( $, $$ ) { var uuid = -1; var _TabControl = function (ops) { this._ops = { items: ops.items || [], hashItems: {}, selectedIndex: ops.selectedIndex || 0 }; this._element = $(ops.element); this._tabContainerId = "ui-tabcontrol-container-"; this._oldValue = { selectedIndex: 0 }; this._convertHashItems(); this._init() ._initId() ._create() ._initMember() ._setTabContainer() ._setTabContent() ._bindEvent(); }; _TabControl.prototype = {//...省略了 } return { TabControl: _TabControl } }, "ui")
React殼子
import ReactWidget from './react-widget'; class TabControl extends ReactWidget { constructor(props) { super(props); } componentWillReceiveProps(newProps) { this.element.setOptions({ items: newProps.items, selectedIndex: newProps.selectedIndex }); } componentDidMount() { this.element = new ui.TabControl({ element: ReactDOM.findDOMNode(this), items: this.props.items, selectedIndex: this.props.selectedIndex }); $(ReactDOM.findDOMNode(this)).on('tabHandleChanged', this.props.selectChanged.bind(this)); } render() { return <div> <div className='ui-tabcontrol-content'> {this.props.children} </div> </div> } } window.$$.TabControl = TabControl;
當我們使用vue的時候,只需要將react殼子換成vue就行了
外觀模式所謂的外觀模式,就是將核心的方法打包成一個方法暴漏給外圍模組
function add() { console.log('add'); } function delete1() { console.log('delete'); } function multiplication() { console.log('multiplication'); } function division() { console.log('division') } function execute() { add(); delete1(); multiplication(); division(); } execute();
介面卡模式
介面卡模式核心思想就是相容不同情況,其實這種方式同樣可以使用在框架相容方面
function add(props) { if (toString.call(props) === '[object Object]') { var arr = []; for (var i in props) { if (props.hasOwnProperty(i)) arr.push(props[i]); } return arr.reduce(function (pre, next) { return pre + next; }) } if (toString.call(props) === '[object Array]') { return props.reduce(function (pre, next) { return pre + next; }) } throw new Error('paramster is error.') } add([1, 2, 3]); add({ a: 1, d: 2, c: 3 })
享元模式
享元模式是一種效能優化,核心思想是運用共享技術來實現大量細粒度物件的建立,我們來見識下吧
function Person(props) { this.name = props.name; this.sex = props.sex; this.height = props.height; this.weight = props.weight; } Person.prototype.info = function () { console.log(this.name + this.sex + this.height + this.weight); } var metadata = [ { name: 'dqhan0', sex: 'male', height: '170cm', weight: '125kg' }, { name: 'dqhan1', sex: 'female', height: '165cm', weight: '135kg' }, { name: 'dqhan2', sex: 'male', height: '180cm', weight: '145kg' }, { name: 'dqhan3', sex: 'male', height: '173cm', weight: '155kg' }, { name: 'dqhan4', sex: 'female', height: '169cm', weight: '165kg' }, { name: 'dqhan5', sex: 'male', height: '168cm', weight: '175kg' }, ] function execute() { metadata.forEach(m => { new Person(m).info(); }) } execute();
上面的例子我們可以看出來new出來了6個物件,當程式設計師的都知道new的過程就是在記憶體空間開闢記憶體的過程,如果成千上萬個物件呢,是不是記憶體就炸了,我們利用享元模式優化一下
從上面例子我們可以看出,其實person的性別只有男女,我們就抽離這個男女當作享元
function Person(sex) { this.sex = sex; this.name = ''; this.height = ''; this.weight = ''; } Person.prototype.info = function () { console.log(this.name + this.sex + this.height + this.weight); } var male = new Person('male'); var female = new Person('female'); function execute() { metadata.forEach(m => { if (m.sex === 'male') { male.name = m.name; male.height = m.height; male.weight = m.weight; male.info(); } else { female.name = m.name; female.height = m.height; female.weight = m.weight; female.info(); } }) } execute();
程式碼實現抽離,但是看起來耦合是不是很高啊,我們在改進一下
批量建立物件,我們想到了什麼設計模式,沒錯工廠模式,好了我們就用工廠模式優化
function Person(sex) { this.sex = sex; console.log('create person'); } Person.prototype.info = function (name) { ManagerPeson.setExternalState(name, this); console.log(this.name + this.sex + this.height + this.weight); } var PersonFactory = (function () { var pool = {}; return function (sex) { if (pool[sex]) { } else { pool[sex] = new Person(sex); } return pool[sex]; } })(); var ManagerPeson = (function () { var pool = {}; return function () { return { add: function (m) { if (pool[m.name]) { } else { pool[m.name] = { name: m.name, height: m.height, weight: m.weight }; } return PersonFactory(m.sex); }, setExternalState(name, target) { var poolTarget = pool[name]; for (var i in poolTarget) { if (poolTarget.hasOwnProperty(i)) target[i] = poolTarget[i] } } } } })() function execute() { metadata.forEach(m => { ManagerPeson.add(m).info(m.name); }) }
這裡由三部分組成,首先是目標物件person,然後工廠模式建立物件,通過性別來決定是否建立新的物件,當然為了快取享元,我們採用了閉包,最後我們通過MangerPerson整合每個person的特有屬性
命令模式
一種鬆耦合的設計思想,使傳送者與接收者消除彼此之前得耦合關係
html
<button id="refresh">refresh</button> <button id="add">add</button> <button id="del">delete</button>
我們來對這三個button進行繫結式優化
傳統模式
refreshBtn.addEventListener('click', function () { console.log('refresh'); }) addBtn.addEventListener('click', function () { console.log('add'); }) delBtn.addEventListener('click', function () { console.log('delete') })
var Refresh = function () { } Refresh.prototype.action = function () { console.log('refresh') } var Add = function () { } Add.prototype.action = function () { console.log('add') } var Del = function () { } Del.prototype.action = function () { console.log('delete') } var RefreshCommand = function (receiver) { return { excute() { receiver.action(); } } } var AddCommand = function (receiver) { return { excute() { receiver.action(); } } } var DeleteCommand = function (receiver) { return { name: 'delete command', excute() { receiver.action(); } } } var setCommand = function (btn, command) { console.dir(command); btn.addEventListener('click', function () { command.excute(); }) } var refreshCommand = RefreshCommand(new Refresh()); var addCommand = AddCommand(new Add()); var delCommand = DeleteCommand(new Del()); setCommand(refreshBtn, refreshCommand) setCommand(addBtn, addCommand) setCommand(delBtn, delCommand)
命令模式規定一個命令要有執行函式excute,場景複雜可新增undo,unexcute等方法,命令需要有接收者,具體行為由接收者提供,呼叫者僅需要知道這個命令即可
巨集命令
巨集命令是一組命令的集合,通過執行巨集命令的方式,執行一批命令,核心思想跟訊息佇列是一樣的,也可以是觀察者模式中註冊了針對一個事件註冊多個個函式一樣
現在我們將refresh、delete、add方法一次性全部執行一次
var macioCommand = (function () { var commandPool = []; return { add(command) { if (commandPool.includes(command)) throw new error('已存在'); else commandPool.push(command); }, excute() { for (var command of commandPool) { command.excute(); } } } })(); macioCommand.add(refreshCommand); macioCommand.add(addCommand); macioCommand.add(delCommand); macioCommand.excute();
中介者模式
如果一個場景有好多物件,每個物件之前彼此有聯絡,我們將採用中介者模式
假設現在有三種動物,我們看誰吃的多
傳統方式
var Cat = function () { this.eatNumber = 0 } Cat.prototype.eat = function (num, dog, pig) { this.eatNumber = num; var arr = [this.eatNumber, dog.eatNumber, pig.eatNumber]; arr.sort(function (pre, next) { return next - pre; }) console.log('cat當前排名:' + arr.indexOf(this.eatNumber) + 1); } var Dog = function (cat, pig) { this.eatNumber = 0 } Dog.prototype.eat = function (num, cat, pig) { this.eatNumber = num; var arr = [this.eatNumber, cat.eatNumber, pig.eatNumber]; arr.sort(function (pre, next) { return next - pre; }) console.log('dog當前排名:' + arr.indexOf(this.eatNumber) + 1); } var Pig = function () { this.eatNumber = 0 } Pig.prototype.eat = function (num, dog, cat) { this.eatNumber = num; var arr = [this.eatNumber, dog.eatNumber, cat.eatNumber]; arr.sort(function (pre, next) { return next - pre; }) console.log('pig當前排名:' + arr.indexOf(this.eatNumber) + 1); } var cat = new Cat(); var dog = new Dog(); var pig = new Pig(); cat.eat(20, dog, pig); dog.eat(50, cat, pig); pig.eat(100, cat, dog);
傳統模式的實現方式,在執行eat的時候我們需要將另外兩種動物傳進去做比較,那麼如果我們將比較的方式抽出來實現
var Cat = function () { this.eatNumber = 0 } Cat.prototype.eat = function (num) { this.eatNumber = num; middle(this); } var Dog = function (cat, pig) { this.eatNumber = 0 } Dog.prototype.eat = function (num, cat, pig) { this.eatNumber = num; middle(this); } var Pig = function () { this.eatNumber = 0 } Pig.prototype.eat = function (num, dog, cat) { this.eatNumber = num; middle(this); } var middle = (function () { var pool = []; return function (target) { pool.push(target.eatNumber); pool.sort(function (pre, next) { return next - pre; }) console.log('當前排名:' + pool.indexOf(target.eatNumber) + 1); } })() var cat = new Cat(); var dog = new Dog(); var pig = new Pig(); cat.eat(20, dog, pig); dog.eat(50, cat, pig); pig.eat(100, cat, dog);
職責鏈模式
職責鏈模式在我們平時寫業務邏輯是後比較常用,當一個函式處理很多東西的時候,我們通過職責鏈模式將其拆分
常規形式程式碼
var order = function (orderType, pay, stack) { if (orderType === 1) { if (pay == true) { console.log('500元定金') } else { if (stack > 0) console.log('普通購買') else console.log('庫存不足') } } else if (orderType === 2) { if (pay == true) { console.log('200元定金') } else { if (stack > 0) console.log('普通購買') else console.log('庫存不足') } } else { if (stack > 0) console.log('普通購買') else console.log('庫存不足') } }
我們通過職責鏈模式進行改進
var order500 = function (orderType, pay, stack) { if (orderType === 1 && pay === true) console.log('500定金'); else order200(orderType, pay, stack); } var order200 = function (orderType, pay, stack) { if (orderType === 2 && pay === true) console.log('200定金'); else order(orderType, pay, stack); } var order = function (orderType, pay, stack) { if (stack > 0) console.log('普通購買') else console.log('沒有庫存') }
設想一下,我們平時是不是經常封裝一個請求呢,那麼我們對這個請求利用職責鏈模式進行修改讓請求變成三部分組成,請求前,獲取請求,請求後呢
方法模板模式
方法模板模式是一種只需要對只需要繼承就可以實現的非常簡單的設計模式
方法模板模式有兩部分組成,一部分是抽象父類,第二部分是具體實現子類
抽象父類封裝字類演算法框架,包括實現一些公共方法以及封裝子類中所有方法的執行順序,字類通過繼承的方式繼承這個抽象類,並可以重寫父類方法
就用一個很有名的例子咖啡有茶來實現吧
先泡一杯茶
function Coffee() { } Coffee.prototype.boilWater = function () { console.log('把水煮沸') } Coffee.prototype.brewCoffee = function () { console.log('衝咖啡') } Coffee.prototype.init = function () { this.boilWater(); this.brewCoffee(); } var coffee = new Coffee(); coffee.init();
再泡一杯咖啡吧
function Tea() { } Tea.prototype.boilWater = function () { console.log('把水煮沸') } Tea.prototype.steepTea = function () { console.log('沖茶水') } Tea.prototype.init = function () { this.boilWater(); this.steepTea(); } var tea = new Tea(); tea.init();
這個過程大同小異,只是材料跟步驟變了,我們如何改善呢
//方法與模板模式 var Beverage = function () { } //相同的方法 Beverage.prototype.boilWater = function () { console.log('燒水') } //沖茶或者衝咖啡 不同方法 Beverage.prototype.brew = function () { } Beverage.prototype.init = function () { this.boilWater(); this.brew(); } //建立Tea字類 function Tea() { } //繼承模板類 Tea.prototype = new Beverage(); //重寫brew滿足茶的過程 Tea.prototype.brew = function () { console.log('沖茶水') } var tea = new Tea(); tea.init(); //建立Coffee function Coffee() { } //繼承模板類 Coffee.prototype = new Beverage(); //重寫brew滿足茶的過程 Coffee.prototype.brew = function () { console.log('衝咖啡') } var coffee = new Coffee(); coffee.init();
那麼什麼是模板方法模式的核心是什麼,就是這個init,這種設計模式我們在自己封裝控制元件的時候會經常用到,
封裝控制元件一定會有什麼,initProps初始化屬性,initEvent方法繫結,render渲染等等,我們是不是可以採用這種設計模式去做呢~好了。以上就是js常用的16種設計模式,有些是看書理解的,有的是自己理解,可能有誤差,希望指正,感謝感謝。
程式碼地址:https://github.com/Dqhan/DesignPattern,求星星,麼麼噠