21种JavaScript设计模式最新记录(含图和示例)

  最近观看了《Javascript设计模式系统讲解与应用》教程,对设计模式有了新的认识,特在此做些记录。

一、UML

  文中会涉及众多的UML类图,在开篇需要做点基础概念的认识。以下面的图为例,图片和说明均来源于《大话设计模式》一书。

  (1)矩形框,它代表一个类。类图分三层,第一层显示类的名称,如果是抽象类,则用斜体显示。第二层是类的特性,通常就是字段和属性。第三层是类的操作,通常是方法或行为。前面的符号,+ 表示public,- 表示private,# 表示protected。

  (2)矩形框的顶端包含<<interface>>就表示一个接口。第一行是接口名称,第二行是接口方法。

  (3)继承关系用空心三角形 + 实线来表示的。

  (4)实现接口用空心三角形 + 虚线来表示。

  (5)当一个类知道另一个类时,可以用关联(Association),关联关系用实线箭头来表示。

  (6)聚合(Aggregation)表示一种弱的拥有关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。聚合关系用空心的菱形 + 实线箭头来表示。

  (7)组合(Composition)是一种强的拥有关系,体现了严格的部分和整体的关系,组合关系用实心的菱形 + 实线箭头来表示。

  (8)依赖(Dependency)关系,用虚线箭头来表示。

二、五大原则(SOLID)

  作者认为设计模式得分成两部分,第一是设计(即设计原则),第二才是模式。五大原则是设计模式的基础,SOLID是五大原则的首字母简写,作者在介绍每个模式的时候,都会提到是否符合这五大原则。

  (1)S-单一职责原则,一个程序只做一件事,如果功能过于复杂,那么就拆分,保持独立。

  (2)O-开放封闭原则,对扩展开发,对修改封闭,增加需求就扩展新代码,而非修改已有代码。修改已有代码不但需要测试,成本高昂,而且多人开发很容易发生冲突。

  (3)L-里氏替换原则,子类能覆盖父类,父类能出现的地方子类就能出现。

  (4)I-接口分离原则,保持接口的单一独立,避免胖接口。

  (5)D-依赖倒置原则,面向接口编程,依赖于抽象而不依赖于具体,使用方只关注接口而不关注具体类的实现。

  在JavaScript中,SO体现较多;而LID体现较少,因为JavaScript的语法限制,例如弱类型、没有接口等。如果用Promise来说明SO,那么就是:

  (1)S:每个then中的逻辑只做好一件事。

  (2)O:如果新增需求,那么就扩展then。

  说句题外话,TypeScript是一门强类型面向对象语言,在它问世后,就可以完完整整的应用这五大原则,不会有什么限制。前段时间写过几篇TypeScript的简单教程,有兴趣的可以参考

三、21种设计模式

1)工厂模式

  将new操作单独封装。遇到new时,就要考虑是否该使用工厂模式。例如购买汉堡直接点餐,不用自己制作,而商店要封装做汉堡的操作,然后卖给客户。下面是一个简单示例。

class Creator {
  create(name) {
    return new Product(name);
  }
}
let creator = new Creator();
let p = creator.create("p");

  使用场景包括jQuery的$("div")、React.createElement()和Vue的异步组件。

2)单例模式

  系统中被唯一使用,一个类只有一个实例,例如登录框、购物车。单例模式需要用到private修饰符,但ES6中没有,可用闭包模拟,如下所示。

class SingleObject {
  login() {
    console.log("login...");
  }
}
SingleObject.getInstance = (function() {
  let instance;
  return function() {
    if (!instance) {
      instance = new SingleObject();
    }
    return instance;
  };
})();
let obj1 = SingleObject.getInstance();
obj1.login();

  使用场景包括jQuery中的$(只有一个)、模拟登录框和Redux中的Store。

3)适配器模式

  旧接口格式和使用者不兼容,中间加一个适配转换接口。以转换插头为例,如下所示。

class Adaptee {
  specificRequest() {
    return "德国标准";
  }
}
class Target {
  constructor() {
    this.adaptee = new Adaptee();
  }
  request() {
    let info = this.adaptee.specificRequest();
    return `${info}--转换--中国标准`;
  }
}
let target = new Target();
target.request();

  使用场景包括封装旧接口、Vue的计算属性。

