JavaScript设计模式返璞归真
闲话
本文写于2月初,原分享在有道云笔记上,现在转移到此。现阶段对于设计模式的理解还没有能力对本文的框架做出大的翻新,所以暂且原样搬过来了。
本文不从名目众多的设计模式谈起,而是为语言能力、代码优化、交互关系三个方面寻找解决方案,从而引出设计模式的使用和作用。覆盖了《 javascript设计模式与开发实践 》提到的全部15种javascript内常用的设计模式,具体设计模式的讲解可以翻阅此书。
一、语言特性
动态语言:程序运行,变量被赋值时才具有了类型
高阶函数:函数的参数和返回值,都可以是函数
原型继承:无严格类,通过this和原型实现赋值和继承
优点:天然面向接口,无需向上转型。创建单例无需声明类。调用函数无需创建对象。
缺点:无类别,错误容易隐藏。添加对象的初始化约束(如不按指定参数创建 throw error),繁复且不容易。
二、编程原则
设计模式的存在意义,是服务于“最佳编程实践”的思想
- 单一职责(SRP):一个对象(方法)只做一件事
- 最少知识原则(LKP 又称迪米特法则):如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的联系
- 开放-封闭原则:软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改
三、场景分类
设计模式的应用场景可大致分为3类:
语言能力:语言层面的能力,能实现什么样的功能
Ⅰ:单一对象 | ①功能性用法 ② 工具性用法
Ⅱ:功能增强 | ① 平面(直接扩展) ② 切面(AOP) ③ 时间轴(多对象复用)
代码优化:抽离 “可变” 与 “不变”,提炼函数,优化代码,使维护容易
Ⅰ:提炼函数
Ⅱ:分支优化 | ① 单一因素 ② 多因素 ③ 涉及不同状态
Ⅲ:享元与模板
交互关系:简化各个对象的交互耦合模型,解耦或者降低耦合
四、解决方案
1、语言能力:(单一职责原则、开放-封闭原则)
开发中,通常遵循单一职责原则,一个对象(方法)只做一件事。
这引出了两个重要的话题:
- Ⅰ:单一对象的功能结构
- Ⅱ:如何对一个对象实现“功能增强”
Ⅰ:单一对象
单一对象的功能结构分两类:常规(不做讨论)、闭包
闭包:(闭包实现的常规方式都能实现。闭包的作用是保存变量的状态,但不会污染环境)
图一:功能性用法,用途体现在函数体本身。对需要记录状态的功能使用闭包(如 命令模式 的撤销undo)
图二:工具性用法,作为其他函数或对象的辅助工具。(自执行为单例,手动执行为工厂。图三为面向对象式)
① 宏命令
命令集合,一并执行。本质为 cache、add、iterator 的组合。
配合 “命令模式”,如表单验证的规则,可依次添加,打包进行
② 发布-订阅模式(观察者模式)
本质为 cache、add、remove、iterator 的组合,多了事件名指定 cache={“click”: [fn1,fn2…], “move”: [fn1] …… }
通常对应名字 listen、remove、trigger
③ 组合模式
本质为 cache、add、iterator 的组合。推入堆栈的是组合对象,全部都实现 iterator 接口,执行时调用所有对象的 iterator 进行深度遍历。
叶节点需单独定义,到此终止。可增加指定 parent 的功能,实现自我栈出、删除等功能
Ⅱ:功能增强
利用特性:闭包、高阶函数
① 平面(代理、工具式包装)
图一,典型的 代理模式,以基本功能函数为框架,实现细节上的功能补充。
比如“只允许函数产生一个实例(单例模式)”、“每300毫秒只触发一次(函数节流)”、“函数支持json格式(内部转为顺序参数)”等
代理模式的理想情况:为了减少使用者的记忆负担,proxyFn 与 fn(可以是对象) 的接口和需要传入参数一致。(— fn 的 平稳升级 —)
增强版:接口的第一个参数设置为 Context 环境。(比如原函数是 fn1.fn , 可手动指定以 fn1 调用代理函数)
图二,变体, 被代理者位置从 “参数” 换到 “调用者”
图三(对象池)、图四(外部迭代器,如游标),工具式包装,针对性提供一套接口体系。
“适配器模式” 也比较类似一个简化的工具式包装
② 切面(AOP)
当逻辑的扩充,出现在原函数的切面,而非本身时
图一,“装饰者模式”,实现AOP切面编程。还可用“职责链模式”,参照下面的分支优化②,可构造 “面向切面编程” 及相关逻辑,实现 开放-封闭原则。(单一职责,不要糅合其他作用)
可以链式调用,return值加入判断,则可实现分支判断、终止链条。也可实现为 after(fn, nextFn)这种形式。
“职责链模式”是基于引用,”装饰者模式”是基于封装。因此”职责链模式”还能够实现异步AOP,而”装饰者模式”不行。
③ 时间轴(每次函数执行的数据都能被存储、索引、操作)
有序保存 fn 每次运行时产生的数据,需要利用 “堆栈”(数组或对象)
两种方式:
1. 图一,fn能返回所需数据时,只需把所有需要的数据推入堆栈(如实现 “命令模式” 的撤销N步、重演等)
2. 图二,fn只能操作数据时,需构建create方法提供API操作 fn返回的接口,并把API集合挂载在外层堆栈上(闭包,复用活动对象。活动对象被API的[[scope]]引用,不会销毁)
如:对基本的 “发布-订阅模式” 增加 namespace 寻址。由于 fn 返回的接口只能操作数据,无法把数据推入外部堆栈,必须复用活动对象(方法2)
2、代码优化:(开放-封闭原则、提炼函数)
书写代码通常的原则是:分离 “可变” 与 “不变”。
姑且分为三类:提炼函数、分支优化、享元与模板
Ⅰ:提炼函数
有时候函数显得肿胀不堪,导致主逻辑看不分明,此时也许需要提炼了
图一,可以分离组件,“命令模式” 封装成命令抽离出来,当做一个开关使用
某个 if 中的判断条件比较复杂,不直观时也可抽离封装,如季节月份判断:isSummer()。多次出现的代码片段也可以抽离封装起来。
Ⅱ:分支优化
当需求经常变化,对主逻辑内分支的修改可能会使代码难以维护,而且庞大的分支也影响了业务逻辑的意图表达
① “单一因素” 形成的 if-else 分支
1、 由自己指定单一变量:图一,使用 “策略模式” 抽离成若干策略,如图二。
2、 自主判断(浏览器兼容等)的分支,可用两种方式抽离:
⑴ 懒加载(图三)、预加载(图四);⑵ “策略模式” + “迭代器模式”(仅当需个性化指定尝试顺序时,用 return “success” 终止)
② “多因素” 较复杂判断(if-else 或 多if)
例如:是否预约、是否预付款、是否是vip等导致优惠(结果)不同这样的复杂判断,可以考虑抽离(按结果分,结果1、2、3),使用面向切面编程串成链来
“职责链模式” 先抽离分支(如图一),构造Chain(图二)串起,通过 return “next” 向下进行,否则中断。既可以 if-else式、if式,还可以异步调用下一个。
属于面向切面编程,跟 AOP(after()、before())的不同是 AOP 是逐层封装,过多层影响性能。AOP不能异步调用下一个。
③ 对象有 “若干状态”, 执行相同操作产生不同反应(多处if-else)
例如:云产品有扫描中、正在上传、暂停中、已成功、失败等状态,每种状态下的取消、暂停等按钮反馈不一,可能充斥大量if-else,不易修改。
图一、图二是典型的“状态模式”。主逻辑是添加按钮,这属于 “不变” 。每种状态下点击事件的相应不同,这属于 “变化”。
把变化抽离,每个状态一个单独的对象,让主框架来委托给状态,执行可变的逻辑。
实现的关键在于两者间能够相互引用,相互切换。活用call、apply,或者面向对象式的 this.xxx =xxx
Ⅲ:享元与模板
① “享元模式 ”
有时候,对象太多造成性能灾难,而其中性能耗费严重的属性却相同。(如试穿50件女装只需要1个女模特)
“享元模式”,分离”内部状态”、”外部状态”,每次动作之前需要先找到外部状态对象setState,可写在prototype中
注:主逻辑都在add中,创建对象使用单例代理。(一样的创建对象,一样的执行对象方法,逻辑上与处理前有很好的一致性)
② “模板方法模式”
有时候,创建了很多函数工厂,它们都要求有规定的方法、初始化规则。
“模板方法模式” 使用 init() 定义了类的基本框架,通过 “钩子” 和 throw error 使继承更稳健
上例 “状态模式” 的状态类可以用此种方式优化
3、交互关系:(最小知识原则)
多组件间的耦合关系,有时可以考虑 引入第三方,组件只需要发一个指令,由第三方来通知、改变各组件的状态
通常使用 “中介者模式” (指挥塔,是一种思想)。这非常像 “发布-订阅模式” 的特征
图一, “中介者模式”,若无中介者,每个玩家都要保存一份玩家名单、队友名单,吃药水、死亡等都要遍历名单然后调用其他玩家的接口。
中介者统一保存玩家、状态等,负责调用玩家的接口,玩家只需向其发送信息,使逻辑大大简化。(缺点:中介者可能非常庞大,逻辑复杂)
如果图一,如果增加了动态添加 operations 的方法的API,就是 “发布-订阅模式” 的模型了。
JavaScript设计模式返璞归真相关推荐
- 《JavaScript设计模式》——11.2 一切只因跨域
本节书摘来自异步社区<JavaScript设计模式>一书中的第11章,第11.2节,作者:张容铭著,更多章节内容可以访问云栖社区"异步社区"公众号查看 11.2 一切只 ...
- 《JavaScript设计模式与开发实践》模式篇(12)—— 装饰者模式
在传统的面向对象语言中,给对象添加功能常常使用继承的方式,但是继承的方式并不灵活, 还会带来许多问题:一方面会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之 改变;另一方面,继承这种功能复 ...
- JavaScript设计模式与开发实践——JavaScript的多态
"多态"一词源于希腊文polymorphism,拆开来看是poly(复数)+ morph(形态)+ ism,从字面上我们可以理解为复数形态. 多态的实际含义是:同一操作作用于不同的 ...
- 16种JavaScript设计模式(中)
简介 上文中介绍了学习设计模式前需要了解的一些基础概念和js的基础模式-原型模式,没看过的同学可以点这里,本章将介绍以下几种模式 单例模式 策略模式 代理模式 迭代器模式 发布订阅模式 命令模式 组合 ...
- javascript 设计模式(一)
1.为什么要深入学习Javascript? 用户对页面的美观性,易用性要求越来越高 Javascript+HTMl5+CSS3将是未来客户端技术的潮流 学好JS有助于更好学习JS库.JS框架 开发自己 ...
- JavaScript设计模式--简单工厂模式例子---XHR工厂
JavaScript设计模式--简单工厂模式例子---XHR工厂 第一步,Ajax操作接口(目的是起一个接口检测作用) (1)引入接口文件 //定义一个静态方法来实现接口与实现类的直接检验 //静态方 ...
- JavaScript 设计模式基础(二)
JavaScript 设计模式基础(一) 原型模式 在以类为中心的面向对象编程语言中,类和对象的关系就像铸模和铸件的关系,对象总是从类中创建.而原型编程中,类不是必须的,对象未必从类中创建而来,可以拷 ...
- 《JavaScript设计模式与开发实践》阅读摘要
<JavaScript设计模式与开发实践>作者:曾探 系统的介绍了各种模式,以及js中的实现.应用,以及超大量高质量代码,绝对值得一读 面向对象的js 静态类型:编译时便已确定变量的类型 ...
- JS代理模式《JavaScript设计模式与开发实践》阅读笔记
代理模式 代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问. 保护代理和虚拟代理 保护代理:当有许多需求要向某对象发出一些请求时,可以设置保护代理,通过一些条件判断对请求进行过滤. 虚拟 ...
最新文章
- WPF 使用DrawingVisual绘制高性能曲线图
- 揭秘阿里秒级百万TPS平台架构实现
- 人类遗传变异神库 | ClinVar数据库详解
- 中科院遗传所钱文峰组发表新冠病毒源于自然界的科学证据
- php实现数值的整数次方
- android图片异步加载解决步骤
- 《天天数学》连载38:二月七日
- v380怎么设置云存储_计算机网络云计算技术在应用中的不足及缺陷
- 户口所在地代码查询_毕业生如何查询档案存放地及存档问题?
- CSDN 日报第 1 期:蚂蚁集团将向机构投资者退款;Pyston v2 发布 比 Python 快 20%
- [日志]08/19/2007
- tomcat7源代码Bootstrap
- java手机翻译,使用JUniversal翻译Android项目
- threejs写的模仿微信跳一跳游戏
- MySQL-查询本周过生日的人-终极答案
- Kubernetes 学习笔记(一)--- 基本概念及利用kubeadm部署K8S
- SAP 打开总账科目
- IT项目经理沟通技巧的重要性
- 系统安全启动总结思考
- program received signal sigsegv,Segmentation fault的解释(可能并不全面)
热门文章
- 第一届LCI workshop @ ICCV 2019, 欢迎投稿
- 新年快乐!我爱计算机视觉干货集锦与新年展望
- 谷歌发布AdaNet,快速灵活的AutoML工具,帮助开发者构筑强大集成学习模型
- PyTorch | torch.from_numpy使用方法 | torch.from_numpy如何使用?torch.from_numpy()例子 | 通过torch.from_numpy创建张量
- 一切为了开放科学!Papers with Code 新增CS、物理、数学、统计学等多个学科
- 经验 | 计算机视觉顶会上的灌水文都有哪些特征?
- 【资源下载】512页IBM沃森研究员Charu最新2018著作《神经网络与深度学习》(附下载链接)
- 如何在电脑上制作请假条表格_条码标签打印软件如何制作请假表
- sql语句中使用函数会耗费性能吗_挽救数据库性能的 30 条黄金法则 | 原力计划...
- Java正则获取a标签href_正则表达式,同时获取a标签里的href,text 的值