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

本文是我阅读《JavaScript设计模式与开发实践》过程中记录下来的笔记。之前以为设计模式是要到架构师那个境界才能真正用到的,看了这本书才发现,原来我们平时就一直在接触设计模式,自己也在无意识地用到了这些设计模式。理解了这些设计模式后,对日后的编码思路和代码优化也有很大的帮助,其中原型模式策略模式发布-订阅模式给我的印象是最深刻的。

小总结

  • JavaScript是一门基于原型的面向对象语言,它的对象系统是使用原型模式搭建的,没有类的概念,将函数作为一等对象。
  • 很多设计模式都是通过闭包和高阶函数实现的;编写函数式语言风格的代码,离不开call和apply。
  • 所有设计模式的实现都遵循一条原则:找出程序中变化的部分,并将变化封装起来,注重提高可复用和可维护性,保证代码的高内聚低耦合。
  • 很多模式的类图和结构看起来几乎没有区别,例如代理模式和装饰者模式、策略模式和智能命令模式等。虽然看起来差不多,但是它们之间的意图和设计目的是不同的。
  • 我们不能刻意的去套用设计模式,而是在理解了那些优秀的设计模式后,很自然地将那些模式融入到代码中。

相关概念

  • 设计模式:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。
  • 动态类型语言:单至类型上的定义,变量类型在运行时才会确定。
  • **面向对象编程(OOP)**:本质是以建立模型体现出来的抽象思维过程和面向对象的方法。以类为中心,抽象、封装、继承和多态是面向对象的基础。
  • 抽象:提取现实世界中某事物的关键特性,为该事物构建模型的过程。得到的抽象模型(包含属性、操作)称之为类。
  • 封装:使类具有独立性和隔离性,保证类的高内聚。只暴露给类外部或者子类必须的属性和操作。
  • 继承:对现有类的一种复用机制。一个类如果继承现有的类,则这个类将拥有被继承类的所有非私有特性(属性和操作)。
  • 多态:同一操作作用于不同对象上,可产生不同的解释和不同的执行结果。通常使用继承来得到多态效果。
  • 鸭子类型:只关注对象行为,不关注对象本身。
  • **单一职责原则(SRP)**:一个对象(方法)只做一件事。(低耦合)
  • 最少知识原则(LKP)/迪米特法则:一个软件实体(类、模块、函数等)应当尽可能少地与其他实体发生相互作用。(对象之间减少交互)
  • 开放-封闭原则:软件实体应该是可以扩展的,但是不可修改。

this、call和apply

  • this是执行上下文环境的一个属性,而不是某个变量对象的属性,总是指向一个对象。
  • call和apply能很好地体现JavaScript函数式的语言特性。
    • 最常见的用途是改变函数内部的this指向。
    • apply是Function.prototype.bind的实现核心。
    • 借用其他对象的方法,扩充函数赖以运行的作用域,可以借此实现多重继承

高阶函数

高阶函数式指至少满足下列条件之一的函数:

  • 函数可以作为参数被传递,如回调、节流、分时函数。
  • 函数可以作为返回值输出,如柯里化(部分求值)函数

闭包

  • 定义:指有权访问另一个函数作用域中变量的函数。
  • 常见方式:函数内创建内部函数,外部通过内部函数访问此函数的局部变量。
  • 作用
    • 突破作用链域,将函数内部的变量和方法传递到外部。
    • 延续局部变量的生存周期,防止被垃圾回收机制清除。
  • 应用
    • 将不需要暴露在全局的变量封装成“私有变量”;
    • 缓存功能,减少重复计算或防止数据丢失;

常用的设计模式

创建型模式

原型模式

  • 定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
  • 要点:是一种对象创建型模式,实现关键是语言本身是否提供了clone方法。
  • 基本规则
    • 所有数据都是对象。
    • 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。
    • 对象会记住它的原型。
    • 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。
  • 意义:不单是一种设计模式,也是一种编程泛型,它构成了JavaScript这门语言的根本。
  • 场景:在初始化信息不发生变化的情况,用克隆进行拷贝。

单例模式

  • 定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 要点:将创建对象和管理单例的职责分布在两个不同的方法中。
  • 意义:惰性单例模式是重点,指的是在需要的时候才创建唯一的对象实例,非常简单实用。
  • 场景:登录浮窗、全局缓存、window对象等。

结构型模式

代理模式

  • 定义:为对象提供一个代用品或占位符,以便控制对它的访问。
  • 要点:虚拟代理是最常用的,用一个类去访问原类的接口,进行额外的操作。
  • 意义:在客户看来,代理对象和本体是一致的(可通过鸭子类型来检测一致性),用户可以放心得在任何使用本体的地方替换成使用代理。
  • 场景:虚拟代理(将开销大的对象延迟到需要时再创建),如懒加载、合并请求;缓存代理(为开销大的运算结果提供临时存储);保护代理(控制不同权限的对象对目标对象的访问)

