likes
comments
collection
share

《javaScript设计模式与开发实践》笔记

作者站长头像
站长
· 阅读数 64

内容

  • 各种模式的定义和作用
  • 模式的优缺点
  • 模式在实际项目中的用途
  • 模式之间的关系

第一章 原型模式

js对象通过原型链来实现继承和查找对象属性

第二章 this, bind, call, apply;

  • this指向,默认window,普通函数、箭头函数、对象方法、原型方法中使用
  • call和apply区别,参数不同,应用场景
    • 修正this,指向
    • 借用该类型本没有的方法,如[].push.call(obj, xx)

第三章 闭包、高阶函数

  • 闭包概念、使用场景
  • 高阶函数特征,参数、返回值为函数 应用广泛
    • 异步回调、数组排序、遍历等
    • 返回值为函数,即函数操作可持续进行,柯里化、uncurry
  • 分时函数,1000个好友分批创建dom节点,每批加8个
var timeChunk = function( ary, fn, count ){ 
  var obj, 
    t; 
  var len = ary.length; 
  var start = function(){ 
    for ( var i = 0; i < Math.min( count || 1, ary.length ); i++ ){ 
      var obj = ary.shift(); 
      fn( obj ); 
    } 
  }; 
  return function(){ 
    t = setInterval(function(){ 
      if ( ary.length === 0 ){ // 如果全部节点都已经被创建好
        return clearInterval( t ); 
      } 
      start(); 
    }, 200 ); // 分批执行的时间间隔,也可以用参数的形式传入
  }; 
};
  • 惰性加载函数,实例:浏览器嗅探,只执行一次条件语句,且不调用函数时不执行
var addEvent = function( elem, type, handler ){ 
  if ( window.addEventListener ){ 
    addEvent = function( elem, type, handler ){ 
      elem.addEventListener( type, handler, false ); 
    } 
  }else if ( window.attachEvent ){ 
    addEvent = function( elem, type, handler ){ 
      elem.attachEvent( 'on' + type, handler ); 
    } 
  } 
  addEvent( elem, type, handler ); 
};

第4章 单例模式

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

作用:有些对象很有用但在项目中只需要出现一个,就可以使用单例模式创建

实现:传统面向对象语言的单例模式,通过new来创造实例对象,通过 getInstance 来获取唯一的对象;在js中可以通过闭包和高阶函数实现单例对象

用途:线程池、框架的全局对象、window、全局缓存等

注意点:避免变量污染,可以使用使用命名空间或闭包

var ctx;
if(!ctx){
  ctx = xx
}

将它封装在闭包中,就实现惰性的单例方法,只在需要的时候创建一次。

再通过分离fn和单例方法,来实现通用的惰性单例

function getSingle(fn){
  let instance;
  return function (){
    return instance || (instance = fn.apply(this, arguments))
  }
}

第五章 策略模式

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

目的:将算法的使用和算法的实现分离开。策略模式的实现并不复杂,关键是如何从策略模式的实现背后,找到封装变化、委托和多态性这些思想的价值。

涉及的原则:开放-封闭原则、复用原则、最少知识原则

优点:将策略算法与Context逻辑分离;利用多态、委托、组合等思想和技术,有效避免if/else分支;可复用、易扩展

缺点:违反最少知识原则,必须了解所有strategy

第六章 代理模式

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

代理模式的关键是, 当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身 对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之 后,再把请求转交给本体对象

涉及的原则:依赖倒置原则,单一职责原则,开放-封闭原则

扩展

  • 保护代理:保护代理用于控制不同权限的对象对目标对象的访问
  • 虚拟代理 虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建

什么时候用?当真正发现不方便直接访问某个对象的时候,再编写代理也不迟

第七章 迭代器模式

定义:迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

作用:迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

优点

  1. 支持不同方式遍历一个聚合对象;
  2. 迭代器简化了聚合类;
  3. 同一个聚合上可以有多个遍历;
  4. 增加新的聚合类和迭代器类方便

缺点:新的聚合类需要对应增加新的迭代器类,类的个数成对增加,系统复杂度一定程度上增加

使用:内部迭代器(遍历),外部迭代器(提供next和结束的迭代),倒序迭代器、中止迭代器...

第八章 发布订阅模式

定义:发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状

态发生改变时,所有依赖于它的对象都将得到通知。

作用:可以广泛应用于异步编程;可以取代对象之间硬编码的通知机制。

应用:公众号订阅、餐厅叫号等

优点:时间上的解耦,对象间的解耦

缺点:需要消耗一定的时间和内存,可能会出现订阅以后一直未发布的情况,但订阅对象一直在内存中

