JavaScript设计模式笔记:3个设计原则、14个设计模式、9个技巧(干货)

介绍

软件开发的基础理论对于非科班出身的我来讲一直是个弱项,前一段时间立了个flag,把 《JavaScript设计模式与开发实践》 这本书里的设计模式和设计原则整理成简单易懂的便签😂javascript

一是本身加深理解,倒逼输出,二来能够请你们帮忙纠错,防止本身理解误差;java

喜欢刷沸点小伙伴们可能有看见过最近发的设计模式便签🔖, 因为天天迟早看娃,不多有整块的时间,只能一天抽几分钟整理一两个便签,还好这个flag算是勉勉强强的完成了,收获确实不少,下面就是整理的笔记,若有错误,恳请拍砖,em....,用力拍👋👋👋git

可能不少优秀的模式模式是潜移默化在你的代码和实现思路里,只不过叫不上名字,若是粗略的理解一下设计模式,至少和别人讨论实现思路时能够喷出几个名词(开玩笑)😴,毕竟做为一个编程人员这些知识仍是很很很重要的程序员

3个设计原则

单一职责原则(SRP)

就一个类而言,应仅有一个引发它变化的缘由。github

单一职责原则(SRP)的职责被定义为“引发变化的缘由”。 若是咱们有两个动机去改写一个方法,那么这个方法就具备两个职责。每一个职责都是变化的一个轴线,若是一个方法承担了过多的职责,那么在需求的变迁过程当中,须要改写这个方法的可能性就越大。ajax

此时,这个方法一般是一个不稳定的方法,修改代码老是一件危险的事情,特别是当两个职责耦合在一块儿的时候,一个职责发生变化可能会影响到其余职责的实现,形成意想不到的破坏,这种耦合性获得的是低内聚和脆弱的设计。算法

SRP原则体现为:一个对象/方法,只作一件事情。编程

SRP原则的应用难点是如何分离职责

SRP原则是全部原则中最简单也是最难正确运用的原则之一。 要明确的是,并非全部的职责都应该一一分离。设计模式

  • 一方面,若是随着需求的变化,有两个职责老是同时变化,那就没必要分离他们。好比在ajax请求的时候,建立xhr对象和发送xhr请求几乎老是在一块儿的,那么建立xhr对象的职责和发送xhr请求的职责就没有必要分开。
  • 另外一方面,职责的变化轴线仅当它们肯定会发生变化时才具备意义,即便两个职责已经被耦合在一块儿,但它们尚未发生改变的征兆,那么也许没有必要主动分离它们,在代码须要重构的时候再进行分离也不迟。

SRP原则的优缺点 SRP原则的优势是下降了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码的复用,也有利于进行单元测试。当一个职责须要变动的时候,不会影响到其余的职责。缓存

最明显的缺点是会增长编写代码的复杂度。当咱们按照职责把对象分解成更小的粒度以后,实际上也增大了这些对象之间相互联系的难度。

最少知识原则(LKP)

最少知识原则(LKP)说的是一个软件实体应当尽量少地与其余实体发生相互做用。这里的软件实体是一个广义的概念,不只包括对象,还包括系统、类、模块、函数、变量等。

减小对象之间的联系

最少知识原则要求咱们在设计程序时,应当尽可能减小对象之间的交互。若是两个对象之间没必要彼此直接通讯,那么这两个对象就不要发生直接的相互联系。

迪米特法则(Law of Demeter,LoD)

最少知识原则也叫迪米特法则(Law of Demeter,LoD),“迪米特”这个名字源自1987年美国东北大学一个名为“Demeter”的研究项目。 在实际开发中,是否选择让代码符合最少知识原则,要根据具体的环境来定。

开放-封闭原则

开放-封闭原则最先由Eiffel语言的设计者Bertrand Meyer在其著做Object-Oriented Software Construction 中提出。它的定义以下:

软件实体(类、模块、函数)等应该是能够扩展的,可是不可修改。

开放-封闭原则的思想:

