js常用15种设计模式

对《js设计模式与开发实践》一书的总结,感谢作者,以下是笔者自己码的字,挺辛苦的,给个赞吧,转载,请添加源链接

一、常用设计模式

1、单例模式:确保只有一个实例,并提供全局访问。

2、策略模式:定义一些列的算法,把它们一个个封装起来,并且使它们可以相互替换。

3、代理模式:为一个对象提供一个代用品或占位符,以便控制对它的访问。js里虚拟代理(网络请求方面)、缓存代理(数据方面)最常用

4、迭代器模式:提供一种方法,顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。不需要关心对象的内部构造,也可以按顺序访问其中的每个元素。很多语言都有自己内置的迭代器,比如js的Array.prototype.forEach

5、发布-订阅模式:又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态生改变时,所有依赖于它的对象都将得到通知。在js中,一般用事件模型代替它。

PS:

var a={}
a.b={}
a.c=function(key,fn){
  if(!this.b[key]){
    this.b[key]=[]
     }
  this.b[key].push(fn)
}
a.d=function(){
  var key =Array.prototype.shift.call(arguments),
      fns=this.b[key]
    console.log(arguments)
  if(!fns||fns.length===0){
    return false;
     }
  for(var i=0,fn;fn=fns[i++];){
    fn.apply(this,arguments)
  }
}
a.c('88',function(pri){
    console.log(pri)
})
a.c('110',function(pri){
    console.log(pri)
})
a.d('88',200)
a.d('110',300)

 

6、命令模式:执行某些特定事情的指令。记录信息的清单,就是命令模式中的命令对象。

PS:有时候,需要像某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。这时候就需要命令模式,使得请求发送者和请求接收者能够消除耦合关系。

7、组合模式:使用树形方式创建对象的结构,把相同的操作应用在组合对象和单个对象上。

8、模板方法模式:只需要集成就可以实现,由两部分组成,第一部分是抽象类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。

PS:

//抽象类
var a=function(){};
a.prototype.b=function(){
    console.log('把水煮沸')
}
a.prototype.c=function(){}
a.prototype.d=function(){}
a.prototype.e=function(){}
a.prototype.init=function(){
    this.b();
    this.c();
    this.d();
    this.e();
}
//子类
var g=function(){}
g.prototype.c=function(){
    console.log('用沸水冲咖啡')
}
g.prototype.d=function(){
    console.log('把咖啡倒进杯子')
}
g.prototype.e=function(){
    console.log('加糖和牛奶')
}
var h=new g()
h.init()

这里的a.prototype.init就是模板方法

9、好莱坞原则:即高层组件调用底层组件,模板方法是好莱坞的一个典型使用场景;子类放弃了对自己的控制权,而是改为父类通知子类哪些方法应该在什么时候被调用。作为子类,只负责提供一些设计上的细节。

Ps:发布-订阅模式、回调函数都用到了此原则

10、享元模式:运用共享技术来有效支持大量细粒度的对象。

Ps: 

 

  1. 内部状态存储与对象内部。
  2. 内部状态可以被一些对象共享。
  3. 内部状态独立于具体的场景,通常不会改变。
  4. 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。

11、对象池:维护一个装在空闲对象的池子,如果需要对象的时候,不是直接new,而是转从对象池里获取。如果对象池里没有空闲对象,则创建一个新的对象,当获取出的对象完成它的职责后,再进入池子等待被下次获取;

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

PS:有多个if-else条件的时候,要去考虑是否可以用职责链模式。

13、中介者模式:解除对象与对象之间得耦合关系。增加一个中介者对象后,所有得相关对象都通过中介者对象来通信,而不是互相引用,所有当一个对象发生改变时,只需要通知中介者对象即可。此模式迎合迪米特法则的一种实现,迪米特也叫做最少知识原则,是指一个对象应该尽可能少地了解另外的对象。

PS:编写思路:1、利用发布-订阅模式2、在中介者对象中开放一些接收消息得接口

14、装饰者模式:给对象动态地增加职责。这种方式并没有真正地改动对象自身,而是将对象放入另一个对象之中,这些对象以一条链的方式进行引用,形成一个聚合对象。

PS:经常用到Function.pototype.after和Function.pototype.before两个函数进行装饰

Function.prototype.before=function(beforefn){

    var _self=this;//保存原函数引用

    return function(){// 返回包含了原函数和新函数的“代理”函数

        beforefn.apply(this,arguments);// 执行新函数,且保证this不被劫持,新函数接受的参数也会被原封不动地传入原函数,新函数在原函数之前执行

        return _self.apply(this,arguments);// 执行原函数并返回原函数的执行结果,并且保证this不被劫持

    }

}   

注意:这里的beforefn和原函数_self共用一组参数列表arguments,当我们在beforefn的函数体内改变arguments的时候,_self接收的参数列表自然也会变化,所以经常用于动态地改变原函数的参数

