单例模式

保证一个类只有一个实例,并提供一个访问它的全局访问点(调用一个类,任何时候返回的都是同一个实例)。

实现方法:使用一个变量来标志当前是否已经为某个类创建过对象,如果创建了,则在下一次获取该类的实例时,直接返回之前创建的对象,否则就创建一个对象。

单体模式的优点是:

  1. 可以用来划分命名空间,减少全局变量的数量。
  2. 使用单体模式可以使代码组织的更为一致,使代码容易阅读和维护。
  3. 可以被实例化,且实例化一次。
  1. class Singleton{
  2. constructor(name){
  3. this.name=name
  4. this.instance = null
  5. }
  6. static getInstance(name){
  7. if(!this.instance){
  8. this.instance = new Singleton(name)
  9. }
  10. return this.instance
  11. }
  12. }
  13. const ins = new Singleton('hhhh')
  14. let single1 = Singleton.getInstance('guan')
  15. let single2 = Singleton.getInstance('qingchao')
  16. console.log(single1 === single2) //true
  17. console.log(single1.name) //guan
  18. console.log(single2.name) //guan

发布订阅模式 vs 观察者模式

image.png

从图中可以看出:

  • 观察者与目标(被观察者)是直接进行交互的,包括订阅和触发。
  • 发布订阅模式是透过一个中间者当调度中心,相关的订阅与发布都由调度中心来进行协调。

两者的优缺点:

  • 观察者模式:优点就是一一对应,比较直观明了,占用内存资源容易进行回收。缺点就是两者耦合。
  • 发布订阅模式:优点就是一方面实现了发布者与订阅者之间的解耦,中间者可在两者操作之间进行更细粒度的控制。如:条件过滤发布,权限控制等等。缺点就是整一个中间调度会越来越庞大,需要手动清除里面的发布回调。

    发布订阅模式

    发布/订阅模式,订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。

  • 发布者

  • 事件中心
  • 订阅者

发布者->事件中心<=>订阅者,订阅者需要向事件中心订阅指定的事件 -> 发布者向事件中心发布指定事件内容 -> 事件中心通知订阅者 -> 订阅者收到消息(可能是多个订阅者),到此完成了一次订阅发布的流程。

发布者并不直接发布消息给订阅者,通过调度中心将消息发送给订阅者
解耦

Event 可以理解为事件中心,提供了订阅和发布功能。
订阅者在订阅事件的时候,只关注事件本身,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件本身,而不关心谁订阅了这个事件。

  1. class Event {
  2. constructor() {
  3. // 所有 eventType 监听器回调函数(数组)
  4. this.listeners = {}
  5. }
  6. /**
  7. * 订阅事件
  8. * @param {String} eventType 事件类型
  9. * @param {Function} listener 订阅后发布动作触发的回调函数,参数为发布的数据
  10. */
  11. on(eventType, listener) {
  12. if (!this.listeners[eventType]) {
  13. this.listeners[eventType] = []
  14. }
  15. this.listeners[eventType].push(listener)
  16. }
  17. off(type, fn) {
  18. if (!this.listeners[type]) return;
  19. const index = this.listeners.indexOf(fn);
  20. if (index === -1) return;
  21. this.listeners[type].splice(index, 1);
  22. }
  23. /**
  24. * 发布事件
  25. * @param {String} eventType 事件类型
  26. * @param {Any} data 发布的内容
  27. */
  28. emit(eventType, data) {
  29. const callbacks = this.listeners[eventType]
  30. if (callbacks) {
  31. callbacks.forEach((c) => {
  32. c(data)
  33. })
  34. }
  35. }
  36. }
  37. const event = new Event()
  38. event.on('open', (data) => {
  39. console.log(data)
  40. })
  41. event.emit('open', { open: true })

观察者模式

发布者和观察者耦合,发布者必须有观察者的方法

  1. class Observer {
  2. update() {
  3. console.log("update……");
  4. }
  5. }
  6. class Subject {
  7. constructor() {
  8. this.observers = [];
  9. }
  10. addObserver(obsever) {
  11. this.observers.push(obsever);
  12. }
  13. notify() {
  14. this.observers.forEach((ob) => ob.update());
  15. }
  16. }
  17. // 测试
  18. // 测试
  19. let subject = new Subject();
  20. let ob = new Observer();
  21. //订阅目标
  22. subject.addObserver(ob);
  23. //目标发布消息调用观察者的更新方法了
  24. subject.notify(); //update

装饰器模式

装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责(方法或属性)。与继承相比,装饰者是一种更轻便灵活的做法。

简单说:可以动态的给某个对象添加额外的职责,而不会影响从这个类中派生的其它对象。
装饰器本质就是编译时执行的函数,装饰器只能用于类和类的方法,不能用于函数

  1. ES7装饰器
  2. function isAnimal(target) {
  3. target.isAnimal = true
  4. return target
  5. }
  6. // 装饰器
  7. @isAnimal
  8. class Cat {
  9. // ...
  10. }
  11. console.log(Cat.isAnimal) // true
  12. 作用于类属性的装饰器:
  13. function readonly(target, name, descriptor) {
  14. discriptor.writable = false
  15. return discriptor
  16. }
  17. class Cat {
  18. @readonly
  19. say() {
  20. console.log("meow ~")
  21. }
  22. }
  23. var kitty = new Cat()
  24. kitty.say = function() {
  25. console.log("woof !")
  26. }
  27. kitty.say() // meow ~

代理模式

提供一种代理以控制对这个对象的访问。 代理模式使得代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西。

  1. let person = {
  2. name: "guanqingchao"
  3. };
  4. const proxy = new Proxy(person, {
  5. get(target, property) {
  6. if (property in target) {
  7. return target[property];
  8. } else {
  9. throw new ReferenceError('Property "' + property + '" does not exist.');
  10. }
  11. }
  12. });
  13. console.log(proxy.name);
  14. // console.log(proxy.age)