当须要改变一个程序的功能或者给这个程序增长新功能的时候,可使用增长代码的方式,可是不容许改动程序的源代码。

实践方法

  • 用对象的多态性消除条件分支
  • 找出变化的地方 找出程序中将要发生变化的地方,把变化封装起来,稳定不变的部分和容易变化的部分隔离开来。在系统的演变过程当中,咱们只须要替换那些容易变化的部分,变化的部分使用以下方法。
    1. 放置挂钩
    2. 使用回调函数

开放-封闭原则与设计模式

无论是具体的各类设计模式,仍是更抽象的面向对象设计原则,好比单一职责原则、最少知识原则、依赖倒置原则等,都是为了让程序遵照开放-封闭原则而出现的

堆砌设计模式与过分设计

让程序保持彻底封闭是不容易作到的。就算技术上作获得,也须要花费太多的时间和精力。 下面这段话引自Bob大叔的《敏捷软件开发原则、模式与实践》:

有句古老的谚语说:“愚弄我一次,应该羞愧的是你。再次愚弄我,应该羞愧的是我。”这也是一种有效的对待软件设计的态度。为了防止软件背着没必要要的复杂性,咱们会容许本身被愚弄一次。

这有点像星矢说的:“圣斗士不会被一样的招数击倒第二次。”

14个设计模式

单例模式

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

单例模式是一种经常使用的模式,有一些对象咱们每每只须要一个,好比线程池、全局缓存、浏 览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途一样很是普遍。试想一下,当我 们单击登陆按钮的时候,页面中会出现一个登陆浮窗,而这个登陆浮窗是惟一的,不管单击多少 次登陆按钮,这个浮窗都只会被建立一次,那么这个登陆浮窗就适合用单例模式来建立。

策略模式

定义一系列的算法,把它们一个个封装起来,而且使它们能够相互替换。

一个基于策略模式的程序至少由两部分组成。

  • 第一个部分是一组策略类,策略类封装了具体 的算法,并负责具体的计算过程。
  • 第二个部分是环境类 Context,Context 接受客户的请求,随后 把请求委托给某一个策略类。

代理模式

代理模式是为本体对象提供一个替身,以便控制对本体的访问。

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

能够帮助对象过滤掉一些不知足特定条件的请求,把一些开销很大的请求,延迟到真正须要它的时候才去执行等等。

迭代器模式

指提供一种方法顺序访问一个聚合对象中的各个元素而又不须要暴露该对象的内部表示。

迭代器模式能够把迭代的过程从业务逻辑中分离出来,在使用迭代器模式以后,即便不关心对象的内部构造,也能够按顺序访问其中的每一个元素,如 jQuery 中的$.each 函数。

内部与外部迭代

内部迭代即调用一次循环全部元素,外部迭代须要手动触发下一个元素的迭代,如图:

可迭代特性

不管是内部迭代器仍是外部迭代器,只要被迭代的聚合对象拥有 length 属性并且能够用下标访问,那它就能够被迭代。

迭代顺序

迭代器模式提供了循环访问一个聚合对象中每一个元素的方法,但它没有规定咱们以顺序、倒序仍是中序来循环遍历聚合对象。

终止迭代器

迭代器能够像普通 for 循环中的 break 同样,提供一种跳出循环的方法。

小结

迭代器模式是一种相对简单的模式,简单到不少时候咱们都不认为它是一种设计模式。目前的绝大部分语言都内置了迭代器。

发布—订阅模式

发布—订阅模式和观察者模式相似,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,全部依赖于它的对象都将获得通知。

发布—订阅模式能够普遍应用于异步编程中,这是一种替代传递回调函数的方案。

做用

取代对象之间硬编码的通知机制,一个对象不用再显式地调用另一个对象的某个接口。

让两个对象松耦合地联系在一块儿,能够在不太清楚彼此的细节的状况下相互通讯。

