在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。 枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。—— 维基百科
在实际开发过程中我们非常容易接触到枚举类型,但是又因为 php 原生对枚举的支持不是太好,所以很多时候 开发人员并没有重视枚举的使用,而是使用全局常量或者类常量代替,而这两个数据原则上还是 字符串
并不能用来做类型判断。
等等 ,很多时候我们都会用简单的 1/2/3/4 或者0/1 这样的方式去代表,然后在文档或者注释中规定这些东西。
更高级一点儿的就是定义成常量,然后方便统一存取,但是常量的值还是是字符串,无法进行类型判断。
这里就要看一下 PHP 对枚举的支持,虽然 PHP 对枚举没有完美的支持,但是在 SPL 中还是有一个基础的枚举类
SplEnum extends SplType {
/ Constants /
const NULL __default = NULL ;
/ 方法 /
public getConstList ([ bool $include_default = FALSE ] ) : array
/ 继承的方法 /
SplType::__construct ( [mixed $initial_value [, bool $strict ]] )
}
但是!这个需要额外的安装 PECL 用 PECL 安装 Spl_Types,无意间增加了使用成本,那有没有其他解决方案?答案是肯定的。 直接手写一个。
首先定一个枚举
class Enum
{
// 默认值
const __default = self::WAIT_PAYMENT;
// 待付款
const WAIT_PAYMENT = 0;
// 待发货
const WAIT_SHIP = 1;
// 待收货
const WAIT_RECEIPT = 2;
// 待评价
const WAIT_COMMENT = 3;
}
这样似乎就完成了,我们直接使用 Enum::WAIT_PAYMENT 就可以拿到里面的值了,但是传参的地方我们并没法校验他。
function setStatus(Enum $status){
// TODO
}
setStatus(Enum::WAIT_PAYMENT);
// Error 显然这是不行的 因为上面常量的值时一个 int 并不是 Enum 类型。
这里我们就需要用到 PHP 面向对象中的一个魔术方法 __toString()
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
现在我们来完善一下这个方法。
class OrderStatus extends Enum
{
// 默认值
const __default = self::WAIT_PAYMENT;
// 待付款
const WAIT_PAYMENT = 0;
// 待发货
const WAIT_SHIP = 1;
// 待收货
const WAIT_RECEIPT = 2;
// 待评价
const WAIT_COMMENT = 3;
public function __toString()
{
return '233';
}
}
// object
echo gettype($orderStatus) . PHP_EOL;
// boolean true
var_dump($orderStatus instanceof Enum);
// 233
echo $orderStatus;
这里似乎实现了一部分,那我们应该怎么样让他做的更好?再来改造一下。
class OrderStatus extends Enum
{
// 默认值
const __default = self::WAIT_PAYMENT;
// 待付款
const WAIT_PAYMENT = 0;
// 待发货
const WAIT_SHIP = 1;
// 待收货
const WAIT_RECEIPT = 2;
// 待评价
const WAIT_COMMENT = 3;
/**
* @var string
*/
protected $value;
public function __construct($value = null)
{
$this->value = is_null($value) ? self::__default : $value;
}
public function __toString()
{
return (string)$this->value;
}
}
// 1️⃣
$orderStatus = new OrderStatus(OrderStatus::WAIT_SHIP);
// object
echo gettype($orderStatus) . PHP_EOL;
// boolean true
var_dump($orderStatus instanceof Enum);
// 1
echo $orderStatus . PHP_EOL;
// 2️⃣
$orderStatus = new OrderStatus();
// object
echo gettype($orderStatus) . PHP_EOL;
// boolean true
var_dump($orderStatus instanceof Enum);
// 0
echo $orderStatus;
// 3️⃣
$orderStatus = new OrderStatus('意外的参数');
// object
echo gettype($orderStatus) . PHP_EOL;
// boolean true
var_dump($orderStatus instanceof Enum);
// 意外的参数
echo $orderStatus;
在这一次,我们加入了 构造函数 并且允许他传入一个可选的值,然后来作为 __toString 方法的输出值,这次看起来不错,功能都已经实现了,如果传入的参数否和我们的预期的话。但是 万一不符合呢?看看,第 3️⃣ 个那里,就已经成了意外了,哪还有没有办法补救?答案当然是 有的 ,在这里我们会用到 PHP 另一个好东西 反射类 ,当然这个不是 PHP 特有的,其他语言也有。 当然,除了反射,我们还会用到另外一个东西 方法重载 里面的 __callStatic 方法。
在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。$name 参数是要调用的方法名称。$arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。
继续改造。
class Enum
{
const __default = null;
/**
* @var string
*/
protected static $value;
// 注意这里 将构造函数的 修饰符改成了 受保护的 即 外部无法直接 new
protected function __construct($value = null)
{
// 很常规
self::$value = is_null($value) ? static::__default : $value;
}
/**
* @param $name
* @param $arguments
* @return mixed
* @throws ReflectionException
*/
public static function __callStatic($name, $arguments)
{
// 实例化一个反射类 static::class 表示调用者
$reflectionClass = new ReflectionClass(static::class);
// 这里我们要有一个约定, 就是类常量成员的名字必须的大写。
// 这里就是取出来调用的静态方法名对应的常量值 虽然这里有个 getValue 方法
// 但是因为其返回值不可靠 我们就依赖于他原本的隐式的 __toString 方法来帮我们输出字符串即可。
$constant = $reflectionClass->getConstant(strtoupper($name));
// 获取调用者的 构造方法
$construct = $reflectionClass->getConstructor();
// 设置成可访问 因为我们把修饰符设置成了受保护的 这里需要访问到,所以就需要设置成可访问的。
$construct->setAccessible(true);
// 因为现在类已经是可以访问的了所以我们直接实例化即可,实例化之后 PHP 会自动调用 __toString 方法 使得返回预期的值。
$static = new static($constant);
return $static;
}
public function __toString()
{
return (string)self::$value;
}
}
class OrderStatus extends Enum
{
// 默认值
const __default = self::WAIT_PAYMENT;
// 待付款
const WAIT_PAYMENT = 0;
// 待发货
const WAIT_SHIP = 1;
// 待收货
const WAIT_RECEIPT = 2;
// 待评价
const WAIT_COMMENT = 3;
}
$WAIT_SHIP = OrderStatus::WAIT_SHIP();
var_dump($WAIT_SHIP . '');
var_dump($WAIT_SHIP instanceof Enum);
到这里 一个简单的枚举类就完成了。
那如果我们还有其他需求、比如 判断一个值是不是在枚举范围内?获取所有的枚举值?获取所有的枚举键,判断枚举键是否有效?自动格式化「因为 __toString 方法只允许返回字符串 ,但是有的时候我们强制需要整形、bool 等类型」
class Enum
{
const __default = null;
/**
* @var string
*/
protected static $value;
/**
* @var ReflectionClass
*/
protected static $reflectionClass;
// 注意这里 将构造函数的 修饰符改成了 受保护的 即 外部无法直接 new
protected function __construct($value = null)
{
// 很常规
self::$value = is_null($value) ? static::__default : $value;
}
/**
* @param $name
* @param $arguments
* @return mixed
*/
public static function __callStatic($name, $arguments)
{
// 实例化一个反射类 static::class 表示调用者
$reflectionClass = self::getReflectionClass();
// 这里我们要有一个约定, 就是类常量成员的名字必须的大写。
// 这里就是取出来调用的静态方法名对应的常量值 虽然这里有个 getValue 方法
// 但是因为其返回值不可靠 我们就依赖于他原本的隐式的 __toString 方法来帮我们输出字符串即可。
$constant = $reflectionClass->getConstant(strtoupper($name));
// 获取调用者的 构造方法
$construct = $reflectionClass->getConstructor();
// 设置成可访问 因为我们把修饰符设置成了受保护的 这里需要访问到,所以就需要设置成可访问的。
$construct->setAccessible(true);
// 因为现在类已经是可以访问的了所以我们直接实例化即可,实例化之后 PHP 会自动调用 __toString 方法 使得返回预期的值。
$static = new static($constant);
return $static;
}
/**
* 实例化一个反射类
* @return ReflectionClass
* @throws ReflectionException
*/
protected static function getReflectionClass()
{
if (!self::$reflectionClass instanceof ReflectionClass) {
self::$reflectionClass = new ReflectionClass(static::class);
}
return self::$reflectionClass;
}
/**
* @return string
*/
public function __toString()
{
return (string)self::$value;
}
/**
* 判断一个值是否有效 即是否为枚举成员的值
* @param $val
* @return bool
* @throws ReflectionException
*/
public static function isValid($val)
{
return in_array($val, self::toArray());
}
/**
* 转换枚举成员为键值对输出
* @return array
* @throws ReflectionException
*/
public static function toArray()
{
return self::getEnumMembers();
}
/**
* 获取枚举的常量成员数组
* @return array
* @throws ReflectionException
*/
public static function getEnumMembers()
{
return self::getReflectionClass()
->getConstants();
}
/**
* 获取枚举成员值数组
* @return array
* @throws ReflectionException
*/
public static function values()
{
return array_values(self::toArray());
}
/**
* 获取枚举成员键数组
* @return array
* @throws ReflectionException
*/
public static function keys()
{
return array_keys(self::getEnumMembers());
}
/**
* 判断 Key 是否有效 即存在
* @param $key
* @return bool
* @throws ReflectionException
*/
public static function isKey($key)
{
return in_array($key, array_keys(self::getEnumMembers()));
}
/**
* 根据 Key 去获取枚举成员值
* @param $key
* @return static
*/
public static function getKey($key)
{
return self::$key();
}
/**
* 格式枚举结果类型
* @param null|bool|int $type 当此处的值时什么类时 格式化输出的即为此类型
* @return bool|int|string|null
*/
public function format($type = null)
{
switch (true) {
// 当为纯数字 或者类型处传入的为 int 值时 转为 int
case ctype_digit(self::$value) || is_int($type):
return (int)self::$value;
break;
// 当 type 传入 true 时 返回 bool 类型
case $type === true:
return (bool)filter_var(self::$value, FILTER_VALIDATE_BOOLEAN);
break;
default:
return self::$value;
break;
}
}
}
class OrderStatus extends Enum
{
// 默认值
const __default = self::WAIT_PAYMENT;
// 待付款
const WAIT_PAYMENT = 0;
// 待发货
const WAIT_SHIP = 1;
// 待收货
const WAIT_RECEIPT = 2;
// 待评价
const WAIT_COMMENT = 3;
}
$WAIT_SHIP = OrderStatus::WAIT_SHIP();
// 直接输出是字符串
echo $WAIT_SHIP;
// 判断类型是否存在
var_dump($WAIT_SHIP instanceof OrderStatus);
// 格式化输出一下 是要 字符串 、还是 bool 还是整形
// 自动
var_dump($WAIT_SHIP->format());
// 整形
var_dump($WAIT_SHIP->format(1));
// bool
var_dump($WAIT_SHIP->format(true));
// 判断这个值是否有效的枚举值
var_dump(OrderStatus::isValid(2));
// 判断这个值是否有效的枚举值
var_dump(OrderStatus::isValid(8));
// 获取所有枚举成员的 Key
var_dump(OrderStatus::keys());
// 获取所有枚举成员的值
var_dump(OrderStatus::values());
// 获取枚举成员的键值对
var_dump(OrderStatus::toArray());
// 判断枚举 Key 是否有效
var_dump(OrderStatus::isKey('WAIT_PAYMENT'));
// 判断枚举 Key 是否有效
var_dump(OrderStatus::isKey('WAIT_PAYMENT_TMP'));
// 根据 Key 取去 值 注意 这里取出来的已经不带有类型了
// 更加建议直接使用 取类常量的方式去取 或者在高版本的 直接使用类常量修饰符
// 将类常量不可见最佳,但是需要额外处理了
var_dump(OrderStatus::getKey('WAIT_PAYMENT')
->format(1));
截至目前 一个完整的枚举就完成了~
用for...in循环和Object.keys方法都可以获取对象的属性,那么它们有什么区别呢?getOwnPropertyNames方法输出的结果中还包含了对象的不可枚举属性,可以通过Object.propertyIsEnumerable来判断属性是否可枚举从而对结果进行过滤
题目:在1,2,3,4,5 五个数中,我们随机选取 3个数。问有多少种取法?并且把每种取出数的方法列举出来。乍看这道题,其实感觉没什么难度。三个for循环不就解决问题了。
数字类型枚举:常规枚举的值都是数字类型,因此被称为数字类型枚举;改变与数字枚举关联的数字:默认情况下,第一个枚举值是 0,后续的值会递增。
枚举是对JavaScript标准数据类型集的扩充,常被用来限定在一定范围内取值的场景。在TypeScript中支持数字和字符串的枚举。我们可以用enum来实现。字符串枚举中没有自增的特性,我们在初始化的时候必须给每一个成员都设字符串。
在当前的javascript中,并没有枚举这个概念,在某些场景中使用枚举更能保证数据的正确性,减少数据校验过程,下面就介绍一下javascript如何模拟实现枚举效果。
使用枚举类型可以允许我们定义一些带名字的常量,也可以清晰地表达意图或创建一组有区别的用例。在 TypeScript 中,支持数字的和基于字符串的枚举。
枚举是受 TypeScript 支持的数据类型。枚举允许您定义一组命名常量。使用它们可以更轻松地记录意图或创建一组不同的案例。枚举大多数用于面向对象的编程语言(如 Java 和 C#)中
枚举的好处是,我们可以定义一些带名字的常量,而且可以清晰地表达意图或创建一组有区别的用例,TS支持数字的和基于字符串的枚举,首先来看数字枚举
假设有这样一个场景,我们需要统计员工的技术栈,目前我们需要标记的技术有 CSS、JavaScript、HTML、WebGL。然后我可以这样写枚举:
枚举 Enum是在众多语言中都有的一种数据类型,JavaScript中还没有(TypeScript有)。用来表示一些特定类别的常量数据,如性别、学历、方向、账户状态等,项目开发中是很常用的。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!