设计模式 设计模式(Design pattern)代表了最佳的实践,是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
单例模式
单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。
应用场景:
创建唯一的浮窗(闭包)
const getSingle = function ( fn ) { const result; return function ( ) { return result || ( result = fn .apply(this , arguments ) ); } }; const createLoginLayer = function ( ) { const div = document .createElement( 'div' ); div.innerHTML = '我是登录浮窗' ; div.style.display = 'none' ; document .body.appendChild( div ); return div; }; const createSingleLoginLayer = getSingle( createLoginLayer );document .getElementById( 'loginBtn' ).onclick = function ( ) { const loginLayer = createSingleLoginLayer(); loginLayer.style.display = 'block' ; };
策略模式
策略模式 (Strategy Pattern)又称政策模式,其定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。封装的策略算法一般是独立的,策略模式根据输入来调整采用哪个算法。关键是策略的实现和使用分离。
实例:(螺丝刀与螺丝刀头、车与轮胎)
在这些场景中,有以下特点:
螺丝刀头/轮胎(策略)之间相互独立,但又可以相互替换;
螺丝刀/车(封装上下文)可以根据需要的不同选用不同的策略;
应用场景
奖金计算,绩效为 S 的人年 终奖有 4 倍工资,绩效为 A 的人年终奖有 3 倍工资,而绩效为 B 的人年终奖是 2 倍工资
const calculateBonus = (() => { const strategies = { "S" : function ( salary ) { return salary * 4 ; }, "A" : function ( salary ) { return salary * 3 ; }, "B" : function ( salary ) { return salary * 2 ; } }; return { get: (level, salary ) => { return strategies[level] && strategies[level](salary) }, set: (level, fn ) => { strategies[level] = strategies[level] || fn } } })() calculateBonus.set( 'C' , (salary ) => { return salary * 1 } ); calculateBonus.get( 'S' , 20000 ); calculateBonus.get( 'A' , 10000 );
策略模式的优缺点
策略模式将算法的实现和使用拆分,这个特点带来了很多优点:
1、策略之间相互独立,但策略可以自由切换,这个策略模式的特点给策略模式带来很多灵活性,也提高了策略的复用率;
2、如果不采用策略模式,那么在选策略时一般会采用多重的条件判断,采用策略模式可以避免多重条件判断,增加可维护性;
3、可扩展性好,策略可以很方便的进行扩展;
策略模式的缺点:
1、策略相互独立,因此一些复杂的算法逻辑无法共享,造成一些资源浪费;
2、如果用户想采用什么策略,必须了解策略的实现,因此所有策略都需向外暴露,这是违背迪米特法则/最少知识原则的,也增加了用户对策略对象的使用成本。参考链接
代理模式
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问
故事背景:
假设当 A 在心情好的时候收到花,小明表白成功的几率有 60%,而当 A 在心情差的时候收到花,小明表白的成功率无限趋近于 0。 小明跟 A 刚刚认识两天,还无法辨别 A 什么时候心情好。如果不合时宜地把花送给 A,花 被直接扔掉的可能性很大,这束花可是小明吃了 7 天泡面换来的。 但是 A 的朋友 B 却很了解 A,所以小明只管把花交给 B,B 会监听 A 的心情变化,然后选 择 A 心情好的时候把花转交给 A
代码实现:
const Flower = function ( ) {};const xiaoming = { sendFlower: function ( target ) { const flower = new Flower(); target.receiveFlower( flower ); } }; const B = { receiveFlower: function ( flower ) { A.listenGoodMood(function ( ) { const flower = new Flower(); A.receiveFlower( flower ); }); } }; const A = { receiveFlower: function ( flower ) { console .log( '收到花 ' + flower ); }, listenGoodMood: function ( fn ) { setTimeout (function ( ) { fn(); }, 10000 ); } }; xiaoming.sendFlower( B );
由上面的例子可以引出两种代理模式
保护代理
代理 B 可以帮助 A 过滤掉一些请求,比如送花的人中年龄太大的或者没有宝马的,这种请求就可以直接在代理 B 处被拒绝掉
虚拟代理
假设现实中的花价格不菲,导致在程序世界里,new Flower 也是一个代价昂贵的操作, 那么我们可以把 new Flower 的操作交给代理 B 去执行,代理 B 会选择在 A 心情好时再执行 new Flower
虚拟代理实现图片预加载
const myImage = (function ( ) { const imgNode = document .createElement( 'img' ); document .body.appendChild( imgNode ); return { setSrc: function ( src ) { imgNode.src = src; } } })(); const proxyImage = (function ( ) { const img = new Image; img.onload = function ( ) { myImage.setSrc( this .src ); } return { setSrc: function ( src ) { myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' ); img.src = src; } } })(); proxyImage.setSrc('http://imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
虚拟代理合并HTTP请求 假设我们在做一个文件同步的功能,当我们选中一个 checkbox 的时候,它对应的文件就会被同 步到另外一台备用服务器上面。当一次选中过多时,会产生频繁的网络请求。将带来很大的开销。可以通过一个代理函数 proxySynchronousFile 来收集一段时间之内的请求, 最后一次性发送给服务器
const synchronousFile = function ( id ) { console .log( '开始同步文件,id 为: ' + id ); }; const proxySynchronousFile = (function ( ) { const cache = [], timer; return function ( id ) { cache.push( id ); if ( timer ){ return ; } timer = setTimeout (function ( ) { synchronousFile( cache.join( ',' ) ); clearTimeout ( timer ); timer = null ; cache.length = 0 ; }, 2000 ); } })(); const checkbox = document .getElementsByTagName( 'input' ); for ( let i = 0 , c; c = checkbox[ i++ ]; ){ c.onclick = function ( ) { if ( this .checked === true ){ proxySynchronousFile( this .id ); } } };
参考链接
观察者模式
定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript 开发中,我们一般用事件模型 来替代传统的发布—订阅模式。
DOM 事件
实际上,只要我们曾经在 DOM 节点上面绑定过事件函数,那我们就曾经使用过发布—订阅模式,来看看下面这两句简单的代码发生了什么事情:
document .body.addEventListener( 'click' , function ( ) { alert(2 );}, false ); document .body.click();
发布-订阅模式的通用实现
const event = { clientList: [], listen: ( key, fn ) => { if ( !this .clientList[key] ){ this .clientList[key] = []; } this .clientList[key].push(fn) }, trigger: (...arg ) => { const key = Array .prototype.shift.call( arg ), fns = this .clientList[key]; if ( !fns || fns.length === 0 ){ return false ; } for ( let i = 0 , fn; fn = fns[i++]; ) { fn.apply( this , arg ); } }, remove: ( key, fn ) => { const fns = this .clientList[key]; if ( !fns ){ return false ; } if ( !fn ){ fns && (fns.length = 0 ); } else { for ( let l = fns.length - 1 ; l >=0 ; l-- ){ if ( _fn === fn ){ fns.splice( l, 1 ); } } } } }; 再定义一个 installEvent 函数,这个函数可以给所有的对象都动态安装发布—订阅功能: const installEvent = obj => { for ( let i in event ){ obj[ i ] = event[ i ]; } }; 再来测试一番,我们给售楼处对象 salesOffices 动态增加发布—订阅功能: const salesOffices = {};installEvent( salesOffices ); salesOffices.listen( 'squareMeter88' , price => { console .log( '价格= ' + price ); }); salesOffices.listen( 'squareMeter100' , price => { console .log( '价格= ' + price ); }); salesOffices.trigger( 'squareMeter88' , 2000000 ); salesOffices.trigger( 'squareMeter100' , 3000000 );
命令模式
用于将一个请求封装成为对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及执行可撤销的操作。
菜单例子
const Customer = function (command ) { return { book: function (food, time ) { return command.execute(food, time); }, undo: function (menu ) { command.undo(menu); } }; } const foodCommand = function (cook ) { return { execute: function (food, time ) { const timer = cook.willCook(food, time); return timer; }, undo: function (food ) { cook.unCook(food); } }; } const cook = function ( ) { return { willCook: function (food, time ) { console .log('时间在' + time + ":开始煮:" + food); const timer = setTimeout (function ( ) { console .log(food + '完成了' ); }, time); return timer; }, unCook: function (timer ) { clearTimeout (timer); } }; } const command = foodCommand(cook());const customer = Customer(command);const receipt = customer.book('西红寺炒鸡蛋' , 5000 ); customer.undo(receipt);
代理模式与命令模式区别
JavaScript 可以用高阶函数非常方便地实现命令模式。命令模式在 JavaScript 语言中是一种隐形的模式。
在代理(委托)模式中,调用者就是委托者,执行者就是被委托者,委托者和被委托者接口定义是相同的;在命令模式中,调用者不关注执行者的接口定义是否和它一致。
在调用时机上,代理模式的具体执行是只能在特定的调用者内部执行(接口相同);命令模式的具体执行可以在任何调用者内部执行(接口不相同也可以)。
迭代器模式
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
内部迭代器
const each = function ( ary, callback ) { for ( const i = 0 , l = ary.length; i < l; i++ ){ callback.call( ary[i], i, ary[ i ] ); } }; each( [ 1 , 2 , 3 ], function ( i, n ) { alert ( [ i, n ] ); });
外部迭代器
const Iterator = function ( obj ) { const current = 0 ; const next = function ( ) { current += 1 ; }; const isDone = function ( ) { return current >= obj.length; }; const getCurrItem = function ( ) { return obj[ current ]; }; return { next: next, isDone: isDone, getCurrItem: getCurrItem }; } const compare = function ( iterator1, iterator2 ) { while ( !iterator1.isDone() && !iterator2.isDone() ){ if ( iterator1.getCurrItem() !== iterator2.getCurrItem() ){ throw new Error ( 'iterator1 和 iterator2 不相等' ); } iterator1.next(); iterator2.next(); } alert ( 'iterator1 和 iterator2 相等' ); } const iterator1 = Iterator( [ 1 , 2 , 3 ] );const iterator2 = Iterator( [ 1 , 2 , 3 ] );compare( iterator1, iterator2 );
倒序迭代器
const reverseEach = function ( ary, callback ) { for ( let l = ary.length - 1 ; l >= 0 ; l-- ){ callback( l, ary[ l ] ); } }; } reverseEach( [ 0 , 1 , 2 ], function ( i, n ) { console .log( n ); });
中止迭代器
const each = function ( ary, callback ) { for ( let i = 0 , l = ary.length; i < l; i++ ){ if ( callback( i, ary[ i ] ) === false ){ break ; } } }; each( [ 1 , 2 , 3 , 4 , 5 ], function ( i, n ) { if ( n > 3 ){ return false ; } console .log( n ); });
组合模式
组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构。 除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性
未完,待续…… 8. 组合模式
装饰器模式
外观模式
享元模式
代理模式
责任链模式
命令模式
解释器模式
迭代器模式
中介者模式
备忘录模式
观察者模式
状态模式
介绍
验证
验证