4)装饰器模式

  为对象添加新功能,不改变其原有的结构和功能。与适配器模式不同,之前的接口仍然可以使用。装饰器模式类似于生活中的手机壳,不仅保护了手机,还不影响音量键、扬声器等部分。

  使用场景包括ES7装饰器core-decorator,下面通过装饰器语法演示调用方法时自动打日志。

function log(target, name, descriptor) {
  var oldValue = descriptor.value;
  descriptor.value = function() {
    console.log(`Calling ${name} with`, arguments);
    return oldValue.apply(this, arguments);
  };
  return descriptor;
}
class Math {
  @log
  add(a, b) {
    return a + b;
  }
}
const math = new Math();
math.add(2, 4);

  装饰器的原理如下所示。

@decorator
class A {}
//等同于
class A {}
A = decorator(A) || A;

5)代理模式

  当使用者无权访问目标对象时,可在中间加代理,通过代理做授权和控制,类似于明星的经纪人。

  使用场景包括DOM的事件委托、jQuery的$.proxy、ES6的proxy。下面通过代理语法模拟明星和经纪人。

let star = {
  name: "张XX",                    // 明星
  age: 25,
  phone: "13800138000"
};
let agent = new Proxy(star, {     // 经纪人
  get: function(target, key) {
    if (key === "phone") {        // 返回经纪人自己的手机号
      return "18012345678";
    }
    if (key === "price") {        // 明星不报价,经纪人报价
      return 120000;
    }
    return target[key];
  },
  set: function(target, key, val) {
    if (key === "customPrice") {
      if (val < 100000) {
        throw new Error("价格太低");
      } else {
        target[key] = val;
        return true;
      }
    }
  }
});

  代理模式提供一模一样的接口,而适配器模式提供不同的接口。代理模式直接针对原有功能,不过需要经过限制或阉割;装饰器模式能扩展功能,而原有功能不变且可直接使用。

6)外观模式

  为子系统中的一组接口提供一个高层接口,使用者使用这个高层接口。下面是外观模式的示意图,去医院看病通常是左边的情况,但如果有接待员,那么就会是右边的情况,由他去挂号、门诊、取药等。

  外观模式的UML类图是下面这样。

  使用场景包括同一个函数可接收不同组合的参数,如下所示,在jQuery中提供了很多这类方法。

function bindEvent(elem, type, selector, fn) {
  if (fn == null) {
    fn = selector;
    selector = null;
  }
  //...
}
//调用可以有两种
bindEvent(elem, "click", "#div", fn);
bindEvent(elem, "click", fn);

  注意,外观模式虽然好用,但是不符合单一职责和开放封闭原则,需要谨慎使用。

7)观察者模式

  观察者模式是一种发布订阅机制,当对象间存在一对多(也可以一对一)的关系时,可使用观察者模式。在使用观察者模式后,当一个对象被修改时,就会自动通知它的依赖对象。

  生活中的点咖啡也是一种观察者模式,当点好后,就找位子坐下,等叫号或者等服务员送过来。下面是个简单的示例。

class Subject {                      // 主题,接收状态变化,触发每个观察者
  constructor() {
    this.state = 0;
    this.observers = [];
  }
  getState() {                       //获取状态
    return this.state;
  }
  setState(state) {                  //修改状态
    this.state = state;
    this.notifyAllObservers();
  }
  attach(observer) {                 //添加观察者
    this.observers.push(observer);
  }
  notifyAllObservers() {             //通知所有观察者
    this.observers.forEach(observer => {
      observer.update();
    });
  }
}
class Observer {                     // 观察者,等待被触发
  constructor(name, subject) {
    this.name = name;
    this.subject = subject;
    this.subject.attach(this);
  }
  update() {
    console.log(`${this.name} update, state: ${this.subject.getState()}`);
  }
}

let s = new Subject();
let o1 = new Observer("o1", s);
let o2 = new Observer("o2", s);
s.setState(1);
s.setState(2);

  使用场景包括DOM事件绑定、Promise、jQuery callbacks和Node.js自定义事件。

8)迭代器模式

  访问一个有序集合(不包括对象),使用者无需知道集合的内部结构。下面是一个模拟的迭代器(Iterator),包含next()和hasNext()方法。

