1

设计模式之美-前端

 3 years ago
source link: https://zhuanlan.zhihu.com/p/111553641
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

设计模式之美-前端

最近看完了《JavaScript 设计模式与开发实践》,也在学习极客时间的专栏《设计模式之美》,想着整理一下常用的设计模式。设计模式不仅受用于面试,更能增进代码水平,是每个程序员必须掌握的。

GoF 在《设计模式》一种归纳了 23 种设计模式,而它们又属于三种类型的模式,分别是创建型模式、结构型模式和行为型模式。正如书中所说,其实每一种设计模式都来自真实项目,作者只是将它们取一个好听的名字,方便记忆与传播。所以设计模式可比数据结构算法上手要简单一点,因为很多设计模式你可能都似曾相似,学习的过程不过是一个接一个的顿悟。

本文中每一种设计模式,我都会搭配一个简单的例子,我会尽量用 JS 实现,如果 JS 不能很好的表达,就用 Java(已替换成TS) 实现。毕竟《设计模式》一书的子标题是“可复用面向对象软件的基础”,它是为面向对象总结的设计模式。

  • 创建型(5)

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

  • 结构型(7)

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

  • 行为型(11)

策略,模板,观察者,迭代器,中介者,状态,职责链,命令,访问者,备忘录,解释器。

创建型设计模式主要解决“对象的创建”问题,结构型设计模式主要解决“类或对象的组合或组装”问题,那行为型设计模式主要解决的就是“类或对象之间的交互”问题。

具体一点,设计模式要干的事情就是解耦,创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦。

v2-e760ac52f2a30d6624bbd55dcaaa145f_720w.jpg

常用设计模式

常用的设计模式有,工厂模式,单例模式,,适配器模式,装饰器模式,代理模式,享元模式,策略模式,观察者模式,迭代器模式。如果你还没准备系统的了解设计模式,可以先看这个这些模式。

在 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

用来参数需要在构造函数中初始化,但是参数又过多的场景。

未使用建造者模式

class Shape {
  constructor(width, height, color, opacity, borderWidth, boxShadow) {
    if (typeof width !== 'number') {
      throw new Error('width should be a number');
    }
    this.width = width;

    if (typeof height !== 'number') {
      throw new Error('width should be a number');
    }
    this.height = height;

    this.color = color;
    this.opacity = opacity;

    if (width < borderWidth) {
      throw new Error('width should be greater than borderWidth');
    }
    this.borderWidth = borderWidth;
  }
}

const shape = new Shape(10, 10, 'red', 1, 10);

使用建造者模式改造后

class ShapeConfig {
  constructor(builder) {
    this.width = builder.width;
    this.height = builder.height;
    this.color = builder.color;
    this.opacity = builder.opacity;
    this.borderWidth = builder.borderWidth;
  }
}

class Shape {
  constructor() {
    this.width = 0;
    this.height = 0;
    this.color = '';
    this.opacity = 1;
    this.borderWidth = 0;
  }

  // 可以把多个值的比较逻辑都放在构建函数中
  build() {
    if (this.width < this.borderWidth) {
      throw new Error('width should be greater than borderWidth');
    }
    return new ShapeConfig(this);
  }
  setWidth(width) {
    if (typeof width !== 'number') {
      throw new Error('width should be a number');
    }
    this.width = width;
    return this;
  }
  setHeight(height) {
    if (typeof height !== 'number') {
      throw new Error('height should be a number');
    }
    this.height = height;
    return this;
  }
  setColor(color) {
    this.color = color;
    return this;
  }
  setOpacity(opacity) {
    this.opacity = opacity;
    return this;
  }
  setBorderWidth(borderWidth) {
    this.borderWidth = borderWidth;
    return this;
  }
}

const shape = new Shape();
// 可以通过return this方便的实现链式调用
shape
  .setWidth(10)
  .setHeight(10)
  .setColor('red')
  .setOpacity(1)
  .setBorderWidth(11)
  .build();
console.log(shape);

