常见 JavaScript 设计模式 — 原来这么简单(三)

简介: 常见 JavaScript 设计模式 — 原来这么简单

状态模式

状态模式允许一个对象在其内部状态发生改变时,能够改变原本的行为.

举例子

假如现在我们需要设计一个售票机器,主要出售 巴士、火车、飞机票等,价格分别为 50、150、1000,并且能够根据剩余票数决定是否能够继续购买.

通过策略模式实现核心代码逻辑

有了上面的 策略模式 的思想,立马就可以设计出如下的代码:

缺点:没有根据剩余票数决定是否可以继续售卖,主要原因就在于抽离的 ticketTypeMapTicketMachine 之间的状态没有关联

const ticketTypeMap = {
  bus() {
    // do other thing
    return 50;
  },
  train() {
    // do other thing
    return 150;
  },
  plane() {
    // do other thing
    return 1000;
  },
};
class TicketMachine {
  constructor() {
    // 剩余票数
    this.remain = {
      bus: 100,
      train: 150,
      plane: 200,
    };
  }
  selling(type) {
    return ticketTypeMap[type]();
  }
}
复制代码

关联对象状态 — 函数传参

通过函数传参的方式将对象传递给目标函数,让目标函数通过该对象访问和修改对象内部的状态.

const ticketTypeMap = {
  bus(remain) {
    if (remain.bus <= 0) return Error("抱歉,巴士票已售完");
    remain.bus--;
    return 50;
  },
  train(remain) {
    if (remain.train <= 0) return Error("抱歉,火车票已售完");
    remain.train--;
    return 150;
  },
  plane(remain) {
    if (remain.plane <= 0) return Error("抱歉,飞机票已售完");
    remain.plane--;
    return 1000;
  },
};
class TicketMachine {
  constructor() {
    // 剩余票数
    this.remain = {
      bus: 100,
      train: 150,
      plane: 200,
    };
  }
  selling(type) {
    return ticketTypeMap[type](this.remain);
  }
}
复制代码

关联对象状态 — 整合方法

实际上 ticketTypeMap 映射的方法和 TicketMachine 有较强的关联性,不应该单独存在,因此,可以将这个映射对象整合进 TicketMachine 当中

class TicketMachine {
  constructor() {
    // 剩余票数
    this.remain = {
      bus: 100,
      train: 150,
      plane: 200,
    };
  }
  ticketTypeMap = {
    that: this,
    bus() {
      const { remain } = this.that;
      if (remain.bus <= 0) return Error("抱歉,巴士票已售完");
      remain.bus--;
      return 50;
    },
    train() {
      const { remain } = this.that;
      if (remain.train <= 0) return Error("抱歉,火车票已售完");
      remain.train--;
      return 150;
    },
    plane() {
      const { remain } = this.that;
      if (remain.plane <= 0) return Error("抱歉,飞机票已售完");
      remain.plane--;
      return 1000;
    },
  };
  selling(type) {
    return this.ticketTypeMap[type]();
  }
}
复制代码

观察者模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新.

vue 中的观察者模式

image.png

vue 中的响应式原理就使用了 观察者模式,我们简单回顾一下其工作流程:

  • compile:将模板内容编译得到对应的 render 渲染函数
  • render:渲染函数执行生成 VNode,通过 patch 函数初始化视图 view
  • Observe:负责将 data 中返回的对象进行数据劫持(getter/setter),且其中会使用 Dep 来实现 watcher 的存储,相当于 被观察者
  • Dep:在触发 getter 时执行 dep.depend() 实际上执行的是 watcher.addDep(),该方法会将当前的 dep 对象保存到 Watcher,同时将当前的 watcher 通过 dep.addSub() 添加到 Dep
  • Watcher:相当于 观察者,提供统一的 update() 方法供 Dep 调用
  • data changed:响应式数据发生变更,触发数据劫持操作 setter
  • 进而执行 dep.notify() 方法,通过循环去执行 watcher.update() 方法,即执行 queueWatcher()watcher 添加到 queue 队列中
  • 最后由 scheduler 调度器 中执行 nextTick(flushSchedulerQueue) 进行异步队列刷新操作

以上过程中,显然 ObserveWatcher 就是 被观察者观察者 ,因为 Observe 中实现了对 Watcher 的收集和监听到数据状态发生变化时通知 Watcher 更新的处理,可以认为 Dep 只是 Observe 中使用到的一个存储和派发 Watcher 的工具.

发布订阅模式

发布订阅模式有三个核心:发布者、事件中心、订阅者,且发布订阅模式中的 发布者订阅者 不能直接进行通信,必须要经过 事件中心 来统一调度.

