Js实现基于类的枚举模式

更新日期: 2019-12-22阅读: 1.8k标签: 模式

实现枚举:第一次尝试

枚举是由一组值组成的类型。例如 TypeScript 中有内置的枚举,我们可以通过它们来定义自己的布尔类型:

enum MyBoolean {
  false,
  true,
}

或者可以定义自己的颜色类型:

enum Color {
  red,
  orange,
  yellow,
  green,
  blue,
  purple,
}

这段 TypeScript 代码会被编译为以下 JavaScript 代码(省略了一些详细信息,以便于理解):

const Color = {
  red: 0,
  orange: 1,
  yellow: 2,
  green: 3,
  blue: 4,
  purple: 5,
};

这种实现有几个问题:

  1. 日志输出:如果你输出一个枚举值,例如 Color.red,是看不到它的名称的。
  2. 类型安全:枚举值不是唯一的,它们会其他数字所干扰。例如,数字 1 可能会误认为 Color.green,反之亦然。
  3. 成员资格检查:你无法轻松检查给定的值是否为 Color 的元素。

用普通 JavaScript,我们可以通过使用字符串而不是数字作为枚举值来解决问题 1:

const Color = {
  red: 'red',
  orange: 'orange',
  yellow: 'yellow',
  green: 'green',
  blue: 'blue',
  purple: 'purple',
}

如果我们用符号作为枚举值,还能够获得类型安全性:

const Color = {
  red: Symbol('red'),
  orange: Symbol('orange'),
  yellow: Symbol('yellow'),
  green: Symbol('green'),
  blue: Symbol('blue'),
  purple: Symbol('purple'),
}
assert.equal(
  String(Color.red), 'Symbol(red)');

符号存在的一个问题是需要将它们明确转换为字符串,而不能强制转换(例如,通过 + 或内部模板文字):

assert.throws(
  () => console.log('Color: '+Color.red),
  /^TypeError: Cannot convert a Symbol value to a string$/
);

尽管可以测试成员资格,但这并不简单:

function isMember(theEnum, value) {
  return Object.values(theEnum).includes(value);
}
assert.equal(isMember(Color, Color.blue), true);
assert.equal(isMember(Color, 'blue'), false);


枚举模式

通过对枚举使用自定义类可以使我们进行成员资格测试,并在枚举值方面具有更大的灵活性:

class Color {
  static red = new Color('red');
  static orange = new Color('orange');
  static yellow = new Color('yellow');
  static green = new Color('green');
  static blue = new Color('blue');
  static purple = new Color('purple');

  constructor(name) {
    this.name = name;
  }
  toString() {
    return `Color.${this.name}`;
  }
}

我把这种用类作为枚举的方式称为“枚举模式”。它受到 Java 中对枚举实现的启发。

输出:

console.log('Color: '+Color.red);

// Output:
// 'Color: Color.red'

成员资格测试:

assert.equal(
  Color.green instanceof Color, true);


枚举:枚举模式的辅助库

Enumify 是一个能够帮助我们使用枚举模式的库。它的用法如下:

class Color extends Enumify {
  static red = new Color();
  static orange = new Color();
  static yellow = new Color();
  static green = new Color();
  static blue = new Color();
  static purple = new Color();
  static _ = this.closeEnum();
}

实例属性

Enumify 能够把多个实例属性添加到枚举值中:

assert.equal(
  Color.red.enumKey, 'red');
assert.equal(
  Color.red.enumOrdinal, 0);

原型方法

用 Enumify 实现 .toStrin():

assert.equal(
  'Color: ' + Color.red, // .toString()
  'Color: Color.red');

静态功能

Enumify 设置了两个静态属性– .enumKeys 和 .enumValues:

assert.deepEqual(
  Color.enumKeys,
  ['red', 'orange', 'yellow', 'green', 'blue', 'purple']);
assert.deepEqual(
  Color.enumValues,
  [ Color.red, Color.orange, Color.yellow,
    Color.green, Color.blue, Color.purple]);

它提供了可继承的静态方法 .enumValueOf():

assert.equal(
  Color.enumValueOf('yellow'),
  Color.yellow);

它实现了可继承的可迭代性:

