这是我参与「第五届青训营」笔记创作活动的第11天。今天继续记录老师讲解的TypeScript基本知识。
一、本堂课重点内容:
TypeScript基本知识。
二、详细知识点介绍:
枚举 enum
在 TypeScript 中,枚举是双向赋值。
enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 0 1 2 3
console.log(Direction[0]); // Up
利用 tsx 命令编译后,上面的 TypeScript 对应的 JavaScript 代码如下:
var Direction1;
(function (Direction1) {
// 赋值语句返回值是被赋值的值,所以下面这句等价于 Direction1["Up"] = 0; Direction1[0] = "Up"
// 这就是双向绑定的含义
Direction1[Direction1["Up"] = 0] ="Up";
Direction1[Direction1["Down"] = 1] = "Down";
Direction1[Direction1["Left"] = 2] = "Left";
Direction1[Direction1["Right"] = 3] ="Right";
})(Direction1 || (Direction1 = {}));
console.log(Direction1.Up);
console.log(Direction1[0]);
从上面的代码中不难看出 TS 中枚举赋值的 “双向赋值” 的含义。也就是说,如果说对枚举赋了初值。那么用[]来索引,[]中的内容也必须是初始赋值。
enum Direction2 {
Up = 10,
Down,
Left,
Right
}
console.log(Direction2.Up, Direction2.Down, Direction2.Left, Direction2.Right); // 10, 11, 12, 13
console.log(Direction2[10]); // Up
console.log(Direction2[0]); // undefined
从上面的代码中可以看出,TS中的枚举的双向绑定的含义。此外,枚举的赋值是只读的,在枚举之外不可以对枚举的属性进行二次赋值。
enum Direction2 {
Up = 10,
Down,
Left,
Right
}
Direction2.Down = 12 // 只读属性
上面代码中对于 Down 的二次赋值会导致编译报错。
除此之外,在枚举前常常加上 const 关键字进行修饰,此时我们称为 常量枚举。TS的常量枚举在JS中会被自动内联替换,因此可以提高性能。
泛型 generics
个人感觉TypeScript的类和Class的类非常类似,不过TS中泛型应用的更加灵活,函数返回值、数组等地方都可以直接使用泛型。
函数中的泛型
观察下面这段代码。
function echo(arg) {
return arg;
}
const str: string = "eeee";
const res = echo(str);
尽管 TS 中的类型推断十分强大,而且上面的代码可以说也是比较清晰了,但是res的类型提示依然是any属性: const res: any。此时,我们可以通过在函数定义时添加泛型来解决这个问题。
function echo<T>(arg: T): T {
return arg;
}
const str: string = "eeee";
const res = echo(str);
此时,res会明确显示出自己是string类型。
不难看出上面的函数引入泛型的方式是在函数名旁边加上<T>关键字,之后就可以在整个函数作用域里面使用泛型 T。甚至可以把泛型 T 作为这个函数的返回值。
此外,泛型可以帮助元组改变内部顺序:
function swap<T, U>(tuple:[T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
const res2 = swap(['1', 2]);// res2显示是[number, string]类型
约束泛型
可以通过 T[] 的方式来表示是一个泛型数组,从而可以使用数组的属性(比如说length)和方法(比如说push、map)。下面这段代码就展现了 T[] 的作用:
function echoWithArr<T>(arg: T[]): T[] {
console.log(arg.length);
return arg;
}
const res1 = echoWithArr(['1', '2', '3']);// res1显示是string[]类型
const res2 = echoWithArr(['1', 2, '3']);// res2显示是(string | number)[]类型
个人感觉这个T[]实际上是一个元组,因为正如上面的 res2 显示单独那样,T[]并不限制所有的成员都是一个类型。
此外,还可以通过泛型继承接口的方式来对泛型进行约束。
interface IWithLength {
length: number,
}
function echoWithLength<T extends IWithLength>(arg: T): T {
// 这样即使arg是泛型,由于继承了接口T,一定有length属性
console.log(arg.length);
return arg;
}
const string = echoWithLength('str'); // 输出3
const object = echoWithLength({length: 17}); // 输出17。这里在写对象的时候,甚至有length的提示补全
const array = echoWithLength([1, 2, 3, 4]); // 输出4
// echoWithLength(12); // 报错
接口和类中的泛型
和 Java 一样,泛型在类和接口中有广泛的应用,可以说撑起了TypeScript的半边天。下面是利用泛型写成的一个简化版本的队列类Queue。
class Queue<T> {
private data: T[] = [];
push(item: T){
return this.data.push(item);
}
pop() {
return this.data.shift();
}
}
const queue = new Queue();
queue.push(1);
queue.push('22');
console.log(queue.pop());
除了上面的基本应用外,还可以如同数组那样用泛型来特化泛型类,比如说可以限制上面定义的队列只能是 number类型。此时,类中所有涉及到泛型的属性和方法都会收到影响:
// 限制只能是 number 类型
const numberQueue = new Queue<number>();
// numberQueue.push('22'); // 报错
下面则是泛型在接口中的应用:
let kp1: KeyPair<number, string> = {key: 1, value: '123'};
let kp2: KeyPair<string, number> = {key: '123', value: 123};
// 官方默认泛型接口Array
let arrTwo: Array<number> = [1, 2, 3, 4, 5];
let arr2: Array<number | string> = [1, 2, 3, 4, 'qe'];
// 使用interface 和 泛型 描述函数类型
interface IPlus<T> {
(a: T, b: T): T
}
function plus(a: number, b: number): number {
return a + b;
}
function connect(a: string, b: string): string {
return a + b;
}
const func1: IPlus<number> = plus;
const func2: IPlus<string> = connect;
三、实践练习例子:
例子已经在文章中举出。
四、课后个人总结:
本章的知识点需要大量的实例和参考资料来辅助理解。
五、引用参考:
我主要是基于老师讲解提供的代码仓库进行理解和分析,并记录了自己的心得。