组合模式

  • 定义:用小的子对象来构建更大的对象。
  • 要点:将对象合成树形结构,以表示“部分-整体”的层次结构。
  • 意义:通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。
  • 场景:文件系统的增删改查等树形结构场景。

享元模式

  • 定义:运用共享技术有效的支持大量细粒度的对象。
  • 要点:将对象属性划分为内部状态与外部状态,剥离了外部状态的对象成为共享对象,在合适的时刻将外部状态组装进共享对象。
  • 意义:用于性能优化,用时间换空间,通过共享大幅度减少单个实例的数目。
  • 场景:上次创建的DOM节点共享给下次创建操作。

装饰者模式

  • 定义:在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责,常常会形成一条装饰链。
  • 要点:将对象放入另一个对象中,这些对象以链的形式进行引用,形成一个聚合对象。请求沿着链依次传递到所有对象,每个对象执行完自身操作,再讲请求转发给下一个对象。
  • 意义:在框架开发中非常有用,个性化功能可在框架之外动态装饰上去。
  • 场景:统计函数执行时间、插件式表单验证等。

适配器模式

  • 定义:将一个类的接口转换成客户希望的另外一个接口。
  • 要点:不需要改变已有接口,只包装一次。
  • 意义:复用现存的类,客户端统一调用同一接口,更简单、直接、紧凑。
  • 场景:当系统的数据和行为都正确,但接口不符时。

行为型模式

策略模式

  • 定义:定义一系列算法,各自封装成策略类,对context发起请求时,context将请求委托给对应的策略类。
  • 要点:将算法的使用(环境类context)和算法的实现(策略类)分离开来。
  • 意义:策略模式是对象多态的完美体现,高阶函数就是一种隐式的策略模式。策略模式已经融入到了JS语言本身中。
  • 场景:游戏动效、验证表单等。

迭代器模式

  • 定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
  • 要点:循环访问聚合对象(包括数组和类数组对象)中的各个元素,但没有规定访问规则,即可使用顺序、倒序、中序等方式。
  • 意义:将迭代的过程从业务逻辑中分离出来,不用关心对象的内部构造。绝大部分语言都内置了迭代器。
  • 场景:内部迭代器(内部已定义好迭代规则,外部不需要关心迭代器内部实现,外部仅需一次初始调用);外部迭代器(必须显示地请求迭代下一个元素,可手动控制迭代的过程或顺序)

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

  • 定义:当一个对象的状态发生变化时,所有依赖于它的对象都将得到通知。
  • 要点:在JS中是用注册回调函数的形式来代替传统的发布订阅者模式。
  • 意义:发布订阅者模式可以实现对时间及对象间的解耦。从架构上看,MVC和MVVM都少不了发布订阅者模式,而且JS本身也是一门基于事件驱动的语言。
  • 场景:DOM节点上绑定事件函数;订阅ajax请求的error、succ等事件;模块之间的通信;

命令模式

  • 定义:将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及执行可撤销的操作。
  • 要点:客户(client)创建命令;调用者(invoking)执行该命令;接收者(receiving)在命令执行时执行相应操作。当命令对象可以直接实现请求时,就不再需要接受者的存在,这时的命令模式与策略模式非常相近,只有意图的不同。
  • 意义:旨在将函数的调用、请求和操作封装成一个单一的对象;解耦调用者与接收对象。
  • 场景:命令模式与策略模式一样融入了JS语言中。

模板方法模式

  • 定义:子类实现中的相同部分被上移到父类,将不同部分留待子类实现。
  • 要点:在抽象父类中封装好子类的算法框架和方法的执行顺序,再由子类继承抽象类。
  • 意义:基于继承的代码复用技术。
  • 场景:有重复行为的平行子类。

职责链模式

  • 定义:一系列可能会处理请求的对象被连接成一条链,请求沿着链传递,直到有对象处理为止。
  • 要点:节点数量和顺序可以自由变化,在运行时也能决定链中包含哪些节点。
  • 意义:解耦了请求的发送者和多个接收者之间的关系。
  • 场景:作用域链、原型链、DOM节点的事件冒泡。

中介者模式

  • 定义:用一个中介对象来封装一系列的对象交互。
  • 要点:所有相关对象之间的交互都交给中介者来实现和维护。
  • 意义:解除对象与对象之间的紧耦合关系。
  • 场景:form表单各项的实时校验。

状态模式

  • 定义:将对象的内部状态都封装成独立的类,请求会被委托给当前的状态对象。
  • 要点:不同状态下,对象像是从不同的类中实例化而来的。
  • 意义:状态模式是状态机的实现之一。
  • 场景:游戏人物状态切换等。

代码重构技巧

  1. 提炼函数,包括重复代码、条件分支语句等情况
  2. 合理使用循环
  3. 使用return提前让函数退出
  4. 传递对象参数代替过程的参数列表
  5. 尽量减少参数数量
  6. 少用三目运算符(可读性差)
  7. 合理使用链式调用
  8. 分解大类型