class Iterator {
  constructor(conatiner) {
    this.list = conatiner.list;
    this.index = 0;
  }
  next() {
    if (this.hasNext()) {
      return this.list[this.index++];
    }
    return null;
  }
  hasNext() {
    if (this.index >= this.list.length) {
      return false;
    }
    return true;
  }
}
class Container {
  constructor(list) {
    this.list = list;
  }
  getIterator() {
    return new Iterator(this);
  }
}

let container = new Container([1, 2, 3, 4, 5]);
let iterator = container.getIterator();
while (iterator.hasNext()) {
  console.log(iterator.next());
}

  使用场景包括jQuery的each、ES6的Iterator。ES6之所以引入Iterator有以下三个原因:

  (1)实现了迭代器模式。

  (2)ES6语法中,有序集合的数据类型已经有很多,例如Array、Map、Set、String、TypedArray、arguments和NodeList。

  (3)需要有一个统一的接口来遍历所有数据类型。

  注意,Object不是有序集合,可用Map替代。

9)状态模式

  一个对象会有状态变化,每次状态变化都会触发一个逻辑,而这个逻辑不能总是用if...else语句来控制。生活中的交通信号灯是一种状态模式,当颜色变化时,会有不同的结果,例如红灯禁止、绿灯通行。

  使用场景包括Promise原理、有限状态机。下面的示例使用了开源的javascript-state-machine

var fsm = new StateMachine({
  init: "solid",
  transitions: [
    { name: "melt", from: "solid", to: "liquid" },
    { name: "freeze", from: "liquid", to: "solid" },
    { name: "vaporize", from: "liquid", to: "gas" },
    { name: "condense", from: "gas", to: "liquid" }
  ],
  methods: {
    onMelt: function() { console.log("I melted"); },
    onFreeze: function() { console.log("I froze"); },
    onVaporize: function() { console.log("I vaporized"); },
    onCondense: function() { console.log("I condensed"); }
  }
});

10)原型模式

  克隆自己,生成一个新对象。Java默认有clone接口,不用自定义。而JavaScript中的Object.create()采用了原型模式的思想,如下所示。

const person = {
  isHuman: false,
  printIntroduction: function () {
    console.log(`My name is ${this.name}`);
  }
};
const me = Object.create(person);
me.name = "Matthew";
me.isHuman = true;

11)桥接模式

  将抽象化与实现化两者解耦,使得它们可以独立变化。

  下图和分析均来源于《》,在用桥接模式优化后,就能将形状和颜色通过继承生产的强耦合关系改成弱耦合的关联关系,这里采用了组合大于继承的思想。

  如果想添加一个五角星,只需要添加一个形状类的子类五角星接即可,不需要再去添加各种颜色的具体五角星了。如果想要一个蓝色五角星就将五角星和蓝色进行组合来获取。这样设计降低了形状和颜色的耦合,减少了具体子类的种类。

12)组合模式

  生成树形结构,表示“整体-部分”关系。让整体和部分都具有一致的操作方式。虚拟DOM中的vnode是这种形式(如下所示),但数据类型比较简单。

{
  tag: "div",
  attr: {
    className: "container"
  },
  children: [
    {
      tag: "p",
      attr: {},
      children: ["123"]
    },
    {
      tag: "p",
      attr: {},
      children: ["456"]
    }
  ]
}

13)享元模式

  享元是个组合词,分为共享和元数据。享元模式关注共享内存,考虑内存而非效率。

  在JavaScript中不用刻意的考虑内存问题,因此没有享元模式的直接场景,但可以找到意义相关(即共享数据开销思想)的场景,例如事件委托。

14)策略模式

  不同策略分开处理,避免出现大量if...else或者switch语句,下面是个示例。

class User {
  constructor(type) {
    this.type = type;
  }
  buy() {
    if (this.type == "ordinary") {
      console.log("普通用户购买");
    } else if (this.type == "member") {
      console.log("会员用户购买");
    } else if (this.type == "vip") {
      console.log("VIP用户购买");
    }
  }
}
//策略模式生成三个类,各自实现buy()方法
class UserOrdinary {
  buy() {
    console.log("普通用户购买");
  }
}
class UserMember {
  buy() {
    console.log("会员用户购买");
  }
}
class UserVip {
  buy() {
    console.log("VIP用户购买");
  }
}

