Javascript设计模式-学习笔记

朱治龙
2022-04-13 / 0 评论 / 21 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2022年04月15日,已超过999天没有更新,若内容或图片失效,请留言反馈。

设计模式概念解读

设计模式(Design pattern)是一套被反复使用、思想成熟、经过分类和无数 实战设计经验 的总结的。使用设计模式是为了让系统代码可重用、可扩展、可解耦、更容易被人理解且能保证代码可靠性。设计模式使代码开发真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。只有夯实地基搭好结构,才能盖好坚实的大楼。也是我们迈向高级开发人员必经的一步。

单例模式

文字解读

单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。

构造函数模式

文字解读

构造函数用于创建特定类型的对象——不仅声明了使用的对象,构造函数还可以接受参数以便第一次创建对象的时候设置对象的成员值。你可以自定义自己的构造函数,然后在里面声明自定义类型对象的属性或方法。

在JavaScript里,构造函数通常是认为用来实现实例的,JavaScript没有类的概念,但是有特殊的构造函数。通过new关键字来调用自定义的构造函数,在构造函数内部,this关键字引用的是新创建的对象。

拟物化解读

l1wvhodq.png

代码

示例一:

//1.用于创建特定类型的对象
//2.这样的函数名会被人笑话的
//3.js开发的时候写单引号
//4.js里构造函数比较特殊的地方new
//5.其他的语言里比如PHP里人家实现有一个关键字A class
//6.zaomen就是构造函数他又充当了类的概念
function zaomen (suo, huawen) {
  if (!(this instanceof zaomen)) {
    return new zaomen(...arguments);
  }
  this.suo = suo || '普通'
  this.huawen = huawen || '普通'
  this.create = function(){
    return ' [锁头] '+this.suo +' [ 花纹] '+ this.huawen
  }
}
var xiaozhang = new zaomen('指纹锁', '高贵')
alert('xiaozhang' + xiaozhang.create())

var xiaoli  = zaomen('密码锁', '雕花')
alert('xiaoli' + xiaoli.create())

示例二:结合单例模式

var Panpan = {
  zaomen: function (suo, huawen) {

    this.suo = suo || '普通'
    this.huawen = huawen || '普通'
    this.create = function(){
      return ' 盼盼造门:[锁头] '+this.suo +' [ 花纹] '+ this.huawen
    }
  }
}
var Tubaobao = {
  zaomen: function (suo, huawen) {
    this.suo = suo || '普通'
    this.huawen = huawen || '普通'
    this.create = function(){
      return ' 兔宝宝造门:[锁头] '+this.suo +' [ 花纹] '+ this.huawen
    }
  }
}

var xiaozhang = new Panpan.zaomen('指纹锁', '高贵')
alert('xiaozhang' + xiaozhang.create())

var xiaoli  = new Tubaobao.zaomen('密码锁', '雕花')
alert('xiaoli' + xiaoli.create())

工厂模式

概念解读

工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型(抽象工厂)。

这个模式十分有用,尤其是创建对象的流程赋值的时候,比如依赖于很多设置文件等。并且,你会经常在程序里看到工厂方法,用于让子类类定义需要创建的对象类型。

使用场景

  • 1.对象的构建十分复杂。
  • 2.需要依赖具体的环境创建不同实例。
  • 3.处理大量具有相同属性的小对象。

注意事项:

  • 1.不能滥用工厂,有时候仅仅是给代码增加复杂度。

代码实战

简单工厂

//这是一个简单工厂模式
var XMLHttpFactory = function() {
}
XMLHttpFactory.createXMLHttp = function() {
  var XMLHttp = null
  //XMLHttpFactory.createXMLHttp()这个方法根据当前环境的具体情况返回一个XHR对象。
  if (window.XMLHttpReqyest){
    XMLHttp = new XMLHttpRequest()
  } else if(window.ActiveXObject) {
    XMLHttp = new ActiveXOb ject("Microsof+.XMLHTTP")
    return XMLHttp
  }
}
var AjaxHander = function() {
  var XMLHttp = XMLHttpFactory.createXMLHttp();
    /...具体的操作..*/
}

抽象工厂