原型模式是用于创建对象的一种模式,通过克隆来创建对象的。JavaScript 就是一种基于原型的语言。但就 JavaScript 的真正实现来说,其实并不能说对象有原型,而只能说对象的构造器有原型。我们可以通过Object.create克隆对象。

const Plane = function() {
  this.blood = 100;
  this.attackLevel = 1;
  this.defenseLevel = 1;
};

const plane = new Plane();
plane.blood = 500;
plane.attackLevel = 10;
plane.defenseLevel = 7;
const clonePlane = Object.create(plane);
console.log(clonePlane);

适配器英文是 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');

也叫门面模式,GoF上的定义是,外观模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。它和适配器模式类似,配器是做接口转换,解决的是原接口和目标接口不匹配的问题,而外观模式做接口整合,解决多接口带来的调用问题。

比如下面的获取用户信息的接口。

getUserBaseInfo() {
  return API.getUserBaseInfo();
}

getUserPriority() {
  return API.getUserPriority();
}

getUserCustomContent() {
  return API.getUserCustomContent();
}

// 对外提供一个总的接口
async getUserInfo() {
  const baseInfo = await getUserBaseInfo();
  const priority = await getUserPriority();
  const customContent = await getUserCustomContent();
  return { ...baseInfo, ...priority, ...customContent };
}

const userInfo = getUserInfo();

在《设计模式》中解释为将抽象和实现解耦,让它们可以独立变化。比如h5开发过程中,就通过JsBridge桥,实现JS和Native的双向通信,这里桥不做逻辑处理,只是收集双方的请求,再转发,桥实现了抽象,具体实现交给JS和Native。

举一个简单的例子,比如某个男生想要某个女生的号码,但是他不好意思,就通过共同朋友将他们桥接起来,最后那个男生对共同好友说了谢谢,很有礼貌。

function getNumber(number, callback) {
  callback('thank you');
}

function sendNumber() {
  return '13***********';
}

function bridge() {
  const number = sendNumber();
  getNumber(number, (feedback) => {
    console.log(feedback);
  });
} 

bridge();

我们也可以进行抽象,通过一个bridge函数,实现双方通信。

function bridge(send, receiveFn, sendFn) {
  const receive = receiveFn(send);
  sendFn(receive);
}

const send = 'hello';
function receiveFn(send) {
  if (send === 'hello') {
    return '我们不合适';
  }
  return '我们可以聊聊';
}
bridge(send, receiveFn, receive => {
  console.log(receive);
});

不是我们平时说的组合关系。它规定了数据类型必须是树型结构,并且表示“部分-整体”的层次结构,是用来处理单个对象和组合对象之间的关系。

我们通过一个扫描文件的例子来说明。

class Folder {
  constructor(name) {
    this.name = name;
    this.files = [];
  }

  add(file) {
    this.files.push(file);
  }

  scan() {
    console.log('开始扫描文件夹: ' + this.name);
    this.files.forEach(file => {
      file.scan();
    })
  }
};


class File {
  constructor(name) {
    this.name = name;
  }

  add(file) {
    throw new Error('不能往文件里添加东西,' + file);
  }

  scan() {
    console.log('开始扫描文件: ' + this.name);
  }
}

const rootFolder = new Folder('根目录');
const folder1 = new Folder('一级目录');
const folder2 = new Folder('二级目录');

const file = new File('根文件');
const file1 = new File('一级文件');
const file2 = new File('二级文件');
const file3 = new File('也是二级文件');

rootFolder.add(folder1);
folder1.add(folder2);

rootFolder.add(file);
folder1.add(file1);
folder2.add(file2);
folder2.add(file3);

rootFolder.scan();

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

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

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);

将公共的代码抽成一个抽象类,子类继承抽象类,并重写相应的方法。模板方法模式是为数不多的基于继承的设计模式。

因为JS中无法实现抽象类,我直接用TS实现这个例子。

如果你对TS还不了解,可以看这篇文章。

abstract class Beverage {
  init() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    this.addCondiments();
  }
  boilWater() {
    console.log('把自来水煮沸');
  }
  abstract brew(): void
  abstract pourInCup(): void
  abstract addCondiments(): void
}

