Javascript十六种常用设计模式
漠然1992丶 人气:0单例模式
何为单例模式,就是无论执行多少次函数,都只会生成一个对象哈哈,看一个简单的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,求星星,么么哒
加载全部内容