//这是一个抽象工厂模式
var XMLHttpFactory = function() {
}
XMLHttpFactory.prototype = {
  //如果真的要调用这个无法会抛出一个错误,它不能被实例化,只能用来派生子类
  createFactory: function(){
    throw new Error('This is an abstract class')
  }
}
//派生子类,文章开始处有基础介绍那有讲解继承的模式,不明白可以去参考原理
var XHRHandler = function() {
  XMLHttpFactory.call(this)
}
XHRHandler.prototype = new XMLHttpFactory()
XHRHandler.prototype.constructor = XHRHandler
//重新定义createFactory方法

XHRHandler.prototype.createFactory = function() {
  var XMLHttp = null
  if (window.XMLHttpRequest){
    XMLHttp = new XMLHttpRequest()
  }else if(window.ActiveXObject){
    XMLHttp = new ActiveXObject('Microsoft.XMLHTTP')
  }
  return XMLHttp
}

代理模式

概念解读

代理,顾名思义就是帮助别人做事,GoF对代理模式的定义如下:
代理模式(Proxy) ,为其他对象提供一种代理以控制对这个对象的访问。
代理模式使得代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资

源,内存中的对象,或者是一-些难以复制的东西。

l1wyogvu.png

使用场景

  • 1.远程代理(一个对象将不同空间的对象进行局部代理)。
  • 2.虚拟代理(根据需要创建开销很大的对象如渲染网页暂时用占位代替真图)。
  • 3.安全代理(控制真实对象的访问权限)。
  • 4.智能指引(调用对象代理处理另外一些事情如垃圾回收机制)。

代码实战