var event = { 
  clientList: [], 
  listen: function( key, fn ){ 
    if ( !this.clientList[ key ] ){ 
      this.clientList[ key ] = []; 
    } 
    this.clientList[ key ].push( fn ); // 订阅的消息添加进缓存列表
  }, 
  trigger: function(){ 
    var key = Array.prototype.shift.call( arguments ), // (1); 
      fns = this.clientList[ key ]; 
    if ( !fns || fns.length === 0 ){ // 如果没有绑定对应的消息
      return false; 
    } 
    for( var i = 0, fn; fn = fns[ i++ ]; ){ 
      fn.apply( this, arguments ); // (2) // arguments 是 trigger 时带上的参数
    } 
  } 
};

var installEvent = function( obj ){ 
 for ( var i in event ){ 
 obj[ i ] = event[ i ]; 
 } 
};

第九章 命令模式

定义:命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令

作用:请求对象和调用对象解耦;命令对象方便传递;支持撤销和排队

优点:降低耦合、新命令易添加

缺点:额外维护命令对象,可能有过多的命令类

项目中的应用:操作栏命令、电视机命令

其他应用: 宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令。

智能命令 vs 策略模式: 没有接收者的智能命令,退化到和策略模式非常相近,从代码结构上已经无法分辨它们,能分辨的只有它们意图的不同。策略模式指向的问题域更小,所有策略对象的目标总是一致的,它们只是达到这个目标的不同手段,它们的内部实现是针对“算法”而言的。而智能命令模式指向的问题域更广,command 对象解决的目标更具发散性。命令模式还可以完成撤销、排队等功能。

第十章 组合模式

定义:组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成。

思想:事物是由相似的子事物构成

应用场景

  • 表示对象的部分-整体层次结构。
  • 利用多态性统一对待树中的所有对象

注意点

  • 组合模式不是父子关系
  • 对叶节点对象保持一致性
  • 双向映射关系不适合用组合模式,比如架构师属于不同部门,用组合模式可能收到多份节日费

缺点

  • 太多的节点带来的性能问题,可借助职责链模式处理
  • 与普通树对象难以区分

项目中的应用:组件成组功能

第十一章 模版方法模式

定义 模版模式是基于继承来实现的一种模式

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

解决:避免子类重复出现相同方法

思想:封装变化提高系统可扩展性

项目中的应用:com类,各种框架模板

通过钩子函数可以实现更多的变化,如某一步骤是否调用

第十二章 享元模式

定义:享元模式要求将对象的属性划分为内部状态与外部状态(状态在这里通常指属性)。

如何区分内部状态和外部状态

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

目标:尽量减少共享对象的数量

什么时候用

  • 对象的大多数状态都可以变为外部状态。 6
  • 一个程序中使用了大量的相似对象。
  • 由于使用了大量对象,造成很大的内存开销。
  • 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。

项目应用:文件上传创建了大量的上传对象

优缺点:优点是优化了性能,避免资源浪费;缺点是代码复杂度提高,需要分离和维护内部状态与外部状态

第十三章 职责链模式

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

优点

  • 请求发送者只需要知道链中的第 一个节点,从而弱化了发送者和一组接收者之间的强联系
  • 使用了职责链模式之后,链中的节点对象可以灵活地拆分重组
  • 可以手动指定起始节点,请求并不是非得从链中的第一个 节点开始传递。

缺点

  • 我们不能保证某个请求一定会被链中的节点处理。
  • 职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分 节点并没有起到实质性的作用

使用场景:作用域链、原型链、事件冒泡等

第十四章 中介者模式

作用:解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。

优点:对象解耦,符合最少知识原则

缺点:加入了中介对象,对象关系越复杂,中介对象越难维护

什么时候用:对象之间的复杂耦合导致调用和维护出现了困难,而且这些耦合度随项目的变化呈指数增长曲线

建议:中介者模式可以非常方便地对模块或者对象进行解耦,但对象之间并非一定需要解耦。在实际项目中,模块或对象之间有一些依赖关系是很正常的。毕竟我们写程序是为了快速完成项目交付生产,而不是堆砌模式和过度设计。关键就在于如何去衡量对象之间的耦合程度。

项目应用:商品页,商品参数与库存关系

第十五章 装饰者模式

定义:给对象动态地增加职责的方式称为装饰者模式。

作用:装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。

对比继承:装饰者模式更灵活,“即插即用”,不会影响原函数

对比代理模式:主要是意图和设计目的不同。代理的关系是本体与代理,本体可以拒绝访问者;装饰者更像是给对象赋予功能

使用场景:需要职责分离,为方法提供可插拔的功能。框架中使用的多,比如表单的 validate

优点:符合开放-封闭原则、职责分离原则

缺点:装饰链可能会比较长

思考:

继承: A 对象有init 方法和fire方法,init给初始化一些属性;B想拥有属性,继承了A对象,并重写fire方法

装饰者:A还是A,只是想给fire加点功能,通过改写Function原型,用before和after钩子可以轻松给fire加功能而且不用改写fire内部

