JavaScript设计模式和开发实践

345 阅读14分钟

设计模式是面试以及作为高级工程师解决问题的方法。

接下来总结一下常用额设计模式:

单例模式

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点

实例:线程池,全局缓存,浏览器中的window对象等,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会创建一次,那么这个登录浮窗就适合单例模式来创建。

总结:单例模式是一种简单但是非常使用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只是唯一的。更奇妙的是,创建对象和管理单例的职责被分布在不同的方法中,这两个方法组合起来才具有单例模式

策略模式

俗话说条条大路通罗马。在美国《越狱》中,主角Michael Scofiled就设计了两条越狱的道路。这两条道路都可以到达靠近监狱外墙的医务室。

同样,在现实中,很多途径也有很多途径到达同一目的地。在程序设计中,也常常遇到类似的问题,要实现某一功能有多种方案可以选择。比如压缩文件的程序,可以是zip算法,也可以是gzip算法。

定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

应用:编写一些让div飞来飞去的动画,验证表单

优缺点:

优点:

1.策略模式利用组合,委托和多态等技术和思想,可以有效地避免多重条件选择语句

2.策略模式提供了对开放-封闭原则的原煤支持,将算法封闭在独立的strategy中,使得它们易于切换,易于理解,易于扩展

3.策略模式的算法可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作

4.在策略模式中利用组合和委托来让Context拥有执行计算法的能力,这也是继承的一种更轻便的替代方案

缺点:

1.使用策略模式会让程序增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌俄在Context中要好。

2.要使用策略模式,必须了解所有的strategy,必须了解各个strategy之间的不同点,这样才能选择一个合适的strategy。

使用策略模式重构代码

经过思考,我们想到更好的方法-使用策略模式来重构代码。策略模式指的是定义一系列算法,把它们一个个封装起来。将不变的不分和变化的部分隔开是每个设计模式的主题。

策略模式的目的是将算法的使用和算法的实现分离开来

一个基本策略模式的程序至少由两部分组成。第一部分是一组策略类,策略类封装了具体的算法,并负责具体的算法过程。第二部分是环境类Context,Context接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明Context中要维持对某个策略对象的引用

就像从北京到上海的方案有,飞机,火车,大巴,自行车,轿车等策略类,但是根据客户的需求乘坐不同的工具去,那飞机,火车等属于策略类,不同的乘客做不同的工具属于环境类Context


代理模式

定义:代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。

举例:明星有经纪人代理

不用代理的模式:

客户->本体

使用代理的模式:

客户- >代理->本体

保护代理和虚拟代理

保护代理用于控制不同权限的对象对目标对象的访问,但在JavaScript并不容易实现保护代理,因为我们无法判断谁访问了某个对象。而虚拟代理是最常见的一种代理,虚拟代理就是把一些开销大的对象,延迟到最需要它的时候才去创建

虚拟代理实现图片预加载:如果直接给某一个img标签节点设置src属性,由于图片过大或者网络不佳,图片的位置往往会是一片空白。常用的做法就是先用一场loading图占位,然后用异步的方式加载图片,等图片加载好再把它填充到img节点里,这种场景适合使用虚拟代理。

缓存代理

缓存代理应用到ajax异步请求数据

我们在常常在项目中遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直接使用之前的数据


总结:

代理模式包括许多小分类,在Javascript开发中最常见的是虚拟代理和缓存代理。虽然代理模式非常有用,但它们在编写业务的时候,往往不需要去预先猜测是否需要使用代理模式。当真正发现不方便直接访问某个对象的时候,再编写不迟

迭代器模式

定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。即不关系对象的内部构造,也可以按顺序访问其中的元素。

应用:jQuery的$.each

实现自己的迭代器

var each=function(ary,callback){for(var i=0,l=ary.length;i<l;i++){
    callback.call(ary[i],i,ary[i]);//把下标和元素当作参数传给callback参数
}}

each([1,2,3],function(i,n){
   alert([i,n])
})

总结:

迭代器模式是一种相对简单的模式,简单到很多时候我们都不认为它是一种好的设计模式。目前的绝大部分语言都内置了迭代器

发布-订阅模式(观察者模式)

定义:定义对象间的一种一对多的依赖关系,当一个状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript开发中,我们一般用实践模型来替代传统的发布-订阅模式。

举例:dom的点击事件,模块间的通信

优点:

1.发布-订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的函数,比如订阅ajax请求的error,succ等事件

2.可以取代对象之间的硬编码的通知机制,一个对象不用显式地调用另一个对象的某个接口。

DOM事件

document.body.addEventListener('click',function(){
   alert(2)
},false)

document.body.click()//模拟用户点击

我们没有办法预知用户什么时候点击。所以我们订阅document.body上的click事件,当body点击时,body节点便会向订阅者发布这个消息。

总结:

一为时间上的解耦,二为对象之间的解耦。即可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写。发布-订阅模式还可以用来帮助实现一些别的设计模式,比如中介者模式。从架构上来看,无论是MVC还是MVV吗,都少不了发布-订阅模式的参与,而且JavaScript本身也是一门基本事件驱动的语言。