15)模板方法模式

  如果代码中有几步处理,可以用一个方法将它们封装在一个方法中(如下所示),在方法中可对顺序或特殊逻辑进行处理,对外统一输出,有效降低了耦合度。

class Action {
  handle() {
    handle1();
    handle2();
    handle3();
  }
  handle1() { }
  handle2() { }
  handle3() { }
}

16)职责链模式

  一步操作可能分为多个职责角色来完成,然后把这些角色都分开,再用一个链串起来,并且将发起者和各个处理者进行隔离。例如请假审批,需要逐级审批,如下所示。

class Action {
  constructor(name) {
    this.name = name;
    this.nextAction = null;
  }
  setNextAction(action) {
    this.nextAction = action;
  }
  handle() {
    console.log(`${this.name} 审批`);
    if (this.nextAction != null) {
      this.nextAction.handle();
    }
  }
}
let a1 = new Action("组长");
let a2 = new Action("经理");
let a3 = new Action("总监");
a1.setNextAction(a2);
a2.setNextAction(a3);
a1.handle();

  职责链模式和业务结合较多,能联想到链式操作,例如jQuery的链式操作、Promise.then的链式操作。

17)命令模式

  执行命令时,发布者和执行者分开。中间加入命令对象,作为中转站。发布者相当于将军,他会让旗手或号手将命令传达给普通士兵,如下所示。

class Receiver {
  exec() {
    console.log("执行");
  }
}
class Command {
  constructor(receiver) {
    this.receiver = receiver;
  }
  cmd() {
    console.log("触发命令");
    this.receiver.exec();
  }
}
class Invoker {
  constructor(command) {
    this.command = command;
  }
  invoke() {
    console.log("开始");
    this.command.cmd();
  }
}
let solider = new Receiver();             //士兵
let trumpeter = new Command(solider);     //小号手
let general = new Invoker(trumpeter);     //将军
general.invoke();

  使用场景包括网页富文本编辑器,由浏览器封装一个命令对象,如下所示。

document.execCommand("bold");
document.execCommand("undo");

18)备忘录模式

  随时记录一个对象的状态变化,随时可以恢复之前的某个状态,例如网页编辑器中的撤销功能。

19)中介者模式

  当有许多对象,并且对象之间会频繁的相互访问(如左图)时,就会变得很混乱,而对象之间的访问都由中介者代转(如右图),就会变得很有条理,不会出现牵一发动全身的情况。

  生活中的房屋中介就采用了这种模式,如下所示。

class Mediator {        //中介者
  constructor(a, b) {
    this.a = a;
    this.b = b;
  }
  setA() {
    let number = this.b.number;
    this.a.setNumber(number * 100);
  }
  setB() {
    let number = this.a.number;
    this.b.setNumber(number / 100);
  }
}
class A {
  constructor() {
    this.number = 0;
  }
  setNumber(num, m) {
    this.number = num;
    if (m) {
      m.setB();         //通过中介者修改
    }
  }
}
class B {
  constructor() {
    this.number = 0;
  }
  setNumber(num, m) {
    this.number = num;
    if (m) {
      m.setA();         //通过中介者修改
    }
  }
}

20)访问者模式

  将数据操作和数据结构进行分离。

21)解释器模式

  描述语言语法如何定义,如何解释和编译,例如Babel。

原文地址:https://www.cnblogs.com/strick/p/12107950.html

时间: 2024-08-11 01:35:41

21种JavaScript设计模式最新记录(含图和示例)的相关文章

23种JavaScript设计模式

原文链接:https://boostlog.io/@sonuton/23-javascript-design-patterns-5adb006847018500491f3f7f 转自: https://mp.weixin.qq.com/s?__biz=MzUxMzcxMzE5Ng==&mid=2247488996&idx=1&sn=c337e4cfe2a67550f5f22536d58a9735&chksm=f951a0a7ce2629b13bb306f9a2410e2be

23种常用设计模式的UML类图

本文UML类图参考<Head First 设计模式>(源码)与<设计模式:可复用面向对象软件的基础>(源码)两书中介绍的设计模式与UML图. 整理常用设计模式的类图,一方面是为了练习UML,另一方面可以重新思考设计模式.当然,整理完成后可以作为一份手册供今后翻阅. 绘图工具:Visual Studio 2015 一.创建型 Factory Method(工厂方法) 定义:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个.工厂方法让类把实例化推迟到子类. 类图: Abstr

