# JS设计模式与开发实践笔记

# 第4章 单例模式

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

例如单击登陆按钮,页面中出现的登陆浮窗,适合用单例模式创建。

# 实现单例模式

类中添加一个getinstance方法保证单例,但是这样使用起来并不透明,每次都需要调用该方法。

# 透明的单例模式

通过闭包的方法使得调用方式透明,但是这种方法阅读起来不舒服,并且构造函数做了创建对象和保证只有一个对象两件事,不符合“单一职责原则”的概念。一旦不需要单例模式,就涉及到原构造函数的代码更改,带来不必要的麻烦。

# 用代理实现单例模式

原构造函数只负责创建对象,引入一个代理类负责保证只有一个对象。

# JS中的单例模式

为降低全局变量带来的命名污染,可以通过以下几种方式:

  • 使用命名空间
  • 使用闭包封装私有变量

# 惰性单例

在需要的时候才创建实例。

# 通用的惰性单例

遵循“单一职责原则”,将管理单例的功能抽象成一个函数,入参为一个创建实例的函数。

# 第5章 策略模式

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

# 多态在策略模式中的体现

策略模式中Context不再包含处理各个分支的代码,而是分散给不同的策略对象中,每个对象负责各自的逻辑,每个策略对象根据初始化条件可以相互替代,Context只需要保存策略对象即可。

# 使用策略模式实现动画

lineareaseInstrongEaseInstrongEaseOutsineaseInsineaseout共同包含形参:动画已消耗时间、物体原始位置、物体目标位置、动画持续总时间,根据动画名进行策略委托。

# 更广义的“算法”

只要业务规则指向目标一致,并且可被替换使用,就可以使用策略模式进行封装。

# 使用策略模式实现表单校验

# 使用策略模式的优缺点

优点

  • 利用了组合、委托和多态的技术思想,有效避免多重条件选择语句
  • 提供了开发-封闭的原则,算法封装在各个策略对象中,使得彼此易于切换、理解、扩展
  • 提高了复用

缺点

  • 需要了解不同的策略对象之间的差异才能选择合适的策略,违反最少知识原则

# 第6章 代理模式

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

# 保护代理和虚拟代理

保护代理可以用于控制不同权限的对象对目标对象的访问。

虚拟代理可以代理开销很大的对象,延迟到真正需要它的时候才创建。

# 虚拟代理实现图片预加载

通过loading图作为代理对象,待真正的图片加载完成后才开始使用其src。

# 代理的意义

单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。

例如代理实现图片预加载中,原本的类要负责img节点设置src和预加载图片,将预加载的实现通过代理完成可以解耦。如果不需要预加载时,使用代理也不再需要更改原类中的代码。

# 代理和本体接口的一致性

代理接手的过程对用户来说是透明的,接口一致性好处有两点:

  • 用户可以放心请求代理,只关心结果
  • 在任何使用本体的地方都可以替换成使用代理

# 虚拟代理合并HTTP请求

例如有一个文件列表,每点击一个文件的发送按钮就会将该文件发送到服务器,点击按钮这个操作可能在1s内出发多次,但是每点击一次就发送一次会造成较多的网络开销。可以使用一个虚拟代理,收集2s内需要发送的文件,然后进行批量发送。

# 虚拟代理在惰性加载中的应用

miniConsole通过缓存队列缓存参数实现惰性加载。

# 缓存代理

  • 计算乘积

  • ajax异步请求数据

# 用高阶函数动态创建代理

# 其他代理模式

  • 防火墙代理:控制网络资源访问
  • 远程代理:为一个对象在不同地址空间提供局部代表
  • 保护代理:限制不同访问权限
  • 智能引用代理:取代了简单的指针,在访问对象时执行一些附加操作,例如计算一个对象被引用的次数
  • 写时复制代理:通常用于复制一个庞大对象的情况,写时复制代理延迟了复制的过程,只有对象被修改了才会对其进行复制。

# 第7章 迭代器模式

定义:提供一种方法顺序访问一个聚合对象的各个元素,而又不需要暴露对象的内部表示。

# 内部迭代器和外部迭代器

  • 内部迭代器调用时非常方便,外界无需关注迭代器内部的实现,跟迭代器的交互仅在一次初始调用。但这样缺乏了一些灵活性,例如无法决定何时进行下一次迭代。
  • 外部迭代器必须显示请求迭代下一次元素,增加了一定的复杂性,但是比较灵活。

# 迭代类数组对象或字面量对象

只要有length属性并且允许下标访问,就可以进行迭代。

# 倒序迭代器

# 终止迭代器

callback函数执行结果如果为false,可以决定跳出迭代。

# 迭代器模式的应用举例

例如文件上传存在不同对象,例如IE中的ActiveObject、Flash插件、原生表单,如果通过if else进行判断则会违反开放封闭原则,开发者需要关注内部实现并且代码难以阅读、维护。通过迭代器模式,将兼容判断逻辑分发给各个对象,可以简化调用过程,并且可以随时新增一个新的对象,遵循开放封闭原则。

开发封闭原则:对修改封闭,对扩展开放。

# 第8章 发布-订阅模式 | 观察者模式

定义:对象间存在一种一对多的关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知。

# 第9章 命令模式

定义:请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

# 命令模式的用途

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

# 命令模式的例子——菜单程序

描述:某个用户界面存在数十个Button,因此决定让一部分人完成Button的样式,一部分人完成点击Button之后执行的逻辑。

设计按钮的人并不知道按下这个按钮之后会发生什么事情,因此只需要预留一个点击接口setCommand,该接口接受一个Button和一个Command对象,点击Button之后就执行Command对象的execute方法。负责逻辑功能的人只需要将相应的功能封装到一个Command对象中即可。

# JavaScript中的命令模式

execute方法只是一个约定,在JS中也可以直接传入一个函数而不是Command对象,该函数利用闭包的性质。

# 撤销命令

需要记录命令执行前的状态,提供一个undo接口。

# 撤销和重做

撤销一系列命令,有时候通过undo无法完成,例如canvas擦除某条曲线比较困难。可以选择记录历史操作,然后重做。

# 命令队列

例如动画按钮,用户可能连续点击多次;可以将多个命令对象保存在队列中,待一个完成后再执行下一个。

# 宏命令

宏命令是命令模式和组合模式的联合产物。

# 智能命令与傻瓜命令

# 第10章 组合模式

# 组合模式用途

  • 表示树形结构。
    • 程序递归调用组合对象下面叶子对象的execute方法。
  • 利用对象多态性统一对待组合对象和单个对象。
    • 调用者无需关心它是组合对象还是单个对象,只需调用execute方法。

# 请求在树中传递的过程

以宏命令为例,如果当前处理的对象是组合对象,则遍历其子节点;如果当前处理对象是叶对象,则叶对象自动对请求做相应处理。

# 抽象类在组合模式中的作用

父对象和子对象都拥有相同的方法,因此可以使用一个抽象类进行约定,实现该抽象类的类必须实现对应的抽象方法。

# 透明性带来的安全问题

叶对象负责具体的实现,和组合对象负责添加子节点和遍历子节点。如果对叶对象进行add方法调用是不合适,因此可以在其add方法中抛出错误。

# 组合模式的例子

  • 扫描文件夹

# 一些值得注意的地方

  • 组合模式不是父子关系

  • 对叶对象操作的一致性

  • 双向映射关系

  • 用职责链模式提高组合模式性能

# 引用父对象

  • 职责链
  • 文件删除

# 何时使用组合模式

  • 表示对象的部分-整体层次结构。
  • 客户希望统一对待树中的所有对象。