当然,发布-订阅模式也不是完全没有缺点。创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外,发布-订阅模式虽然弱化了对象之间的联系,但是过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以维护和理解。特别是有多个发布者和订阅者嵌套到一起的时候,要跟踪一个bug不是件轻松的事件。

命令模式

定义:这些记录着订餐信息的清单,便是命令模式的命令对象,分为简单和优雅,命令模式中的命令指的是一个执行某些特定事件的指令。

举例:有时候需要向某些对象发送请求,但是并不知道请求的接受者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式设计程序,使得请求发送者和请求者能够消除彼此的耦合关系。

小结:

JavaScript可以用高阶函数非常方便地实现命令模式。命令模式在Javascript语言中是一种隐形的模式。

组合模式

定义:用小的子对象构建更大的对象,而这些小的子对象本身也许是由更小的‘孙对象’构成

举例:

值得注意的是组合模式不是父子关系,是HAS-A(聚合)的关系

总结:

组合模式可以让我们使用树创建对象的结构,我们可以把相同的操作应用到组合对象和单个对象上。在大多数情况下我们都可以忽略掉组合对象和单个对象之间的差别,从而用一致的方式来处理它们。

然而,组合模式并不是完美的,它可能会产生一个这样的系统:系统中的每个对象看起来都与其他对象差不多。它们的区别只有在运行的时候才会显示出来,这会使代码难以理解。此外,如果通过组合模式创建太多的对象,那么这些对象可能会让系统负担不起。

模版方法模式

定义:是一种只需使用继承就可以实现的非常简单的模式。分为两部分:第一部分是抽象父类,第二部分是具体的实现子类,通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以重写父类的方法。

在模版方法模式中,子类实现中的相同部分被上移到父类中,将不同的部分留待子类来实现,这也是很好的体现了泛化的思想。

小结:模版方法模式是一种典型的通过封装变化提高系统扩展性的设计模式。

享元模式

定义:用于性能优化的模式,运用共享技术来有效支持大量细粒度的对象。

总结:享元模式是解决性能问题而生的模式,这跟大部分模式的诞生原因都不一样。在一个存在大量相似对象的系统中,享元模式可以很好的解决大量对象带来的性能问题。

职责链模式

定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象形成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

举例:

1.如果早高峰能顺利挤上地铁的话,那么估计这一天都会过得很开心

2.中学时代的期末考试,如果平时不太老实,考试时就会被安排在第一个为止。

最大优点:请求发送者只需要直到链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系。

小结:

无论是作用域链,原型链,还是DOM节点中的事件冒泡,我们都能从中找到职责链模式的影子。职责链模式还可以和组合模式结合在一起,用来连接部件的父部件,或是提高组合对象的效率。学会使用职责链模式,相信在以后的代码编写中,将会对你大有裨益

中介者模式

定义:解除对象之间的耦合松散,而且可以独立地改变它们之间的交互

小结:

中介者模式是迎合迪米特法则的一种实现。迪米特法则也叫最少知识原则,是指一个对象应该尽可能少的了解另外的对象(类似不和陌生人说话)。如果对象之间耦合性太高,一个对象发生改变之后,难免会影响到其他的对象,跟“城门失火,殃及池鱼”的道理是一样的。而在中介者模式里,对象之间几乎不知道彼此的存在,它们只能通过中介者对象来相互影响对方。

装饰者模式

定义:给对象动态地增加职责的方式成为装饰者模式。装饰者模式可以在不改变自己的基础上,在程序运行期间给对象动态地添加职责,即用即付、

举例:数据上报,统计函数的执行事件,动态改变函数参数以及插件式的表单验证这4个例子

小结:

装饰者模式在实际开发中非常有用,它在框架开发中也十分有用。作为框架作者,我们希望框架里的函数提供的是一些稳定而方便移植的功能,那些个性化的功能可以在框架之外动态装饰上去,这可以避免为了让框架拥有更多的功能,而去使用一些if,else语句预测用户的实际需要。

状态模式

定义:解决某些需求场景的最好方法。区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。

举例:同一个开关按钮,在不同的状态下,可以开也可以关

总结:通过状态模式重构代码之后,很多杂乱无章的代码会变得清晰。虽然状态模式一开始并不是非常理解,但我们又必须好好掌握这种设计模式。

适配器模式

定义:解决两个软件的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能功能的两个软件实体可以一起工作,别名叫包装器。是一种亡羊补牢的模式  

举例:当我们试图调用模块或者对象的某个接口时发现这个接口的格式并不符合目前的需求。这是就需要创建一个适配器。港式插头转换器

设计原则和编程技巧

设计原则:

1.单一职责原则,里氏替换原则,依赖倒置原则,接口隔离原则,合成复用原则和最少知识原则。

单一职责原则

例如代理模式,迭代器模式,单例模式和装饰者模式

最少知识原则(迪米特法则)

定义:一个软件实体应当尽可能少地与其他实体发生相互作用

实例:体现最多的地方是中介者模式和外观模式

开放-封闭原则

定义:软件实体(类,模块,函数)等应该是可以扩展的,但是不可修改

举例:发布-订阅,模版方法模式,策略模式,代理模式,职责链模式