当有新的订阅者出现时,发布者的代码不须要任何修改;一样发布者须要改变时,也不会影响到以前的订阅者。只要约定的事件名没有变化,就能够自由地改变它们。

订阅实现的关键点

  • 发布者
  • 订阅列表
  • 订阅方法
  • 发布方法
  • 取消订阅方法

优缺点

发布—订阅模式的优势很是明显,时间、对象之间的解耦,从架构上来看,不管是 MVC 仍是 MVVM, 都少不了发布—订阅模式的参与。

建立订阅者自己要消耗必定的时间和内存,订阅一个消息后,也许此消息最后都未发生,但订阅者始终存在内存中。另外,发布—订阅模式会弱化对象之间的联系,过分使用后,对象和对象之间的必要联系也将被深埋在背后,致使程序难以跟踪维护和理解。

命令模式

命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令。

命令模式的由来实际上是回调(callback)函数的一个面向对象的替代品,跟许多其余语言不一样,JavaScript 能够用高阶函数方便地实现命令模式。

封装命令类

封装在普通函数

JavaScript 做为将函数做为一等对象的语言,跟策略模式同样,命令模式也早已融入到了 JavaScript 语言之中。运算块不必定要封装在命令类中,也能够封装在普通函数中。

撤销命令: 某个命令须要运行较长时间,能够增长撤销操做。

命令队列: 咱们把命令存入一个队列,能够很简单的实现如“回放”、“后退”的功能。

宏命令 : 一组命令的集合,一次执行一组命令。

命令模式在 JavaScript 语言中是一种隐形的模式。

组合模式

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

组合模式将对象组合成树形结构,以表示“部分-总体”的层次结构。 除了用来表示树形结构以外,组合模式的另外一个好处是经过对象的多态性表现,使得用户对单个对象和组合对象的使用具备一致性。

优势

提供了一种遍历树形结构的方案,组合模式能够很是方便地描述对象的层次结构。

统一地使用组合结构中的全部对象,不须要关心它到底是组合对象仍是单个对象。

组合模式不是父子关系

有时候把上下级对象称为父子节点,但你们要知道,它们并不是真正意义上的父子关系。

必要条件

只有用一致的方式对待列表中的每一个叶对象的时候,才适合使用组合模式。

小结

咱们能够把相同的操做应用在组合对象和单个对象上。大多数状况下,咱们均可以忽略掉组合对象和单个对象之间的差异,从而用一致的方式来处理它们。

模板方法模式

严重依赖抽象类,使用继承和重写父类的某些方法来实现功能的设计模式。

两部分组成

  • 抽象父类:封装了子类的算法框架、公共方法、以及子类中全部方法的执行顺序。
  • 子类:继承抽象类,按照整个算法结构选择重写父类的某些方法。

JavaScript 没有从语法层面提供对抽象类怎么办?

  • 接口检查,确保子类重写了父类的方法。
  • 未重写方法直接抛出异常。

小结

模板方法模式是一种典型的经过封装变化提升系统扩展性的设计模式。子类的方法种类和执行顺序在抽象类中定义且不可变,新功能经过增长子类且不须要改动抽象父类及其余子类便可实现,这也符合开放-封闭原则。

享元模式

享元模式是一种用于性能优化的模式,核心是运用共享技术来有效支持大量细粒度的对象。

享元模式要求将对象的属性划分为内部状态与外部状态,目标是尽可能减小共享对象的数量。

内部状态存储于对象内部

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

享元模式适用的场景

  • 对象的大多数状态均可以变为外部状态。
  • 一个程序中使用了大量的类似对象。
  • 因为使用了大量对象,形成很大的内存开销。
  • 剥离出对象的外部状态以后,能够用相对较少的共享对象取代大量对象。

对象池

对象池维护一个装载空闲对象的池子,须要对象的时候,不是直接 new,而是转从对象池里获取。若是对象池里没有空闲对象,则建立一个新对象,当获取出的对象完成它的职责以后, 再进入池子等待被下次获取。

小结