class Tea extends Beverage {
  brew() {
    console.log('用沸水浸泡茶叶');
  }
  pourInCup() {
    console.log('把茶倒进杯子里');
  }
  addCondiments() {
    console.log('加点糖');
  }
}

class Coffee extends Beverage {
   brew() {
    console.log('用沸水浸泡咖啡');
  }
  pourInCup() {
    console.log('把咖啡倒进杯子里');
  }
  addCondiments() {
    console.log('加点牛奶');
  }
}

const tea = new Tea();
tea.init();

const coffee = new Coffee();
coffee.init();

又称发布-订阅模式,它定义对象间的一种一对多的依赖关系。主要用于异步编程。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

定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。主要解决各个类之间关系复杂,且每个类都需要知道它要交互的类。这个时候就可以引入中介者,把脏活累活,耦合关系全放到中介者类中,我们其他类负责貌美如花。

下面是一个机场指挥塔的例子,它就是一个中介者,所有飞机想要获取航道信息都需要向指挥塔请求。

class PlaneCommandTower {
  constructor(channel) {
    this.channel = new Array(channel).fill(false);
  }

  enter(i) {
    if (this.channel[i] === true || (i + 1 > this.channel.length)) {
      throw new Error('这个航道已经被占用了');
    }
    console.log('航道预占成功');
    this.channel[i] = true;
  }

  leave(i) {
    this.channel[i] = false;
  }

  isBusy(i) {
    return this.channel[i];
  }

  getChannelCnt() {
    return this.channel.length;
  }
}

class Plane {
  constructor(commander) {
    this.commander = commander;
    this.channelCnt = this.commander.getChannelCnt();
  }

  land() {
    let i = 0;
    while(this.commander.isBusy(i)) {
      ++i;
      if (i > this.channelCnt) {
        break;
      }
    }
    if (i < this.channelCnt) {
      this.commander.enter(i);
    } else {
      console.log('航道已经被占满了');
      return 'wait';
    }
  }
}

const commander = new PlaneCommandTower(3);

const plane1 = new Plane(commander);
const plane2 = new Plane(commander);
const plane3 = new Plane(commander);
const plane4 = new Plane(commander);

plane1.land();
plane2.land();
plane3.land();
plane4.land();

状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。举个例子,有一个电灯,电灯上面只有一个开关。当电灯开着的时候,此时按下开关,电灯会切换到关闭状态;再按一次开关,电灯又将被打开。同一个开关按钮,在不同的状态下,表现出来的行为是不一样的。

我们以灯的例子,扩展一下,比较高级的灯,不止开关两种状态,它有弱光,正常光,环保光,关四种状态。

class Light {
  constructor() {
    this.status = ['opened', 'closed'];
    this.curStatus = -1;
  }

  setStatus(status) {
    this.status = status;
  }

  press() {
    this.curStatus = (this.curStatus + 1 === this.status.length) ? 1 : this.curStatus + 1;
    console.log(this.status[this.curStatus]);
  }
}

const light = new Light();
light.setStatus(['weak', 'common', 'environmental', 'closed']);
light.press(); // weak
light.press(); // common
light.press(); // environmental
light.press(); // closed

定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。举个例子,如果早高峰能顺利挤上公交车的话,那么估计这一天都会过得很开心。因为公交车上人 实在太多了,经常上车后却找不到售票员在哪,所以只好把两块钱硬币往前面递。除非 你运气够好,站在你前面的第一个人就是售票员,否则,你的硬币通常要在 N 个人手上 传递,才能最终到达售票员的手里。

在JS中,无论是作用域链、原型链,还是 DOM 节点中的事件冒泡,我们都能从中找到职责链模式的影子。

假设我们负责一个售卖手机的电商网站,经过分别交纳 500 元定金和 200 元定金的两轮预定后(订单已在此时生成),现在已经到了正式购买的阶段。公司针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过 500 元定金的用 户会收到 100 元的商城优惠券,200 元定金的用户可以收到 50 元的优惠券,而之前没有支付定金 的用户只能进入普通购买模式。

class Chain {
  constructor(fn) {
    this.fn = fn;
    this.successor = null;
  }