15、状态模式:关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。此模式关键是把事物的每种状态都封装成单独的类。

PS:状态模式和策略模式区别:

策略模式的各个策略类之间是平等又平行的,他们之间没有任何联系,所以客户必须熟知这些策略类的作用。以便随时切换算法;而状态模式,状态和状态对应的行为早已被封装好的,状态之间的切换也早被规定完成,“改变行为”这件事发生在状态模式内部。对客户来说,并不需要了解这些细节,这正是状态模式的作用所在。

16、适配器模式:解决两个软件实体的接口不兼容的问题。不考虑接口是怎样实现的,也不考虑将来可能会如何让变化,适配器模式不需要改变已有的接口,就能够使他们协同作用,适配器模式通常只包装一次。

PS:

var a={
    show:function(){
        console.log('a数据源开始渲染')
    }
}
var b={
    disply:function(){
        console.log('b数据源开始渲染')
    }
}
//适配器c
var c={
    show:function(){
        return b.disply();
    }
}
var render=function(fn){
    fn.show()
}
render(a)
render(c)

17、外观模式:在JS中不常用。主要是为子系统中的一组接口提供一个一致的界面,定义了一个高层的接口,这个接口使子系统更加容易使用。在C++或者JAVA中指的是一组类的集合,这些类相互协作可以组成系统中一个相对独立的部分。但在JS中,这个子系统至少应该指的是一组函数的集合。

PS:

var A=function(){
    a1()
    a2()
}

 

二、番外篇:

1、单一职责原则(SRP):一个对象或方法只做一件事。代理、单例、迭代、装饰模式经常用到。

PS:

  1. 如果随着需求的变化,有两个职责总是同时变化,那就不必分离他们。比如ajax,创建xhr和发送xhr总是在一起,那么这个过程就没必要分开
  2. 职责的变化轴线仅当它们确定会发生变化时才具有意义,即使两个职责已经被耦合在一起,但他们还没有发生改变的征兆,那么也许没有必要主动分离它们,重构的时候在进行分离也不迟。

2、最少知识原则(LKP):也叫“迪米特法则”,一个软件实体应当尽可能少地与其他实体发生相互作用。软件实体指的是系统、类、模块、函数、变量、对象等等。

PS:

  1. 尽量减少对象之间的交互。
  2. 非要联系的化,可使用第三者对象承担两者之间的通信。
  3. 也可以让对象暴露必要的接口,让对象之间的联系限制在最小范围内。
  4. 闭包,很适合此原则。

此原则,也要根据实际开发中,情况而定,不能一味的遵守最少原则,不然会不断的增加第三对象,庞大到难以维护。

3、开放-封闭原则:软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。

PS:

  1. 最明显的就是找出程序中将要发生变化地地方,然后把变化封装起来。
  2. 通过封装变化的方式,可以把系统中稳定不变的部分和容易变化的部分隔离开来。在系统中,只需替换容易变化的部分,如果这些部分被封装好了,替换也就容易多了(可以指上面例子的b\c\d)。而变化的部分之外(不变的,可以指上面例子的a)就是稳定的部分,稳定的部分是不需要改变的。
  3. Hook、回调函数都可以帮助解决开闭原则
  4. 回调函数,把部分容易变化的逻辑封装在回调函数里,然后把回调函数当作参数传入一个稳定和封闭的函数中。这是高阶函数的技法
  5. 挑选出最容易发生变化的地方,然后构造抽象来封闭这些变化。
  6. 在不可避免发生修改的时候,尽量修改哪些相对容易的地方。比如,修改某个开源库的配置文件,总比修改它的源码简单的多。
  7. 把if换成switch-case是没用的,换汤不换药。看到if和switch的时候,要考虑是否可以用对象的多态性来重构它们。把不变的部分隔离出来,把可变的部分封装起来。
  8. var a=function(m){
        m.sound()
    }
    var b=function(){}
    b.prototype.sound=function(){
        console.log('bbbb')
    }
    var c=function(){}
    c.prototype.sound=function(){
        console.log('cccc')
    }
    a(new b())
    a(new c())
    
    //增加一个跟b和c一样的行为对象d,不用该a函数
    var d=function(){}
    d.prototype.sound=function(){
        console.log('dddd')
    }
    a(new d())

三、写代码注意点:

  1. 在最初编写代码的时候,先假设变化永远不会发生,这有利于我们速度完成需求。当变化发生并且对我们接下来的工作造成影响的时候,可以在回过头来封装这些变化的地方。然后确保我们不会掉进同一个坑里。
  2. 开闭原则是编写一个好程序的目标,其他设计原则和设计模式都是达到这个目标的过程。
  3. 将不变的部分和变化的部分隔开是每个设计模式的主题
  4. 虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值