for (const c of Color) {
  console.log('Color: ' + c);
}
// Output:
// 'Color: Color.red'
// 'Color: Color.orange'
// 'Color: Color.yellow'
// 'Color: Color.green'
// 'Color: Color.blue'
// 'Color: Color.purple'


使用枚举的例子

具有实例属性的枚举值

class Weekday extends Enumify {
  static monday = new Weekday(true);
  static tuesday = new Weekday(true);
  static wednesday = new Weekday(true);
  static thursday = new Weekday(true);
  static friday = new Weekday(true);
  static saturday = new Weekday(false);
  static sunday = new Weekday(false);
  static _ = this.closeEnum();
  constructor(isWorkDay) {
    super();
    this.isWorkDay = isWorkDay;
  }
}
assert.equal(Weekday.sunday.isWorkDay, false);
assert.equal(Weekday.wednesday.isWorkDay, true);

通过 switch 使用枚举值

枚举模式也有其缺点:通常在创建枚举时不能引用其他的枚举(因为这些枚举可能还不存在)。解决方法是,可以通过以下函数在外部实现辅助函数:

class Weekday extends Enumify {
  static monday = new Weekday();
  static tuesday = new Weekday();
  static wednesday = new Weekday();
  static thursday = new Weekday();
  static friday = new Weekday();
  static saturday = new Weekday();
  static sunday = new Weekday();
  static _ = this.closeEnum();
}
function nextDay(weekday) {
  switch (weekday) {
    case Weekday.monday:
      return Weekday.tuesday;
    case Weekday.tuesday:
      return Weekday.wednesday;
    case Weekday.wednesday:
      return Weekday.thursday;
    case Weekday.thursday:
      return Weekday.friday;
    case Weekday.friday:
      return Weekday.saturday;
    case Weekday.saturday:
      return Weekday.sunday;
    case Weekday.sunday:
      return Weekday.monday;
    default:
      throw new Error();
  }
}

能够通过 getter 获取实例的枚举值

另一个解决在声明枚举时无法使用其他枚举的方法是通过 getter 延迟访问同级的值:

class Weekday extends Enumify {
  static monday = new Weekday({
    get nextDay() { return Weekday.tuesday }
  });
  static tuesday = new Weekday({
    get nextDay() { return Weekday.wednesday }
  });
  static wednesday = new Weekday({
    get nextDay() { return Weekday.thursday }
  });
  static thursday = new Weekday({
    get nextDay() { return Weekday.friday }
  });
  static friday = new Weekday({
    get nextDay() { return Weekday.saturday }
  });
  static saturday = new Weekday({
    get nextDay() { return Weekday.sunday }
  });
  static sunday = new Weekday({
    get nextDay() { return Weekday.monday }
  });
  static _ = this.closeEnum();
  constructor(props) {
    super();
    Object.defineProperties(
      this, Object.getOwnPropertyDescriptors(props));
  }
}
assert.equal(
  Weekday.friday.nextDay, Weekday.saturday);
assert.equal(
  Weekday.sunday.nextDay, Weekday.monday);

getter 传递给对象内部的构造函数。构造函数通过 Object.defineProperties() 和 Object.getOwnPropertyDescriptors()将它们复制到当前实例。但是我们不能在这里使用 Object.assign(),因为它无法复制 getter 和其他方法。

通过实例方法实现状态机

在下面的例子中实现了一个状态机。我们将属性(包括方法)传递给构造函数,构造函数再将其复制到当前实例中。

class State extends Enumify {
  static start = new State({
    done: false,
    accept(x) {
      if (x === '1') {
        return State.one;
      } else {
        return State.start;
      }
    },
  });
  static one = new State({
    done: false,
    accept(x) {
      if (x === '1') {
        return State.two;
      } else {
        return State.start;
      }
    },
  });
  static two = new State({
    done: false,
    accept(x) {
      if (x === '1') {
        return State.three;
      } else {
        return State.start;
      }
    },
  });
  static three = new State({
    done: true,
  });
  static _ = this.closeEnum();
  constructor(props) {
    super();
    Object.defineProperties(
      this, Object.getOwnPropertyDescriptors(props));
  }
}
function run(state, inputString) {
  for (const ch of inputString) {
    if (state.done) {
      break;
    }
    state = state.accept(ch);
    console.log(`${ch} --> ${state}`);
  }
}

