四、枚举
枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等
1、简单的例子
枚举使用 enum 关键字来定义:
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射:
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 0); // true console.log(Days["Mon"] === 1); // true console.log(Days["Tue"] === 2); // true console.log(Days["Sat"] === 6); // true console.log(Days[0] === "Sun"); // true console.log(Days[1] === "Mon"); // true console.log(Days[2] === "Tue"); // true console.log(Days[6] === "Sat"); // true
2、手动赋值
我们也可以给枚举项手动赋值:
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 7); // true console.log(Days["Mon"] === 1); // true console.log(Days["Tue"] === 2); // true console.log(Days["Sat"] === 6); // true
上面的例子中,未手动赋值的枚举项会接着上一个枚举项递增。
如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的:
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 3); // true console.log(Days["Wed"] === 3); // true console.log(Days[3] === "Sun"); // false console.log(Days[3] === "Wed"); // true
所以使用的时候需要注意,最好不要出现这种覆盖的情况。
手动赋值的枚举项可以不是数字,此时需要使用类型断言来让 tsc 无视类型检查 (编译出的 js 仍然是可用的):
enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S"};
当然,手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为 1:
enum Days {Sun = 7, Mon = 1.5, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 7); // true console.log(Days["Mon"] === 1.5); // true console.log(Days["Tue"] === 2.5); // true console.log(Days["Sat"] === 6.5); // true
3、常数项和计算所得项
枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。
前面我们所举的例子都是常数项,一个典型的计算所得项的例子:
enum Color {Red, Green, Blue = "blue".length};
上面的例子中,"blue".length 就是一个计算所得项。
上面的例子不会报错,但是如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错:
enum Color {Red = "red".length, Green, Blue};
4、常数枚举
常数枚举是使用 const enum 定义的枚举类型:
const enum Directions { Up, Down, Left, Right } let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。
假如包含了计算成员,则会在编译阶段报错:
const enum Color {Red, Green, Blue = "blue".length}; // index.ts(1,38): error TS2474: In 'const' enum declarations member initializer must be constant expression.
5、外部枚举
外部枚举(Ambient Enums)是使用 declare enum 定义的枚举类型:
declare enum Directions { Up, Down, Left, Right } let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
declare 定义的类型只会用于编译时的检查,编译结果中会被删除。
五、类
1、类的定义
Demo
使用 class 定义类,使用 constructor 定义构造函数。
通过 new 生成新实例的时候,会自动调用构造函数。
class Animal { constructor(name) { this.name = name; } sayHi() { return `My name is ${this.name}`; } } let a = new Animal('Jack'); console.log(a.sayHi()); // My name is Jack
2、类的继承
使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。
class Cat extends Animal { constructor(name) { super(name); // 调用父类的 constructor(name) console.log(this.name); } sayHi() { return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi() } } let c = new Cat('Tom'); // Tom console.log(c.sayHi()); // Meow, My name is Tom
3、访问修饰符
public private 和 protected
· public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
· private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
· protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
使用 private 修饰的属性或方法,在子类中也是不允许访问的:
class Animal { private name; public constructor(name) { this.name = name; } } class Cat extends Animal { constructor(name) { super(name); console.log(this.name); } } } 而如果是用 protected 修饰,则允许在子类中访问: class Animal { protected name; public constructor(name) { this.name = name; } } class Cat extends Animal { constructor(name) { super(name); console.log(this.name); } }
4、静态属性和静态方法
可以使用 static 定义一个静态属性:
class Animal { static num = 42; constructor() { // ... } } console.log(Animal.num); // 42
使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:
class Animal { static isAnimal(a) { return a instanceof Animal; } } let a = new Animal('Jack'); Animal.isAnimal(a); // true a.isAnimal(a); // TypeError: a.isAnimal is not a function
5、抽象类和多态
- 抽象类是提供其他类继承的基类(父类)不能直接被实例化
- 抽象方法只能包含在抽象类中,抽象类中可以包含抽象方法和非抽象方法
- 子类继承抽象类,实现抽象方法
abstract 用于定义抽象类和其中的抽象方法。什么是抽象类?首先,抽象类是不允许被实例化的:
abstract class Animal { public name; public constructor(name) { this.name = name; } public abstract sayHi(); } let a = new Animal('Jack');
由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat
交流
我是老礼,公众号「进军全栈攻城狮」作者 ,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!