JavaScript 枚举Enum和常量池

JavaScript 枚举Enum和常量池

在JavaScript中, 通常我们都会用到枚举和常量池, 那该如何优雅的定义常量池呢?

// 定义全局默认格式化函数
let _defaultFormatter = function (value) {
  return this.getLabel(value);
};

/**
 * @class Constant 定义枚举类
 *
 * @param {Object | Array} map 枚举对象或数组
 * @param {Function} formatter 自定义格式化函数
 */
export default class Constant {
  constructor (map, formatter) {
    // 只接收对象或数组参数
    if (!(map instanceof Object)) {
      throw new Error('The parameter should be an object or array.');
    }

    const self = this;

    this._keys = Object.keys(map);
    this._values = {};
    this.LABEL = {};
    this._options = {};

    this._keys.forEach((key) => {
      const m = map[key];

      // 接收整数或字符串
      if (typeof m === 'number' || typeof m === 'string') {
        self[key] = m;
        self.LABEL[key] = m;
      } else if ((m instanceof Array)) {
        // 数组 [1, 'Label']
        self[key] = m[0];
        self.LABEL[key] = m[1];

        self._options[self[key]] = {};
        if (m[2] && typeof m[2] === 'object') {
          // get other attrs
          Object.keys(m[2]).forEach(attr => {
            if (attr !== 'value' && attr !== 'label') {
              self._options[self[key]][attr] = m[2][attr];
            }
          });
        }
      } else if ((m instanceof Object)) {
        // 对象 {value: 1, label: 'Label'}
        self[key] = m.value;
        self.LABEL[key] = m.label;
        self._options[self[key]] = {};

        // get other attrs
        Object.keys(m).forEach(attr => {
          if (attr !== 'value' && attr !== 'label') {
            self._options[self[key]][attr] = m[attr];
          }
        });
      } else {
        throw new Error('The attribute\'s value should be an object, array, number or string.');
      }

      this._values[self[key]] = self.LABEL[key];
    });

    // custom formatter
    this.formatter = typeof formatter === 'function' ? formatter.bind(self) : Constant.defaultFormatter.bind(self);
  }

  // [[[ formatter setter and getter
  static get defaultFormatter () {
    return _defaultFormatter;
  }

  static set defaultFormatter (fn) {
    _defaultFormatter = fn;
  }
  // ]]]

  /**
   * 通过value获取Label
   * @param value
   * @returns {*}
   */
  getLabel (value) {
    return this._values[value] || '';
  }

  /**
   * 获取额外参数项
   * @param value
   * @returns {*|{}}
   */
  getOptions (value) {
    // console.log(value, this._options);
    return this._options[value] || {};
  }

  /**
   * 校验value合法性
   * @param value
   * @returns {boolean}
   */
  isValid (value) {
    // console.log(Object.keys(this._values), value);
    return Object.keys(this._values).map(o => +o).indexOf(value) !== -1;
  }

  /**
   * 以数组方式返回
   * @returns {{label: *, value: *, key: string}[]}
   */
  toArray () {
    return this._keys.filter(key => {
      // 过滤小于1的枚举值
      const val = this[key];
      return (typeof val === 'string') || val >= 0;
    }).map(key => {
      return {
        key,
        value: this[key],
        label: this.LABEL[key],
        ...this._options[this[key]]
      };
    });
  }
}

// 使用方式
const level = new Constant([
  { key: 'error', value: 1, label: '错误', arg1, arg2, ... },
  { key: 'warn', value: 2, label: '警告', arg1, arg2, ... },
  { value: 3, label: '正常' }
]);

const level = new Constant({
  error: [1, '错误', [argsObject]],
  warn: [2, '警告', [argsObject]],
  info: [3, '正常', [argsObject]],
}, [function(value) formatter]);

level.error; // 1
level.LABEL.error; // 错误
level.getLabel([value]); // 菜单
level.getOptions(1); // 额外信息 => argsObject
level.toArray(); // [{ key: 'error', value: 1, label: '错误', arg1, arg2, ... }, { key: 'warn', value: 2, label: '警告', arg1, arg2, ... }, ...]