状态机检测字符串中是否存在连续的三个 1 的序列:

run(State.start, '01011100');

// Output:
// '0 --> State.start'
// '1 --> State.one'
// '0 --> State.start'
// '1 --> State.one'
// '1 --> State.two'
// '1 --> State.three'

任意枚举值

有时我们需要枚举值是数字(例如,用于表示标志)或字符串(用于与 HTTP 头中的值进行比较)。可以通过枚举来实现。例如:

class Mode extends Enumify {
  static user_r = new Mode(0b100000000);
  static user_w = new Mode(0b010000000);
  static user_x = new Mode(0b001000000);
  static group_r = new Mode(0b000100000);
  static group_w = new Mode(0b000010000);
  static group_x = new Mode(0b000001000);
  static all_r = new Mode(0b000000100);
  static all_w = new Mode(0b000000010);
  static all_x = new Mode(0b000000001);
  static _ = this.closeEnum();
  constructor(n) {
    super();
    this.n = n;
  }
}
assert.equal(
  Mode.user_r.n | Mode.user_w.n | Mode.user_x.n |
  Mode.group_r.n | Mode.group_x.n |
  Mode.all_r.n | Mode.all_x.n,
  0o755);
assert.equal(
  Mode.user_r.n | Mode.user_w.n | Mode.user_x.n |
  Mode.group_r.n,
  0o740);

原文:https://2ality.com/2020/01/enum-pattern.html

链接: https://fly63.com/article/detial/7305

js设计模式之单例模式,javascript如何将一个对象设计成单例

单例模式是我们开发中一个非常典型的设计模式,js单例模式要保证全局只生成唯一实例,提供一个单一的访问入口,单例的对象不同于静态类,我们可以延迟单例对象的初始化,通常这种情况发生在我们需要等待加载创建单例的依赖。

前端设计模式:从js原始模式开始,去理解Js工厂模式和构造函数模式

工厂模式下的对象我们不能识别它的类型,由于typeof返回的都是object类型,不知道它是那个对象的实例。另外每次造人时都要创建一个独立的person的对象,会造成代码臃肿的情况。

JavaScript设计模式_js实现建造者模式

建造者模式:是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象

html和xhtml,DOCTYPE和DTD,标准模式和兼容模式

主要涉及知识点: HTML与XHTML,HTML与XHTML的区别,DOCTYPE与DTD的概念,DTD的分类以及DOCTYPE的声明方式,标准模式(Standard Mode)和兼容模式(Quircks Mode),标准模式(Standard Mode)和兼容模式(Quircks Mode)的区别

前端四种设计模式_JS常见的4种模式

JavaScript中常见的四种设计模式:工厂模式、单例模式、沙箱模式、发布者订阅模式

javascript 策略模式_理解js中的策略模式

javascript 策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句,策略模式提供了开放-封闭原则,使代码更容易理解和扩展, 策略模式中的代码可以复用。

javascript观察者模式_深入理解js中的观察者模式

javascript观察者模式又叫发布订阅模式,观察者模式的好处:js观察者模式支持简单的广播通信,自动通知所有已经订阅过的对象。存在一种动态关联,增加了灵活性。目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

Vue中如何使用方法、计算属性或观察者

熟悉 Vue 的都知道 方法methods、计算属性computed、观察者watcher 在 Vue 中有着非常重要的作用,有些时候我们实现一个功能的时候可以使用它们中任何一个都是可以的

我最喜欢的 JavaScript 设计模式

我觉得聊一下我爱用的 JavaScript 设计模式应该很有意思。我是一步一步才定下来的,经过一段时间从各种来源吸收和适应直到达到一个能提供我所需的灵活性的模式。让我给你看看概览,然后再来看它是怎么形成的

Flutter 设计模式 - 简单工厂

在围绕设计模式的话题中,工厂这个词频繁出现,从 简单工厂 模式到 工厂方法 模式,再到 抽象工厂 模式。工厂名称含义是制造产品的工业场所,应用在面向对象中,顺理成章地成为了比较典型的创建型模式

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!