//代理模式需要3方
//1.买家
function maijiq(argument){
  this.name = '小明'
}
//2.中介卖房
function zhongjie(){
  
}
zhongjie.prototype.maifang = function() {
  new fangdong(new maijia().maifang('20万')
}
//3.房东坐等收钱
function fangdong(maijia){
  this.mailjia_name = maijia.name
  this.maifang = function(money){
    alert('收到了来自I ' + this.maijia_ name+' ]' + money+'人民币')
  }
}
(new zhongjie).maifang()

建造者模式

概念解读

建造者模式可以将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。也就是说如果我们用了建造者模式,那么用户就需要指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。建造者模式实际,就是一个指挥者,-个建造者,一个使用指挥者调用具体建造者工作得出结果的客户。
建造者模式主要用于“分步骤构建一个复杂的对象”,在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。

l1wzf7ec.png

适用场景

  • 1.分步创建一一个复杂的对象。
  • 2.解耦封装过程和具体创建的组件。
  • 3.无需关心组件如何组装。

注意事项

  • 1.一定要一个稳定的算法进行支持。
  • 2.加工工艺是暴露的。

代码实战

// 1.产出的东西是房子
// 2.baogongtou调用工人进行开工而且他要很清除工人们具体的某一个大项
// 3.工人是盖房子的工人可以建卧室建客厅建厨房
// 4.包工头只是一个接口而已他不干活他只对外说我能盖房子
function Fangzi() {
  this.woshi = ''
  this.keting = ''
  this.chufang = ''
}
function Baogongtou() {
  this.gaifangzi = function(gongren1) {
    gongren1.jian_woshi()
    gongren1.jian_keting()
    gongren1.jian_chufang()
  }
}

function Gongren() {
  this.jian_woshi = function() {
    console.log('卧室盖好了')
  }
  this.jian_keting = function() {
    console.log('客厅建好了')
  }
  this.jian_chufang = function() {
    console.log('厨房建好了')
  }
  this.jiaogong = function() {
    var _fangzi = new Fangzi()
    _fangzi.woshi = 'ok'
    _fangzi.keting = 'ok'
    _fangzi.chufang = 'ok'
    return _fangzi
  }
}

var gongren = new Gongren()
var baogongtou = new Baogongtou()
baogongtou.gaifangzi(gongren)
// 主人要房子
var myfangzi = gongren.jiaogong()
console.log(myfangzi)

命令模式

概念解读

命令模式(Command)的定义是:用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行。也就是说该模式旨在将函数的调用、请求和操作封装成一个单一的对象,然后对这个对象进行一系列的处理。它也可以用来消除调用操作的对象和实现操作的对象之间的耦合。这为各种具体的类的更换带来了极大的灵活性。

l1x6c48d.png

模式作用

  • 1.将函数的封装、请求、调用结合为一体。
  • 2.调用具体的函数解耦命令对象与接收对象。
  • 3.提高程序模块化的灵活性。

注意事项

  • 1.不需要接口一致,直接调用函数即可,以免造成浪费。

代码实战

var lian = {}
lian.paobing = function(pao_num) {
  console.log(pao_num + '门炮开始战斗')
}
lian.bubing = function(bubing_num) {
  console.log(bubing_num + '个人开始战斗')
}
lian.lianzhang = function(mingling) {
  lian[mingling.type](mingling.num)
}
lian.lianzhang({
  type: 'paobing',
  num: 100
})

lian.lianzhang({
  type: 'bubing',
  num: 500
})

观察者模式 / 发布订阅模式

概念解读

观察者模式又叫发布订阅模式(Publish/Subscribe) ,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。

模式作用

  • 1.支持简单的广播通信,自动通知所有已经订阅过的对象。
  • 2.页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
  • 3.目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

注意事项:

  • 1.监听要在触发之前。

代码实战

// 提前引入jQuery
~(function() {
  var $ = {}
  var o = $({})
  $.jianting = function() {
    o.on.apply(a.arguments)
  }
  $.fabu = function() {
    o.trigger.apply(arguments)
  }
  $.qingchu = function() {
    o.off.apply(o, arguments)
  }
})()
$.jianting('/test/ls', function(e, a, b, c) {
  console.log(a + '||' + b + '||' + c)
})
$.jianting('/test/ls', function(a, b, c) {
  console.log('ok')
})
$.fabu('/test/ls', [1, 2, 3])

适配器模式

概念解读

适配器模式(Adapter)是将一个类(对象)的接口(方法或属性)转换成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一起工作。

l1xa7hup.png

模式作用

  • 1.使用一个已经存在的对象,但其方法或接口不符合你的要求。
  • 2.创建一个可复用的对象,该对象可以与其他不相关或不可见的对象协同工作。
  • 3.使用已经存在的一个或多个对象,但是不能进行继承已匹配它的接口。

注意事项

  • 1.与代理模式的区别,代理模式是不改变原接口适配是原接口不符合规范。

代码实战

function pp() {
  this.test = function() {
    console.log('我是原test')
  }
}

pp.prototype.gogo = function() {
  console.log('我是原go')
}
function shipeiqi() {
  var s = new pp
  var aa = {
    test: function () {
      s.test()
    },
    go: function() {
      s.gogo()
    }
  }
  return aa
}
var bb = shipeiqi()
bb.test()
bb.go()

责任链模式

概念解读

职责链模式是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。
链中收到请求的对象要么亲自处理它,要么转发给下一个候选者。提交方并不明确有多少个对象会处理它,任一候选者都可以响应相应的请求,可以在运行时刻决定哪些候选者参与到链中。

l1xb8le4.png

模式作用

  • 1.dom的冒泡有些类似职责链。
  • 2.nodejs 当controller中有很多负责操作逻辑的时候拆分中间件。
  • 3.解耦发送者和接受者。

注意事项

  • 1.javascript中的每一次 「.」 是有代价的,要在必要的时候应用。

代码实战

// 职责链模式
function laoban(xiangmujingli) {
  if (xiangmujingli) {
    this.xiangmujingli = xiangmujingli
  }
}
laoban.prototype.write = function(code) {
  this.xiangmujingli.write(code)
}
function xiangmujingli(coder) {
  if (coder) {
    this.coder = coder
  }
}
xiangmujingli.prototype.write = function(code) {
  this.coder.write(code)
}
function coder() {
}
coder.prototype.write = function(code) {
  console.log('coding:' + code)
}
// 由begin发起,coder结束
var begin = new laoban(new xiangmujingli(new coder()))
begin.write('php')

迭代器模式

概念解读

迭代器模式提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该方法中的内部表示。
jquery中我们经常会用到一个each函数就是迭代器模式。

l1xcrwzp.png

模式作用

  • 1.为遍历不同的集合结构提供一个统一 的接口, 从而支持同样的算法在不同的集合结构上进行操作。
  • 2.对于集合内部结果常常变化各异,我们不想暴露其内部结构的话,但又响让客户代码透明底访问其中的元素,这种情况下我们可以使用迭代器模式。

注意事项

  • 1.一般的迭代,我们至少要有2个方法,hasNext()和Next(), 这样才做做到遍历所有对象。
  • 2.遍历的同时更改迭代器所在的集合结构可能会导致问题(比如C#的foreach里不允许修改item)。

代码实战

var arr = ['1', '2', '3', 'test', 20080708]
var diedai = (function() {
  var length = arr.length
  var index = 0
  return {
    hasNext: function() {
      return index < length
    },
    next: function() {
      var data = arr[index]
      index = index + 1
      return data
    },
    reset: function() {
      index = 0
    }
  }
})()

while(diedai.hasNext()) {
  console.log(diedai.next())
}

外观模式

概念解读

外观模式(Facade) 为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式不仅简化类中的接口,而且对接口与调用者也进行了解耦。外观模式经常被认为开发者必备,它可以将一些复杂操作封装起来,并创建一个简单的接口用于调用。

模式作用

  • 1.在设计初期,应该要有意识地将不同的两个层分离,比如经典的三层结构。
  • 2.在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加外观模式可以提供一个简单的接口,减少他们之间的依赖。
  • 3.在维护一个遗留的大型系统时,为系统开发一个外观Facade类,为设计粗糙和高度复杂的遗留代码提供比较清晰的接口,让新系统和Facade对象交互。

注意事项

    1. 外观模式被开发者连续使用时会产生一定的性能问题,因为在每次调用时都要检测功能的可用性。

代码实战

var stopEvent = function(e){
  // 同时阻止时间默认行为和冒泡
  e.stopPropagation()
  e.preventDefault()
}
// stopEvent 本身就是生产门面
$('#xxxx').click(function(e){
  stopEvent(e)
})

策略模式

概念解读

策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。

模式作用

  • 1.所有的这些算法都是做相同的事情,只是实现不同。
  • 2.以相同的方式调用所有的方法,减少了各种算法类与使用算法类之间的耦合。
  • 3.单独定义算法类,也方便了单元测试。

注意事项

  • 1.不仅可以封装算法,也可以用来封装几乎任何类型的规则,是要在分析过程中需要在不同时间应用不同的业务规则,就可以考虑是要策略模式来处理各种变化。

代码实战

var $input = $('#input')
var util = {
  isEmpty: function() {
    return false
  },
  isTel: function() {
    return true
  }
}
var isEmpty = util.isEmpty($input.val())
var isTel = util.isTel($input.val())
if (!isEmpty && isTel ) {
  console.log('通过校验')
}
// OR $.fn.validate
$.input.vallidate({
  isEmpty: false,
  isTel: true
})

中介者模式

概念解读

中介者模式(Mediator) ,用一个中介对象来封装一系列的对象交互。 中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

模式作用

  • 1.软件开发中,中介者是一个行为设计模式,通过提供一个统一的接口让系统的不同部分进行通信。一般,如果系统有很多子模块需要直接沟通,都要创建一个中央控制点让其各模块通过该中央控制点进行交互。中介者模式可以让这些子模块不需要直接沟通,而达到进行解耦的目的。

注意事项

  • 1.当系统出现了多对多交互复杂的对象群时,先不要急于使用中介者模式,而是要思考一下是不是系统设计有问题。

代码实战

// 飞机
var feiji = function(name) {
  this.name = name
}
feiji.prototype.send = function(msg, to) {
  console.log(this.name + '发送了信息')
  tatai.send(msg, to)
}
feiji.prototype.jieshou = function(msg) {
  console.log(this.name + '接收到' + msg)
}
// 塔台
var tatai = {
  all: {},
  zhuce: function(feiji) {
    this.all[feiji.name] = feiji
  },
  send: function(msg, to) {
    this.all[to.name].jieshou(msg)
  }
}
var feiji1 = new feiji('feiji1')
var feiji2 = new feiji('feiji2')
tatai.zhuce(feiji1)
tatai.zhuce(feiji2)
feiji1.send('我马上降落,还有200米', feiji2)

原型模式

概念解读

原型模式(prototype) 是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。
对于原型模式,可以利用JavaScript特有的原型继承特性去创建对象的方式,真正的原型继承是作为最新版的ECMAScript5标准提出的,使用0bject.create方 法来创建这样的对象,如Object.create(prototype, optionalDescriptorObjects)

模式作用

  • 1.原型对象本身就是有效地利用了每个构造器创建的对象

注意事项

  • 1.注意的依然是浅拷贝和深拷贝的问题,免得出现引用问题。
  • 2.现有的文献里查看原型模式的定义,没有针对JavaScript的,你可能发现很多讲解的都是关于类的,但是现实情况是基于原型继承的JavaScript完全避免了类(class)的概念。

代码实战

// 深拷贝克隆对象。。。

模板方法

概念解读

模板方法(TemplateMethod) 定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法是一种代码复用的基本技术,在类库中尤为重要,因为他们提取了类库中的公共行为。模板方法导致一种反向的控制结构,这种结构就是传说中的“好莱坞法则”,即“别找找我们,我们找你”,这指的是父类调用一个类的操作,而不是相反。具体体现是面向对象编程编程语言里的抽象类(以及其中的抽象方法),以及继承该抽象类(和抽象方法)的子类。

模式作用

  • 1.一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
  • 2.各子类中公共的行为应被提取出来并集中到一个公共父类中的避免代码重复,不同之处分离为新的操作,最后,用一个钓鱼这些新操作的模板方法来替换这些不同的代码
  • 3.控制子类扩展,模板方法只在特定点调用“hook” 操作,这样就允许在这些点进行扩展

注意事项

  • 1.和策略模式不同,模板方法使用继承来改变算法的一部分,而策略模式使用委托来改变整个算法。

代码实战

/***示例一:造人的模板***/
function shangdi() {}
shangdi.prototype.zaoren_yanjing = function() {
  console.log('眼睛')
}
shangdi.prototype.zaoren_bizi = function() {
  console.log('鼻子')
}
shangdi.prototype.zaoren_zuiba = function() {
  console.log('嘴巴')
}
shangdi.prototype.zaoren_yanjing = function() {
  throw new Error('我只是个钩子,需要你自己去探索')
}

// 小明
function xiaoming() {
  console.log('小明是上帝的子类')
  shangdi.call(this)
}
xiaoming.protype = new shangdi()
xiaoming.prototype.aihao = function(){
  console.log('小明爱讲笑话')
}


/***示例二:流程***/
function liucheng(){}
liucheng.prototype.start = function() {
  confirm('您是否要进入游戏?')
}

liucheng.prototype.loading = function() {
  confirm('游戏加载中…………')
}

liucheng.prototype.out = function() {
  confirm('您是否要离开游戏?')
}
function xiaojigege(){}
xiaojigege.prototype = new liucheng()
xiaojigege.start()

装饰者模式

概念解读

装饰者提供比继承更有弹性的替代方案。装饰者用用于包装同接口的对象,不仅允许你向方法添加行为,而且还可以将方法设置成原始对象调用(例如装饰者的构造函数)。

装饰者用于通过重载方法的形式添加新功能,该模式可以在被装饰者前面或者后面加上自己的行为以达到特定的目的。

模式作用

  • 1.装饰者是-种实现继承的替代方案。当脚本运行时,在子类中增加行为会影响原有类所有的实例,而装饰者却不然。取而代之的是它能给不同对象各自添加新行为。
  • 2.添加辅助的额外功能。
  • 3.把类(函数)的核心职责和装饰功能区分开了。

注意事项

  • 1.装饰的类跟被装饰的类,要求拥有相同的访问接口方法(功能)。
  • 2.装饰类的要有对被装饰类的引用,用于在装饰类的相应方法,调用相应被装饰类的方法,然后对其进行修饰。
  • 3.把每个要装饰的功能放在单独的函数里。

代码实战

var fangzi = function() {}
fangzi.prototype.kongjian = function() {
  console.log('我是空的房子')
}
var zhuangshi = function(fangzi) {
  this.zfangzi = fangzi
}
zhuangshi.prototype.kongjian = function() {
  this.zfangzi.kongjian()
  console.log('我添加了一个家具')
}
var _fangzi = new fangzi()
var _zhuangshi = new zhuangshi(_fangzi)
_zhuangshi.kongjian()

组合模式

概念解读

组合模式(Composite) 将对象组合成树形结构以表示“ 部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。

组合模式

模式作用

  • 1.你想表示对象的部分整体层次结构时。
  • 2.你希望用户忽略组合对象和单个对象的不同,用户将统一地使用组合结构中的所有对象(方法)

注意事项

  • 1.该模式经常和装饰者一起使用,它们通常有一个公共的父类(也就是原型)因此装饰必须支持具有add、remove、 getChild操作的 component接口。

代码实战

var zhengti() {}
zhengti.prototype.kafei = function() {
  throw new Error('不能直接使用')
}
zhengti.prototype.mianbao = function() {
  throw new Error('不能直接使用')
}

function guke() {}
guke.prototype.kafei = function() {}
guke.prototype.mianbao = function() {}
guke.prototype.diancan = function() {
  this.kafei()
  this.mianbao()
}
0

评论 (0)

取消