与观察者模式的区别

实际上,发布订阅模式和观察者模式在概念上非常相似,做的事情也都一致,主要区别在于:

  • 发布订阅模式依赖于 事件中心 统一调度 发布者订阅者发布者订阅者 不直接进行通信
  • 观察者模式中的 被观察者观察者 是直接建立连接的,被观察者 需要保存 观察者 的信息,观察者 需要提供统一的 方法 供观察者进行使用

实现发布订阅模式

vue 中的 全局事件总线(Event Bus)和 node 中的 Event Emitter,甚至是浏览器中的事件注册(addEventListener)和执行,它们都属于发布订阅模式.

下面实现一个简单的发布订阅模式:

class EventEmitter {
  constructor() {
    this.handlers = {};
  }
  on(name, handle) {
    if (!this.handlers[name]) {
      this.handlers[name] = [];
    }
    this.handlers[name].push(handle);
  }
  emit(name, ...args) {
    if (this.handlers[name]) {
      this.handlers[name].forEach((handle) => {
        handle(...args);
      });
    }
  }
  off(name, handle) {
    if (this.handlers[name]) {
      this.handlers[name] = this.handlers[name].filter((h) => {
        if (handle) return h !== handle;
        return false;
      });
    }
  }
  once(name, handle) {
    const onceHandle = (...args) => {
      handle(...args);
      this.off(name, onceHandle);
    };
    this.on(name, onceHandle);
  }
}
复制代码

迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示,核心目的就是 遍历.

JavaScript 中的遍历方式

  • Array :for...of、for...in、forEach、map、filter
  • Object :for...in
  • Map :for...of、forEach
  • Set :for...of、forEach

看起来很难有一种方法能够兼容以上几种数据结构的遍历方式,即不需要考虑数据结构本身就能实现遍历的目的,但我们可以基于 ES6Symbol.iterator 实现自定义迭代器.

Symbol.iterator 实现通用迭代

Symbol.iterator 为每一个对象定义了默认的迭代器,拥有该迭代器后就可以被 for...of 循环使用.

function $each(data, handle) {
  if (typeof data !== "object") throw TypeError("data should be object!");
  if (!data[Symbol.iterator]) {
    Object.prototype[Symbol.iterator] = function () {
      let i = 0;
      let keys = Reflect.ownKeys(this);
      return {
        next() {
          const done = i >= keys.length;
          return {
            value: done ? undefined : keys[i++],
            done,
          };
        },
      };
    };
  }
  for (const item of data) {
    handle(item);
  }
}
复制代码

最后

大前端的各种新技术层出不穷,很容易忽视如数据结构、设计模式等基础内容,笔者最近也正在学习设计模式相关的内容,其实看很多设计模式相关的内容,很少有讲得简单易懂的,这里还是向那些需要学习设计模式相关内容的同学推荐修言大佬的 JavaScript 设计模式核⼼原理与应⽤实践 .

目录
相关文章
|
12天前
|
设计模式 前端开发 JavaScript
【JavaScript 技术专栏】JavaScript 设计模式与实战应用
【4月更文挑战第30天】本文探讨JavaScript设计模式在提升开发效率和代码质量中的关键作用。涵盖单例、工厂、观察者、装饰器和策略模式,并通过实例阐述其在全局状态管理、复杂对象创建、实时数据更新、功能扩展和算法切换的应用。理解并运用这些模式能帮助开发者应对复杂项目,提升前端开发能力。
|
5月前
|
设计模式 JavaScript 数据安全/隐私保护
js设计模式之工厂模式
js设计模式之工厂模式
34 0
|
4月前
|
设计模式 前端开发 算法
【面试题】 ES6 类聊 JavaScript 设计模式之行为型模式(二)
【面试题】 ES6 类聊 JavaScript 设计模式之行为型模式(二)
|
2月前
|
设计模式 缓存 JavaScript
js常用设计模式
js常用设计模式
22 1
|
5月前
|
设计模式 存储 JavaScript
js设计模式之单例模式
js设计模式之单例模式
48 7
|
10月前
|
设计模式 前端开发 JavaScript
|
10月前
|
设计模式 前端开发 JavaScript
|
7月前
|
设计模式 JSON 前端开发
前端面试必看(手写Promise+js设计模式+继承+函数柯里化等)JavaScript面试全通关(1/3)
前端面试必看(手写Promise+js设计模式+继承+函数柯里化等)JavaScript面试全通关(1/3)
43 0
|
8月前
|
设计模式
js-设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
|
10月前
|
存储 设计模式 前端开发