15分钟带你了解前端工程师必知的javascript设计模式(附详细思维导图和源码)

15分钟带你了解前端工程师必知的javascript设计模式(附详细思维导图和源码) 前言 设计模式是一个程序员进阶高级的必备技巧,也是评判一个工程师工作经验和能力的试金石.设计模式是程序员多年工作经验的凝练和总结,能更大限度的优化代码以及对已有代码的合理重构.作为一名合格的前端工程师,学习设计模式是对自己工作经验的另一种方式的总结和反思,也是开发高质量,高可维护性,可扩展性代码的重要手段. 我们所熟知的金典的几大框架,比如jquery, react, vue内部也大量应用了设计模式, 比如观察

一种JavaScript 类的设计模式

一种JavaScript 类的设计模式尽管前面介绍了如何定义一个类,如何初始化一个类的实例,但既可以在function定义的函数体中添加成员,又可以用prototype 定义类的成员,代码显的很混乱,和面向对象语言类的实现之间有着很大的区别.那么,如何以一种清晰的方式来定义类呢?下面给出了一种类的实现模式,并将它们对应到面向对象语言类的实现上.类的构造函数用来初始化一个实例,是每个类必不可少的一部分.在传统意义的面向对象中,类的构造函数的名称和类的名称一致,同时它们的定义方式和类成员的定义是类似

javascript设计模式详解之命令模式

每种设计模式的出现都是为了弥补语言在某方面的不足,解决特定环境下的问题.思想是相通的.只不过不同的设计语言有其特定的实现.对javascript这种动态语言来说,弱类型的特性,与生俱来的多态性,导致某些设计模式不自觉的我们都在使用.只不过没有对应起来罢了.本文就力求以精简的语言去介绍下设计模式这个高大上的概念.相信会在看完某个设计模式之后有原来如此的感慨. 一.基本概念与使用场景: 基本概念: 将请求封装成对象,分离命令接受者和发起者之间的耦合. 命令执行之前在执行对象中传入接受者.主要目的相互

JavaScript设计模式 - 迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示. 迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素 许多浏览器都支持 Javascript 的 Array.prototype.forEach 迭代器可以分为 内部迭代器 和 外部迭代器 一.jQuery 中的迭代器 1 $.each( [1,2,3,4], function (i, n) { 2 console.log( "当

javascript设计模式

javascript设计模式 阅读目录 什么是设计模式 单体模式: 工厂模式: 单例模式 观察者模式(发布订阅模式) 策略模式 模板模式 代理模式 外观模式 设计模式太多了,貌似有23种,其实我们在平时的工作中没有必要特意去用什么样的设计模式,或者你在不经意间就已经用了设计模式当中的一种.本文旨在总结平时相对来说用的比较多的设计模式. 回到顶部 什么是设计模式 百度百科: 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结. 使用设计模式是

javascript设计模式实践之迭代器--具有百叶窗切换图片效果的JQuery插件(一)

类似于幻灯片的切换效果,有时需要在网页中完成一些图片的自动切换效果,比如广告,宣传,产品介绍之类的,那么单纯的切就没意思了,需要在切换的时候通过一些效果使得切换生动些. 比较常用之一的就是窗帘切换了. 先贴上完成的效果. 实现原理不复杂,在动的一条一条的称之为“窗帘条”或者是“strip”,每一个strip都是一个div,类似于雪碧图的方式将其背景图的位置设置为strip的可视位置,然后用jquery的animate让他们按照一定规律动起来就完成窗帘切换效果了. 为了使用方便,将这个功能作为jq

JavaScript 设计模式与开发实践读书笔记 http://www.open-open.com/lib/view/open1469154727495.html

JavaScript 设计模式与开发实践读书笔记 最近利用碎片时间在 Kindle 上面阅读<JavaScript 设计模式与开发实践读书>这本书,刚开始阅读前两章内容,和大家分享下我觉得可以在项目中用的上的一些笔记. 我的 github 项目会不定时更新,有需要的同学可以移步到我的 github 中去查看源码: https://github.com/lichenbuliren/design-mode-notes 1.currying 函数柯里化 currying 又称 部分求值 .一个 cu