享元模式是为解决性能问题而生的模式,大部分模式的诞生缘由都不同。在一个存在大量类似对象的系统中,享元模式能够很好地解决大量对象带来的性能问题。

职责链模式

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

传统实现的缺点

传统实现就像一根环环相扣打了死结的链条,若是要增长、拆除或者移动一个节点,就必须得先砸烂这根链条。

职责链模式优势

职责链模式的最大优势就是解耦了请求发送者和 N 个接收者之间的复杂关系, 请求发送者只须要知道链中的第一个节点,弱化了发送者和一组接收者之间的强联系。

职责链模式缺点

不能保证某个请求必定会被链中的节点处理,大部分节点没有起到实质性的做用,仅是让请求传递下去。 从性能方面考虑,咱们要避免过长的职责链带来的性能损耗。

小结

在 JavaScript 开发中,职责链模式是最容易被忽视的模式之一。只要运用得当,能够下降发起请求的对象和处理请求对象之间的耦合性,能够自由变化职责链中的节点数量和顺序

中介者模式

用来下降多个对象和类之间的通讯复杂性,它提供了一个中介类,该类处理不一样类之间的通讯,并支持松耦合,使代码易于维护。

面向对象设计鼓励将行为分布到各个对象中,把对象划分红更小的粒度,有助于加强对象的可复用性,但因为这些细粒度对象之间的联系激增,又有可能会反过来下降它们的可复用性。

中介者模式的做用

中介者模式的做用就是解除对象与对象之间的紧耦合关系

增长一个中介者对象后,全部的相关对象都经过中介者对象来通讯,而不是互相引用,因此当一个对象发生改变时,只须要通知中介者对象便可。

中介者使各对象之间耦合松散,并且能够独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。

中介者模式的缺点是系统中会新增一个中介者对象,由于对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象常常是巨大的。中介者对象自身每每就是一个难以维护的对象。

装饰者模式

在不改变对象自身的基础上,程序运行期间动态给对象添加职责的方式称为装饰者模式。

在程序开发中并不但愿某个类天生就很是庞大,一次性包含许多职责; 装饰者模式能够动态地给某个对象添加一些额外的职责,而不会影响这个类派生的其余对象。

装饰者也是包装器

在《设计模式》成书以前,GoF原想把装饰者(decorator)模式称为包装器(wrapper)模式。

从功能上而言,decorator能很好地描述这个模式,但从结构上看,wrapper的说法更加贴切。装饰者模式将一个对象嵌入另外一个对象之中,实际上至关于这个对象被另外一个对象包装起来,造成一条包装链。请求随着这条链依次传递到全部的对象,每一个对象都有处理这条请求的机会

装饰者模式和代理模式的区别

代理模式和装饰者模式最重要的区别在于它们的意图和设计目的

两种模式都描述了怎样为对象提供必定程度上的间接引用,它们的实现部分都保留了对另一个对象的引用,而且向那个对象发送请求。

代理模式的目的是,当直接访问本体不方便或者不符合须要时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供访问本体以前作一些额外的事情,或拒绝对它的访问, 代理模式强调一种关系(Proxy与它的实体之间的关系),这种关系能够静态的表达,一开始就能够被肯定。

装饰者模式的做用就是为对象动态加入行为,而装饰者模式用于一开始不能肯定对象的所有功能时使用。代理模式一般只有一层代理,而装饰者模式常常会造成一条长长的装饰链。

状态模式

容许一个对象在内部状态改变时改变它的行为。

状态模式的关键是区分事物的内部状态,事物状态的改变会带来事物行为的改变,每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部,当请求对象的某个行为时,把这个请求委托给当前的状态对象的行为便可。

优势

  • 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。经过增长新的状态类,很容易增长新的状态和转换。
  • 用对象代替字符串来记录当前状态,使得状态的切换更加一目了然。 Context中的请求动做和状态类中封装的行为能够很是容易地独立变化而互不影响。
  • 状态模式的缺点是会在系统中定义许多状态类

