# JS设计模式与开发实践笔记
# 第4章 单例模式
定义:保证一个类仅有一个实例,并提供全局访问。
例如单击登陆按钮,页面中出现的登陆浮窗,适合用单例模式创建。
# 实现单例模式
类中添加一个getinstance
方法保证单例,但是这样使用起来并不透明,每次都需要调用该方法。
# 透明的单例模式
通过闭包的方法使得调用方式透明,但是这种方法阅读起来不舒服,并且构造函数做了创建对象和保证只有一个对象两件事,不符合“单一职责原则”的概念。一旦不需要单例模式,就涉及到原构造函数的代码更改,带来不必要的麻烦。
# 用代理实现单例模式
原构造函数只负责创建对象,引入一个代理类负责保证只有一个对象。
# JS中的单例模式
为降低全局变量带来的命名污染,可以通过以下几种方式:
- 使用命名空间
- 使用闭包封装私有变量
# 惰性单例
在需要的时候才创建实例。
# 通用的惰性单例
遵循“单一职责原则”,将管理单例的功能抽象成一个函数,入参为一个创建实例的函数。
# 第5章 策略模式
定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
# 多态在策略模式中的体现
策略模式中Context不再包含处理各个分支的代码,而是分散给不同的策略对象中,每个对象负责各自的逻辑,每个策略对象根据初始化条件可以相互替代,Context只需要保存策略对象即可。
# 使用策略模式实现动画
linear
、easeIn
、strongEaseIn
、strongEaseOut
、sineaseIn
、sineaseout
共同包含形参:动画已消耗时间、物体原始位置、物体目标位置、动画持续总时间,根据动画名进行策略委托。
# 更广义的“算法”
只要业务规则指向目标一致,并且可被替换使用,就可以使用策略模式进行封装。
# 使用策略模式实现表单校验
# 使用策略模式的优缺点
优点
- 利用了组合、委托和多态的技术思想,有效避免多重条件选择语句
- 提供了开发-封闭的原则,算法封装在各个策略对象中,使得彼此易于切换、理解、扩展
- 提高了复用
缺点
- 需要了解不同的策略对象之间的差异才能选择合适的策略,违反最少知识原则
# 第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方法中抛出错误。
# 组合模式的例子
- 扫描文件夹
# 一些值得注意的地方
组合模式不是父子关系
对叶对象操作的一致性
双向映射关系
用职责链模式提高组合模式性能
# 引用父对象
- 职责链
- 文件删除
# 何时使用组合模式
- 表示对象的部分-整体层次结构。
- 客户希望统一对待树中的所有对象。