  setNextSuccessor(successor) {
    return this.successor = successor;
  }

  passRequest() {
    const ret = this.fn.apply(this, arguments);
    if (ret === 'nextSuccessor'){
      return this.successor && this.successor.passRequest.apply(this.successor, arguments);
    }
    return ret;
  }
}

const chainOrder500 = new Chain(order500);
const chainOrder200 = new Chain(order200);
const chainOrderNormal = new Chain(orderNormal);

chainOrder500.setNextSuccessor(chainOrder200);

chainOrder200.setNextSuccessor(chainOrderNormal);

chainOrder500.passRequest(1, true, 500); 
chainOrder500.passRequest(2, true, 500); 
chainOrder500.passRequest(3, true, 500); 
chainOrder500.passRequest(1, false, 0);

命令模式中的命令指的是一个执行某些特定事情的指令。命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。

下面通过一个按钮的点击事件,触发页面的刷新事件。

const button1 = document.createElement('button');
document.body.appendChild(button1);

const setCommand = function(button, func) {
  button.onclick = function() {
    func();
  };
};
const MenuBar = {
  refresh: function() {
    console.log("刷新菜单界面");
  }
};
const RefreshMenuBarCommand = function(receiver) {
  return function() {
    receiver.refresh();
  };
};

const refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(button1, refreshMenuBarCommand);

button1.click();

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。其次,必须定义一个访问者类,并且内部有visit方法。元素的执行算法实现accept方法,而内部通常都是visitor.visit(this);

function Visitor() {
  this.visit = function (v) {
    console.log('the computer type is ' + v.type);
  }
}

function ComputerTypeVisitor(type) {
  this.type = type;
  this.accept = function (visitor) {
    visitor.visit(this);
  }
}

const visitor = new ComputerTypeVisitor('dell');
visitor.accept(new Visitor()); // the computer type is dell

定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态

const Page = function () {
  let page = 1,
    cache = {},
    data;
  return function (page) {
    if (cache[page]) {
      data = cache[page];
      render(data);
    } else {
      Ajax.send('cgi.xx.com/xxx', function (data) {
        cache[page] = data;
        render(data);
      });
    }
  }
}();

它是一种特殊的设计模式,它建立一个解释器,对于特定的计算机程序设计语言,用来解释预先定义的文法。通俗点,你通过函数名的定义就能知道程序即将要执行的过程。

function Context() {
  let sum;
  let list = [];
  this.getSum = function () {
    return sum;
  }
  this.setSum = function (_sum) {
    sum = _sum;
  }
  this.add = function (eps) {
    list.push(eps);
  }
  this.getList = function () {
    return list;
  }
}

function PlusExpression() {
  this.interpret = function (context) {
    let sum = context.getSum();
    sum++;
    context.setSum(sum);
  }
}

function MinusExpression() {
  this.interpret = function (context) {
    let sum = context.getSum();
    sum--;
    context.setSum(sum);
  }
}

const context = new Context();
context.setSum(20);
//运行加法三次
context.add(new PlusExpression());
context.add(new PlusExpression());
context.add(new PlusExpression());
//运行减法两次
context.add(new MinusExpression());
context.add(new MinusExpression());
const list = context.getList();
for (let i = 0; i < list.length; i++) {
  const expression = list[i];
  expression.interpret(context);
}
console.log("打印输出结果:" + context.getSum()); // 打印输出结果:21

文章中的内容如果有错误,请及时指出。毕竟将面向对象标准的设计模式迁移到JS中,可能会存在使用不当的情况。文章中的好几个例子都来自《JavaScript 设计模式与开发实战》。等“设计模式之美-前端”篇章彻底结束,我会更新“数据结构和算法-前端”篇,敬请期待吧。

参考资料:《JavaScript 设计模式与开发实战》,《设计模式之美》专栏,《设计模式:可复用面向对象软件的基础》

【JS设计模式】解释器模式代码示例

js设计模式-备忘录模式


如果你是前端小白,可以通过这篇文章找到前端之路,目测今年点赞能破1000。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK