JavaScript 设计模式

104 阅读6分钟

JavaScript 设计模式

可复用面向对象软件的基础

大分类

  • 创建型(5)解决“对象的创建”问题-将创建和使用代码解耦

工厂方法抽象工厂单例,建造者,原型。

  • 结构型(7)解决“类或对象的组合或组装”问题-将不同功能代码解耦

适配器装饰器代理,外观,桥接,组合,享元

  • 行为型(11)解决“类或对象之间的交互”问题-将不同的行为代码解耦 策略,模板,观察者迭代器,中介者,状态,职责链,命令,访问者,备忘录,解释器。

创建型

工厂方法

在 JS 中,工厂方法是创建对象的一种方式。它像工厂一样,生产出来的函数都是标准件(拥有相同的属性)。它和单例模式有一点像,缓存了对象,避免重复重新结构相同的对象。下面是创建不同角色的工厂类。

function createPeopleFactory(id, name, age) {
  const obj = new Object();
  obj.id = id;
  obj.name = name;
  obj.age = age;
  return obj;
}

const child = createPeopleFactory(1, 'baby', 1);
const father = createPeopleFactory(2, 'peter', 25);

抽象工厂

在工厂方法的基础上再抽象一层,用来管理多个工厂类。平时使用场景很少。

abstract class AbstractFactory {
  public abstract getColor(color: string);
  public abstract getShape(shape: string);
}

// 通过传递形状或颜色信息来获取工厂
class FactoryProducer {
  public static getFactory(choice: string) {
    if (choice === 'SHAPE') {
      return new ShapeFactory();
    } else if (choice === 'COLOR') {
      return new ColorFactory();
    }
    return null;
  }
}

class ColorFactory extends AbstractFactory {
  public getColor(color) {
    // do something
  }

  public getShape() {
    return null;
  }
}

class ShapeFactory extends AbstractFactory {
  public getColor() {
    return null;
  }

  public getShape(shape) {
    // do something
  }
}

const shape = FactoryProducer.getFactory('SHAPE');
shape.getShape('CIRCLE');
const color = FactoryProducer.getFactory('COLOR');
color.getColor('RED');

单例

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

const singleton = function(fn) {
  let result = null;
  return function() {
    return result || (result = fn.apply(this, arguments));
  };
};

const getScript = singleton(function() {
  return document.createElement('script');
});

const script1 = getScript();
const script2 = getScript();
console.log(script1 === script2); // true

结构型

适配器

适配器英文是 Adapter。顾名思义它就是做适配用的,将一个不可用的接口转成可用的接口。适配器模式是一种 “亡羊补牢”的模式,没有人会在程序的设计之初就使用它。最近前端比较典型的应用是跨端框架,mpvue 和 taro,它们都是在应用和各个小程序以及终端之间建立了一层适配器。

下面举一个支付的例子,我们只需要调用 pay 函数,适配器帮我们平台之间的差异

function pay(id, price) {
  const platform = window.platform;
  switch (platform) {
    case 'wechat':
      wx.pay({ id, price });
      break;
    case 'alipay':
      alipay.pay({ id, price });
      break;
    case 'jd':
      jd.pay({ id, price });
      break;
    case 'xxx':
      xxx.toPay({ goodsId: id, price });
      break;
  }
}

pay(101, 1000);

装饰者

写代码的时候,我们总遵循“组合优于继承”,而装饰者模式就是一种用组合关系的来组织代码。而我们平时所说的装饰器就是装饰者的一种应用。

这个人原先普普通通,经过装饰者模式改造,瞬间变成人见人爱的帅哥。

function people(height, weight, character) {
  this.height = 170;
  this.weight = 80;
  this.character = 'normal';
  return this;
}

const xiaowang = people();
console.log(xiaowang.character);

function decorate(ctr) {
  ctr.height = 180;
  ctr.weight = 70;
  ctr.character = 'handsome';
  return ctr;
}

const wang = decorate(people);
console.log(wang.character);

代理

它在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。前端最常听到的代理就是 nginx 代理,它其实是代理的一个应用,把自身作为代理服务器将资源请求转发到终端服务器。在 JS 中比较典型的代理有图片懒加载,合并 http 请求,以及缓存计算乘积。

下面是一个图片懒加载的例子,我们加先加载默认图片,等真实图片加载完之后再替换默认图片。

const createImage = (function() {
  const img = document.createElement('img');
  document.body.appendChild(img);

  return function(src) {
    img.src = src;
  };
})();

const proxyImage = function(fn) {
  const image = new Image();
  const defaultImg = 'https://rs.vip.miui.com/vip-resource/prod/mio/v136/static/media/lazyLoad.a10ffbd7.png';

  return function(src) {
    fn(defaultImg);

    // 这里加一个延迟,可以更好的看到图片替换的过程。
    setTimeout(function() {
      image.src = src;
      image.onload = function() {
        fn(src);
      };
    }, 2000);
  };
};

const proxy = proxyImage(createImage);
proxy('https://pic1.zhimg.com/80/v2-ec33fcec249a9cabab61b14436432bf0_r.jpg');

享元

应用于大量相似对象的系统。一般是借用工厂模式,新建一个对象,然后其他对象共享这个工厂对象,避免新建对象。享元模式是一种用时间换空间的优化模式,避免性能损耗。

享元模式的代码比较好理解,因为衣服的型号就那么几种,我们可以通过身高判断衣服类型(忽略贾玲和宋一茜),所以衣服类型就可以作为一个共享对象。

const selectClothes = (function() {
  const clothesType = {
    160: 's',
    170: 'l',
    175: 'xl',
    180: 'xxl',
    181: 'xxxl'
  };

  return function(height) {
    if (height < 170) {
      return clothesType[160];
    } else {
      // 后面的代码省略
      return clothesType[175];
    }
  }
})();

class People {
  constructor(height, weight) {
    this.height = height;
    this.weight = weight;
    this.clothesType = '';
  }
}

const people1 = new People(160, 100);
const people2 = new People(170, 150);
people1.clothesType = selectClothes(people1.height);
people2.clothesType = selectClothes(people2.height);

行为型

策略

定义一系列的算法,把它们一个个封装起来,并且可以相互替换,这就是策略模式。要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点, 这样才能选择一个合适的 strategy。比如,我们要选择一种合适的旅游出行路线,必须先了解选择飞机、火车、自行车等方案的细节。此时 strategy 要向客户暴露它的所有实现,这是违反最少知识原则的。

我直接使用《JavaScript 设计模式和开发实战》中的一个例子。这是一个计算不同绩效的人对应不同的奖金(奖金 = 工资 * 对应的绩效算法)。

const strategies = {
  S: function(salary) {
    return salary * 4;
  },
  A: function(salary) {
    return salary * 3;
  },
  B: function(salary) {
    return salary * 2
  }
}

const calculateBonus = function(level, salary) {
  return strategies[level](salary);
}

const staff1 = calculateBonus('S', 10000);
const staff2 = calculateBonus('A', 20000);

观察者

又称发布-订阅模式,它定义对象间的一种一对多的依赖关系。主要用于异步编程。JavaScript 本身也是一门基于事件驱动的语言,也利用了发布订阅模式。

下面是JS中自定义事件,它就是一个典型的观察者模式。

const msg = new Event('message');
window.addEventListener('message', function() {
  console.log('我接收到了消息');
});
window.dispatchEvent(msg);

我们来手写一个观察者模式,下面这个模式比较简陋,边界处理很粗糙。

class Event {
  constructor() {
    this.events = [];
  }

  on(fn) {
    this.events.push(fn);
  }

  emit(data) {
    this.events.forEach(fn => {
      fn(data);
    })
  }

  // off方法我这里就不实现了,也比较简单
  off () {}
}

const event = new Event();
event.on(function(data) {
  console.log('我是第一个注册事件', data);
});
event.on(function(data) {
  console.log('我是第二个注册事件', data);
});
event.emit('已发送');

迭代器

是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。分为内部迭代器和外部迭代器。

像我们大部分迭代器都是内部迭代器。比如 forEach,map ,filter 等。而外部迭代器,迭代的控制权在外部。

const Iterator = function(obj){
  let current = 0;

  const next = function(){
    console.log(obj[current]);
    current++;
  };

  const isDone = function(){
    return current >= obj.length;
  };

  const getCurItem = function(){
    return obj[current];
  };

  return {
    next: next,
    isDone: isDone,
    getCurItem: getCurItem
  }
};

const iterator = new Iterator([1, 2, 3]);
iterator.next(); // 1
iterator.next(); // 2
iterator.next(); // 3

结束语

摘抄这篇文章内容用做学习,感谢作者大大,链接附上:设计模式之美-前端 - 知乎 (zhihu.com)