let a={
  init: function(){},
  fire: function(){}
}

Function.prototype.before = function(beforeFn){
  var _self = this;
  return function (){
    beforeFn.apply(this, arguments)
    return _self.apply(this, arguments)
  }
}
a.fire.before(function(){
  //添加功能
})

第十六章 状态模式

定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。

结构:上下文,状态类,状态类中封装行为,且每个状态类都实现了统一接口

优点

  • 有状态类,容易增加新的状态
  • 避免Context无限膨胀,去掉了条件语句
  • 对象代替字符串记录状态,更清晰
  • Context请求与状态分离,方便更改

缺点:封装了很多状态类,状态类太多时逻辑容易分散,无法在一个地方看出有哪些状态

对比策略模式:它们类图很相似,都有Context上下文,状态或策略类,上下文将请求交给状态类执行。

意图不同,策略类之间平等且平行,客户需要知道它们的作用

状态类之间切换,且行为早已封装好,行为改变发生在状态内部,客户不需要知道细节。

应用场景:文件上传状态切换、音乐器播放状态、酒店智能开关状态

第十七章 适配器模式

定义:像现实中的电源适配器一样对不同的对象接口进行适配

作用:适配器模式的作用是解决两个软件实体间的接口不兼容的问题

对比其他模式

  • 适配器模式解决接口不匹配的问题,不考虑接口如何实现,也不考虑将来如何演化,只包装一次且不改变原有接口
  • 装饰者模式主要为给对象增加功能,可能有很长的装饰链,适配器只会有一层
  • 代理模式为控制对对象的访问,通常也只包装一次

应用:本地数据与第三方接口数据格式不一致,对第三方接口数据进行适配

第十八章 单一职责原则

定义:一个方法处理一件事

有哪些模式应用:代理模式、迭代器模式等

优点:降低类或对象复杂度,方便测试、复用,修改时不影响其他对象

缺点:增大代码复杂度

应用建议:方便性与稳定性取舍问题,根据具体情况判定,不一定非得遵守该原则;什么情况下不需要分离?

  • 随需求变化,两个职责都同时变化,就不需要分离,比如xhr创建请求对象和发送xhr对象
  • 两个职责耦合在一起但没有变化的征兆

第十九章 最少知识原则

定义:实体之间尽可能少的相互作用。在应用中,可以引入第三方来管理实体之间的联系

有哪些模式应用:中介者模式、命令模式

优缺点同上

第二十章 开放-封闭原则

定义:软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。需要加功能的时候不改动源码

实现

  • 利用对象多态性消除条件分支,示例:鸭子类
  • 封装变化,hook钩子、回调函数

设计模式:发布订阅、模板方法、策略模式、代理模式、职责链模式

注意点

  • 尽可能挑出最容易变化的地方,构建抽象来封闭变化
  • 需要修改时,尽量修改容易修改的地方
  • 接受一次愚弄,可以在需要封闭时再去做

第二十一章 接口和面向接口编程

什么是接口

  1. 字面意义上的api接口,供外部使用,不需关注内部实现
  2. 静态语言提供的关键字 interface,为编写类提供一种契约,方便编译器检查
  3. 面向接口编程的接口

向上转型的方式

  • 抽象类

示例:鸭子类型中,鸭子和鸡类继承往上一层的抽象类 animal,实现了契约和多态,在编写过程中能被类型检查到

面向接口编程在过程上看更像“面向超类型编程”,对象的具体类型隐藏在超类型背后,对象就可以相互替换使用。

  • interface

interface实际上也是继承的一种方式,叫接口继承

第二十二章 代码重构

熟悉设计模式和代码重构工作总是很难分开的,针对代码重构提出些建议

  • 提炼函数: 大型需要多段注释才能函数需要提炼出来,起个好的名字本身可以代替注释
  • 合并重复条件分支语句:每个条件分支都出现的代码片段,可以提出来
  • 条件分支提炼成函数:例如 if(/^[a-zA-z]+://[^\s]*$/.test(val)) 可以将语句封装为工具函数 isUrl, if(isUrl)
  • 合理使用循环:示例 嵌套try catcch
  • 提前让函数退出代替嵌套条件分支:错误处理置前
  • 对象参数代替过长参数:传递的参数过多就用对象代替,就不用在意传递顺序
  • 少用多层三目运算:可读性并不高的长三目运算,还是老老实实用条件语句吧
  • 尽量减少参数数量:(w, h, area) 面积能用宽高算就别传了
  • 合理使用链式调用:易变动的方法链式调用难调试
  • 分解大型类:示例 游戏类又管流程又管攻击类型,干脆把攻击方式提出来作为攻击类
  • return退出大型循环:多层循环,用flag标记满足条件后退出,可以直接在满足条件时return
转载自:https://juejin.cn/post/7190261077148434489
评论
请登录