【学习系列】TypeScript

131 阅读8分钟

TypeScript基础

1、TypeScript的优势

  • 增加了代码的可读性和可维护性,类型系统、编译阶段可以发现大部分问题、增加了编辑器和IDE的功能可以代码补全、提醒等
  • 非常包容,可以直接用js的文件后缀直接改为ts;不显示定义类型也会自动作出推论;编译报错也可以生成js;第三方库可以不是ts
  • 拥有活跃的社区

2、TypeScript的缺点

  • 理解接口、泛型、类、枚举类型
  • 短期增加开发成本
  • 可能和一些库结合不完美

3、ES6新增的基础类型:Symbol(生成独一无二的指)

4、TypeScript的五种原始类型:boolean、number、string、void、null、undefined;

boolean是js的基本类型,Boolean是构造函数

5、任意类型:any(未被指定类型且没有赋值的变量默认为任意指类型)

void和null、undefined的区别在于,null和undefined是所有类型的子集可以复制给其他类型,void不行

let u: undefined = undefined;
let n: null = null;
let v: void;
let num: number = u; // 不会报错
num = n; // 不会报错
num = v; // Type 'void' is not assignable to type 'number'.

6、类型推论:在定义的时候赋值,就会推断为初次赋值的类型;如果定义的时候没有赋值则会推论为any

7、联合类型:取值可以多种类型中的一种

let myFavoriteNumber: string | number;

8、对象类型-接口;

接口一般首字母答谢,建议接口名称加 I 前缀;赋值的时候变量的形状必须和接口的形状保持一致;

接口可选属性、任意属性:

interface Person {
    name: string;
    age?: number;
    [propName: string]: any
}

interface Person { // 这个定义是不行,一旦定义了任意属性,那么确定属性和可选属性的类型必须是它的类型的子集
    name: string;
    age?: number;
    [propName: string]: string
}

只读属性

interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}

// 给person赋值的时候id一定要赋值,而且赋值后不允许在单独赋值
// 以下场景是错误的

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

tom.id = 89757;

9、数组类型

let fibonacci: number[] = [1, 1, 2, 3, 5]; // 数组中必须都是数字

数组泛型

let fibonacci: Array<number> = [1, 1, 2, 3, 5]; 

接口数组,很少使用,常用来表示类数组

interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

类数组

function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}

any数组

let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];

10、函数类型:对函数的输入输出进行约束,输入多余和少于要求的参数是不允许的

function sum(x: number, y: number): number {
    return x + y;
}
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
}; // 这里的=>是函数的定义,和ES6的不一样

可选参数,可选参数必须出现在必需参数后面

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

剩余参数

function push(array, ...items) {
    items.forEach(function(item) {
        array.push(item);
    });
}
let a: any[] = [];
push(a, 1, 2, 3);

重载:允许一个函数接受不同数量或类型的参数,作出不同的处理

11、类型断言:用来手动指定一个值的类型(粗略学习,想不到应用场景)

详情查看:ts.xcatliu.com/basics/type…

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}
function isFish(animal: Cat | Fish) {
    if (typeof (animal as Fish).swim === 'function') {
        return true;
    }
    return false;
}

// 断言只能让编译成功,但是无法避免运行失败,以下就是运行失败的结果
function swim(animal: Cat | Fish) {
    (animal as Fish).swim();
}

const tom: Cat = {
    name: 'Tom',
    run() { console.log('run') }
};
swim(tom); // 这里swim断言了fish,但是传的值是Cat的,导致运行失败

12、声明文件:使用第三哭需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能

例如引用了第三方的jquery

declare var jQuery: (selector: string) => any;
jQuery('#foo'); // 因为jQuery在ts中找不到,需要声明以下变量

详情查看:ts.xcatliu.com/basics/decl…

13、内置对象

ESMAScript的内置对象:Boolean、Error、Date、RegExp

DOM和BOM的内置对象:Document、HTMLElement、Event、NodeList

学习链接:ts.xcatliu.com/

TypeScript进阶

1、类型别名:一般用于联合类型,给联合类型创建一个别名,方便后续使用

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

2、字符串字面量类型:约束取值只能是某几个字符串中的一个

type EventNames = 'click' | 'scroll' | 'mousemove'; // 只能是这三个字符串中的一个,否则会报错
function handleEvent(ele: Element, event: EventNames) {
    // do something
}
handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick'

3、元组:数组只能是相同类型的,元组合并了不同类型的对象

let tom: [string, number] = ['Tom', 25];

越界元素,以上[string,number]要求数组的第一个元素是string、第二个元素是number,超过2个越界的元素以string|number联合类型来约束

4、枚举:enum,枚举的成员被默认赋值从0开始递增的数字,也可以手动赋值

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};

外部枚举:枚举的数据来自外部,在编译的时候删除,只用于编译的检查

declare enum Directions {
    Up,
    Down,
    Left,
    Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

5、类:和java的类的概念类似:继承、多态、修饰符、抽象类、接口等概念

  • 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
  • 对象(Object):类的实例,通过 new 生成
  • 面向对象(OOP)的三大特性:封装、继承、多态
  • 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
  • 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
  • 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat
  • 存取器(getter & setter):用以改变属性的读取和赋值行为
  • 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
  • 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
  • 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口

6、类与接口:接口除了是对对象的形状描述,也是对类的一部分进行抽象

一个类可以继承另一个类,不同类之间有相同点,可以把这些共同的特性抽象盛一个接口,用implement来实现

一个类可以实现多个接口

接口可以继承接口

接口可以继承类

7、接口和抽象类的区别,接口是将共同的特性抽象出来,通过implement来实现的,可以是两个完全不想管的类来实现;抽象类是继承类的基类,通过继承来实现,子类继承抽象类必须实现抽象类定义的抽象方法

同一个类型下的内容可以用抽象类来实现,例如门抽象类,木门、防盗门都可以继承门来实现门的定义;报警功能可以是接口,车、门都可以实现报警的功能

一个类只能继承一个抽象类,但是可以实现多个接口

可以把抽象类看作一个对象,里面有对象的各种属性和方法;而接口可以看作是一个特定的功能,用于实现特定的效果

抽象类

abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

class Cat extends Animal {
  public sayHi() {
    console.log(`Meow, My name is ${this.name}`);
  }
}

let cat = new Cat('Tom');

接口

interface Alarm {
    alert(): void;
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}
class Car implements Alarm {
    alert() {
        console.log('Car alert');
    }
}

8、泛型:在定义函数、接口、类的时候,不预先指定具体的类型,而是在使用的时候再指定类型的一种特性

如果用any,可能输入的时候是string,输出的是number;如果想要限制输入和输出的类型一直,但是又不能确定类型是什么,就可以用泛型来实现

这里的T就是一个泛型,可以看作是一个代号,输入的是什么类型,要求返回的就是什么类型

function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']

多个类型,T、U都是一种类型的代号

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]