策略模式与状态模式

  • 都有一个上下文、一些策略或者状态类,上下文把请求委托给这些类来执行。
  • 使用策略模式时,客户必须熟知这些策略类的做用,以即可以随时主动切换算法;
  • 状态模式中,状态和状态对应的行为是早已被封装好的,状态之间的切换也早被规定完成,“改变行为”这件事情发生在状态模式内部。

javascript-state-machine 有限状态机

github.com/jakesgordon…

适配器模式

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

当咱们试图调用模块或者对象的某个接口时,发现这个接口的格式并不符合目前的需求时, 建立一个适配器,将原接口转换为客户但愿的另外一个接口,客户只须要和适配器打交道。 使用适配器模式以后,本来因为接口不兼容而不能工做的两个软件实体能够一块儿工做。

适配器模式是一种“亡羊补牢”的模式,没有人会在程序的设计之初就使用它。

小结

适配器模式是一对相对简单的模式。有一些模式跟适配器模式的结构很是类似,好比装饰者模式、代理模式和外观模式,这几种模式都属于“包装模式”,都是由一个对象来包装另外一个对象。区别它们的关键仍然是模式的意图。

代码重构技巧

原文是这么说的:

从某种角度来看,设计模式的目的就是为许多重构行为提供目标。

提炼函数

若是在函数中有一段代码能够被独立出来,最好把这些代码放进另一个独立的函数中。

  • 避免出现超大函数。
  • 独立出来的函数有助于代码复用。
  • 独立出来的函数更容易被覆写。
  • 独立出来的函数如拥有良好的命名,自己就起到了注释做用。

合并重复的条件片断

若是一个函数体内有一些条件分支语句,而这些条件分支语句内部散布了一些重复的代码,那么就有必要进行合并去重工做。

把条件分支语句提炼成函数

在程序设计中,复杂的条件分支语句是致使程序难以阅读和理解的重要缘由,并且容易致使一个庞大的函数。

合理使用循环

在函数体内,若是有些代码实际上负责的是一些重复性的工做,那么合理利用循环不只能够完成一样的功能,还可使代码量更少。

提早让函数退出代替嵌套条件分支

用《重构》里的话说:

嵌套的条件分支每每是由一些深信“每一个函数只能有一个出口的”程序员写出的。但实际上,若是对函数的剩余部分不感兴趣,那就应该当即退出。引导阅读者去看一些没有用的else片断,只会妨碍他们对程序的理解。

传递对象参数代替过长的参数列表

一个函数接收的参数数量越多,函数就越难理解和使用。在使用的时候,还要防止少传了某个参数或者把两个参数搞反了位置。 使用对象就能够不用再关心参数的数量和顺序,只要保证参数对应的key值不变就能够了。

尽可能减小参数数量

若是一个函数不须要传入任何参数就可使用,这种函数是深受人们喜好的。在实际开发中,向函数传递参数不可避免,但咱们应该尽可能减小函数接收的参数数量。

少用三目运算符

使用三目运算符和使用if、else代码循环一百万次,时间开销仍处在同一个级别里。 若是条件分支逻辑简单清晰可用三目运算符,如逻辑复杂建议仍是使用if、else。

合理使用链式调用 链式调用带来的坏处是调试很是不方便,若是链条很容易发生变化,建议使用普通调用的形式。

用return退出多重循环

假设在函数体内有一个两重循环语句,使用控制标记变量或者设置循环标记这两种作法无疑都让人头晕目眩。 用return直接退出方法会带来一个问题,未来不能执行循环以后的代码,能够return函数。

结束

你要是能看到这里,真的是太厉害了,这是一篇罗列知识点的总结,自己就很枯燥,我在整理汇总的时候已经快失去耐心了🤧,能看完的必定是很厉害的大牛,也想请你们推荐几本关于软件设计的理论方面的书,感谢亲哒哒哒~~~,哦,对了🤗,撩骚一下,有收获的话就给个当心心吧😍😍😘。

相关文章
相关标签/搜索