TypeScript

114 阅读35分钟

安装

使用vscode作为编码工具
全局安装:npm install -g typescript<br>
查看版本号:tsc --version

创建项目

在项目根目录中创建tsconfig.json(typescript的配置文件)
创建项目代码编写文件,对应的文件后缀为.ts
使用快捷键“Ctrl + Shift + B”或从菜单栏里选择“Terminal→Run BuildTask”来打开并运行构建任务面板,然后再选择“tsc: build - tsconfig.json”来编译TypeScript程序

原始数据类型:

booleanstringnumberbigintsymbolundefinednullvoid、枚举类型、字面量类型

boolean类型

const yes: boolean = true;
const no: boolean = false;

string类型

const foo: string = 'foo';
const bar: string = `bar,${foo}`;

number类型

const bin: number = 0b1010; //二进制
const oct: number = 0o744; //八进制
const integer: number = 10; // 十进制
const float: number = 3.14; // 十进制
const hex: number = 0xffffff; //十六进制

bigint类型(表示任意精度的整数,但也仅能表示整数。)

const bin: bigint = 0b1010n;
const oct: bigint = 0o744n;
const integer: bigint = 10n;
const hex: bigint = 0xffffffn;

symbol类型(symbol类型的值只能通过“Symbol()”和“Symbol.for()”函数来创建或直接引用某个“Well-Known Symbol”值)

const s0: symbol = Symbol(); 
const s1: symbol = Symbol.for('foo');
const s2: symbol = Symbol.hasInstance;
const s3: symbol = s0;
unique symbol类型(将一个Symbol值视作表示固定值的字面量。主要用途是用作接口、类等类型中的可计算属性名,只允许使用const声明或readonly属性声明来定义“unique symbol”类型的值。只允许使用“Symbol()”函数或“Symbol.for()”方法的返回值进行初始化)
const s0: unique symbol = Symbol();
const s1: unique symbol = Symbol.for('s1');
// 必须使用const声明
const a: unique symbol = Symbol();
interface WithUniqueSymbol {
    // 必须使用readonly修饰符
    readonly b: unique symbol;
}
class C {
    // 必须使用static和readonly修饰符
    static readonly c: unique symbol = Symbol();
}
如果程序中未使用类型注解来明确定义是symbol类型还是“unique symbol”类型,那么TypeScript会自动地推断类型
    // a和b均为'symbol'类型,因为没有使用const声明 
    let a = Symbol();
    let b = Symbol.for('');
    // c和d均为'unique symbol'类型
    const c = Symbol();
    const d = Symbol.for('');
    // e和f均为'symbol'类型,没有使用Symbol()或Symbol.for()初始化
    const e = a;
    const f = a;
每一个“unique symbol”类型都是一种独立的类型。在不同的“unique symbol”类型之间不允许相互赋值;在比较两个“unique symbol”类型的值时,也将永远返回false。
由于“unique symbol”类型是 symbol类型的子类型,因此可以将“unique symbol”类型的值赋值给symbol类型

Nullable类型

undefined类型
    const foo: undefined = undefined;
null类型
    const foo: null = null;
没有启用“--strictNullChecks”编译选项时,允许将undefined值和null值赋值给string类型等其他类型

void类型(void类型表示某个值不存在,该类型用作函数的返回值类型,在其他地方使用void类型是无意义的)

function log(message: string): void {
    console.log(message);
}
当启用了“--strictNullChecks”编译选项时,只允许将undefined值赋值给void类型。没有启用“--strictNullChecks”编译选项,那么允许将undefined值和null值赋值给void类型
/**
 * --strictNullChecks=true
*/
// 正确
function foo(): void {
    return undefined;
}
// 编译错误!类型 'null' 不能赋值给类型 'void'
function bar(): void {
    return null;
}

枚举类型(通过enum关键字来定义,枚举类型由零个或多个枚举成员构成,每个枚举成员都是一个命名的常量)

enum Season {
    Spring,
    Summer,
    Fall,
    Winter,
}
按照枚举成员的类型可以将枚举类型划分为以下三类:数值型枚举、字符串枚举、异构型枚举

    数值型枚举(number类型的子类型,它由一组命名的数值常量构成)
    //如果在定义枚举时没有设置枚举成员的值,那么TypeScript将自动计算枚举成员的值。根据TypeScript语言的规则,第一个枚举成员的值为0,其后每个枚举成员的值等于前一个枚举成员的值加1
    enum Direction {
        Up, // 0
        Down, // 1
        Left, // 2
        Right, // 3
    }
    //可以为一个或多个枚举成员设置初始值。对于未指定初始值的枚举成员,其值为前一个枚举成员的值加1
    enum Direction {
        Up = 1, // 1
        Down, // 2
        Left = 10, // 10
        Right, // 11
    }
    //数值型枚举是number类型的子类型,因此允许将数值型枚举类型赋值给number类型
    enum Direction {
        Up,
        Down,
        Left,
        Right
    }
    const direction: number = Direction.Up;
    //number类型也能够赋值给枚举类型,即使number类型的值不在枚举成员值的列表中也不会产生错误
    enum Direction {
        Up,
        Down,
        Left,
        Right,
    } 
    const d1: Direction = 0; // Direction.Up
    const d2: Direction = 10; // 不会产生错误
    
    字符串枚举(字符串枚举中,枚举成员的值为字符串。字符串枚举成员必须使用字符串字面量或另一个字符串枚举成员来初始化。字符串枚举成员没有自增长的行为)
    enum Direction {
        Up = 'UP',
        Down = 'DOWN',
        Left = 'LEFT',
        Right = 'RIGHT',
        U = Up,
        D = Down,
        L = Left,
        R = Right,
    }
    //允许将字符串枚举类型赋值给string类型
    enum Direction {
        Up = 'UP',
        Down = 'DOWN',
        Left = 'LEFT',
        Right = 'RIGHT',
    }
    const direction: string = Direction.Up;
    //不允许将string类型赋值给字符串枚举类型,这一点与数值型枚举是不同的
    
    异构型枚举(在一个枚举中同时定义数值型枚举成员和字符串枚举成员,我们将这种类型的枚举称作异构型枚举)
    enum Color {
        Black = 0,
        White = 'White',
    }
    //在定义异构型枚举时,不允许使用计算的值作为枚举成员的初始值
    enum Color {
        Black = 0 + 0,
        //      ~~~~~
        // 编译错误!在带有字符串成员的枚举中不允许使用计算值
        White = 'White',
    }
    //必须为紧跟在字符串枚举成员之后的数值型枚举成员指定一个初始值。下例中,ColorA枚举的定义是正确的,但是ColorB枚举的定义是错误的,必须为数值型枚举成员Black指定一个初始值。
    enum ColorA {
        Black,
        White = 'White',
    }
    enum ColorB {
        White = 'White',
        Black,
     // ~~~~~
       // 编译错误!枚举成员必须有一个初始值
    }
    
    枚举成员映射(不论是哪种类型的枚举,都可以通过枚举成员名去访问枚举成员值)
    enum Bool {
        False = 0,
        True = 1,
    }
    Bool.False; // 0
    Bool.True; // 1
    //对于数值型枚举,不但可以通过枚举成员名来获取枚举成员值,也可以反过来通过枚举成员值去获取枚举成员名
    enum Bool {
        False = 0,
        True = 1,
    }
    Bool[Bool.False]; // 'False'
    Bool[Bool.True]; // 'True'

boolean类型

let c: boolean;
联合类型:
type BooleanAlias = true | false;

string类型

let c: string;

any类型

let x: any;
所有类型都是any类型的子类型。我们可以将任何类型的值赋值给any类型。
TypeScript允许将any类型赋值给任何其他类型。

unknown类型

let x: unknown;
根据顶端类型的性质,任何其他类型都能够赋值给unknown类型,该行为与any类型是一致的
unknown类型是比any类型更安全的顶端类型,因为unknown类型只允许赋值给any类型和unknown类型,而不允许赋值给任何其他类型,该行为与any类型是不同的。

never类型

function f(): never {
    throw new Error();
}
let x: never;
根据尾端类型的定义,never类型是所有其他类型的子类型。所以,never类型允许赋值给任何类型,尽管并不存在never类型的值。
正如尾端类型其名,它在类型系统中位于类型结构的最底层,没有类型是never类型的子类型。因此,除never类型自身外,所有其他类型都不能够赋值给never类型。
any类型也不能够赋值给never类型

应用场景
场景一 never类型可以作为函数的返回值类型,它表示该函数无法返回一个值。如果函数体中没有使用return语句,那么在正常执行完函数代码后会返回一个undefined值。在这种情况下,函数的返回值类型是void类型而不是never类型。只有在函数根本无法返回一个值的时候,函数的返回值类型才是never类型。一种情况就是函数中抛出了异常,这会导致函数终止执行,从而不会返回任何值。在这种情况下,函数的返回值类型为never类型。
场景二 在“条件类型”中常使用never类型来帮助完成一些类型运算。
场景三  在TypeScript编译器执行类型推断操作时,如果发现已经没有可用的类型,那么推断结果为never类型
function getLength(message: string) {
    if (typeof message === 'string') {
        message; // string
    } else {
        message; // never
    }
}

顶端类型

TypeScript中有以下两种顶端类型:anyunknown

尾端类型

TypeScript中只存在一种尾端类型,即never类型。
尾端类型(Bottom Type)是所有其他类型的子类型。

数组类型

简便数组类型表示法:TElement[]//TElement代表数组元素的类型,方括号“[]”代表数组类型。
const digits: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const red: (string | number)[] = ['f', f, 0, 0, 0, 0];

泛型数组类型表示法:Array<TElement>//“<TElement>”是类型参数的语法,其中TElement代表数组元素的类型。
const digits: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const red: Array<string | number> = ['f', 'f', 0, 0, 0, 0];

只读数组:
const red: ReadonlyArray<number> = [255, 0, 0];
const red: readonly number[] = [255, 0, 0];
const red: Readonly<number[]> = [255, 0, 0];
“Readonly<T>”是TypeScript提供的一个内置工具类型,用于定义只读对象类型。该工具类型能够将类型参数T的所有属性转换为只读属性,它的定义如下所示:
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

注:TypeScript的类型系统无法推断出是否存在数组访问越界的情况,因此即使访问了不存在的数组元素,还是会得到声明的数组元素类型。在进行赋值操作时,允许将常规数组类型赋值给只读数组类型,但是不允许将只读数组类型赋值给常规数组类型。

元组类型

在TypeScript中,元组类型是数组类型的子类型。元组是长度固定的数组,并且元组中每个元素都有确定的类型。
[T0, T1, ..., Tn]//该语法中的T0、T1和Tn表示元组中元素的类型,针对元组中每一个位置上的元素都需要定义其数据类型。
const score: [string, number] = ['math', 100];

只读元组:
const point: readonly [number, number] = [0, 0];
const point: Readonly<[number, number]> = [0, 0];
   
元组类型中可选元素:
[T0?, T1?, ..., Tn?]
如果元组中同时存在可选元素和必选元素,那么可选元素必须位于必选元素之后
[T0, T1?, ..., Tn?]

元组类型中的剩余元素:
在定义元组类型时,可以将最后一个元素定义为剩余元素。
[...T[]]
const tuple: [number, ...string[]] = [0, 'a', 'b'];

元组的长度:
function f(point: [number, number]) {
    // 编译器推断出length的类型为数字字面量类型2
    const length = point.length;
    if (length === 3) { 
        // 编译错误!条件表达式永远为 false
        // ...
    }
}
元组拥有一个固定的长度。TypeScript编译器能够识别出元组的长度并充分利用该信息来进行类型检查。

当元组中包含了可选元素时,元组的长度不再是一个固定值。
const tuple: [boolean, string?, number?] = [true, 'yes', 1];
let len = tuple.length; // 1 | 2 | 3
len = 1;
len = 2;
len = 3;
len = 4; // 编译错误!类型'4'不能赋值给类型'1 | 2 | 3'

注:在给只读元组类型赋值时,允许将常规元组类型赋值给只读元组类型,但是不允许将只读元组类型赋值给常规元组类型。当访问数组中不存在的元素时不会产生编译错误。与之不同的是,当访问元组中不存在的元素时会产生编译错误。可以使用访问数组元素的方法去访问元组中的元素。

object类型

object类型仅能够赋值给以下三种类型:
顶端类型anyunknownObject类型。
空对象类型字面量“{}”

函数类型

function add(x: number, y: number) {
    return x + y;
}
可选参数
function add(x: number, y?: number, z?: number) {
    return x + (y ?? 0) + (z ?? 0);
}
函数的可选参数必须位于函数参数列表的末尾位置。在可选参数之后不允许再出现必选参数,否则将产生编译错误。


function f(...args: number[]) {}//在调用定义了剩余参数的函数时,剩余参数可以接受零个或多个实际参数。
带有剩余元素的元组类型
function f0(...args: [boolean, ...string[]]) {}
// 等同于:
function f1(args_0: boolean, ...args_1: string[]) {}

function f(...args: [boolean, number]) {}//元组类型的剩余参数

带有可选元素的元组类型
function f0(...args: [boolean, string?]) {}
// 等同于:
function f1(args_0: boolean, args_1?: string) {}

解构参数类型
function f0([x, y]) {}
f0([0, 1]);
function f1({ x, y }) {}
f1({ x: 0, y: 1 });
使用类型注解为解构参数添加类型信息
function f0([x, y]: [number, number]) {}
f0([0, 1]);
function f1({ x, y }: { x: number; y: number }) {}
f1({ x: 0, y: 1 });

返回值类型
在函数形式参数列表之后,可以使用类型注解为函数添加返回值类型
function add(x: number, y: number): number {
                                  //~~~~~~
                                 // 函数返回值类型
    return x + y;
}
在绝大多数情况下,TypeScript能够根据函数体内的return语句等自动推断出返回值类型,因此我们也可以省略返回值类型。
function add(x: number, y: number) {
    return x + y;
}
void类型
如果一个函数的返回值类型为void,那么该函数只能返回undefined值。这意味着函数明确地返回了一个undefined值,或者函数没有调用return语句,在这种情况下函数默认返回undefined值。如果没有启用“--strictNullChecks”编译选项,那么void返回值类型也允许返回null值。

字面量定义函数
(ParameterList) => Type
let f: (x: number) => number;
f = function (y: number): number {
    return y;
};

调用签名
//调用签名的语法,ParameterList表示函数形式参数列表类型,Type表示函数返回值类型,两者都是可选的。
{
    (ParameterList): Type
}

let add: { (x: number, y: number): number };
add = function (x: number, y: number): number {
    return x + y;
};

{ ( ParameterList ): Type }
// 简写为:
( ParameterList ) => Type

//若使用函数类型字面量,就无法准确地描述函数f的类型。
function f(x: number) {
    console.log(x);
}
f.version = '1.0';
let foo: (x: number) => void = f;
const version = foo.version;
//                  ~~~~~~~
//                  编译错误:'(x: number) => void' 类型
//                  上不存在 'version' 属性

//可以使用带有调用签名的对象类型字面量来准确地描述函数f的类型。
function f(x: number) {
    console.log(x);
}
f.version = '1.0';
let foo: { (x: number): void; version: string } = f;
const version = foo.version; // string类型

构造函数类型字面量
new ( ParameterList ) => Type
let ErrorConstructor: new (message?: string) => Error;

构造签名
{ new (ParameterList): Type }//new是运算符关键字,ParameterList表示构造函数形式参数列表类型,Type表示构造函数返回值类型,两者都是可选的。

{ new ( ParameterList ): Type } 
// 简写为:
new ( ParameterList ) => Type

let Dog: { new (name: string): object };
Dog = class {
    private name: string;
    constructor(name: string) {
        this.name = name;
    }
};
let dog = new Dog('huahua');

调用签名与构造签名
declare const F: {
    new (x: number): Number; // <- 构造签名
    (x: number): number; // <- 调用签名
};
// 作为普通函数调用
const a: number = F(1);
// 作为构造函数调用
const b: Number = new F(1);

重载函数
不带有函数体的函数声明语句叫作函数重载。
function add(x: number, y: number): number;

在各个函数重载语句之间以及函数重载语句与函数实现语句之间不允许出现任何其他语句,否则将产生编译错误。
function add(x: number, y: number): number;
const a = 0; // <-- 编译错误
function add(x: any[], y: any[]): any[];
const b = 0; // <-- 编译错误
function add(x: number | any[], y: number | any[]): any {
    // 省略了实现代码 11 
}

函数中this值的类型
默认情况下,编译器会将函数中的this值设置为any类型,并允许程序在this值上执行任意的操作。因为,编译器不会对any类型进行类型检查。

TypeScript支持在函数形式参数列表中定义一个特殊的this参数来描述该函数中this值的类型。示例如下:
function foo(this: { name: string }) {
    this.name = 'Patrick';
    this.name = 0;
 // ~~~~~~~~~
 // 编译错误!类型 0 不能赋值给类型 'string'
}

如果我们想要定义一个纯函数或者是不想让函数代码依赖于this的值,那么在这种情况下可以明确地将this参数定义为void类型。这样做之后,在函数体中就不允许读写this的属性和方法。示例如下:
function add(this: void, x: number, y: number) {
    this.name = 'Patrick';
 // ~~~~
 // 编译错误:属性 'name' 不存在于类型 'void'
}

接口

interface InterfaceName { TypeMember; TypeMember; ... }
//interface是关键字,InterfaceName表示接口名,它必须是合法的标识符,TypeMember表示接口的类型成员,所有类型成员都置于一对大括号“{}”之内。接口名的首字母需要大写。

接口类型的类型成员也分为以下五类:
属性签名
调用签名
构造签名
方法签名
索引签名

属性签名:
PropertyName: Type;

调用签名:
(ParameterList): Type
//ParameterList表示函数形式参数列表类型;Type表示函数返回值类型,两者都是可选的。
例:interface ErrorConstructor {
        (message?: string): Error;
    }

构造签名:
new (ParameterList): Type
//new是运算符关键字;ParameterList表示构造函数形式参数列表类型;Type表示构造函数返回值类型,两者都是可选的。
例:interface ErrorConstructor {
        new (message?: string): Error;
    }
    
方法签名:
PropertyName(ParameterList): Type  等同  PropertyName: { (ParameterList): Type } 等同 PropertyName: (ParameterList) => Type
PropertyName表示对象属性名,可以为标识符、字符串、数字和可计算属性名;ParameterList表示可选的方法形式参数列表类型;Type表示可选的方法返回值类型。
例:interface Document {
        getElementById(elementId: string): HTMLElement | null;
    }

索引签名:
    字符串索引签名
    [IndexName: string]: Type
    //IndexName表示索引名,它可以为任意合法的标识符。
    例:interface A {
            [prop: string]: number;
        }
        interface A {
            [prop: string]: number;
            a: number;
            b: 0;
            c: 1 | 2;
        }
        interface B {
            [prop: string]: number;
            a: boolean; // 编译错误
            b: () => number; // 编译错误
            c(): number; // 编译错误
        }
        //字符串索引签名中定义的索引值类型依旧为number类型。属性a的类型为boolean类型,它不能赋值给number类型,因此产生编译错误。属性b和方法c的类型均为函数类型,不能赋值给number类型,因此也会产生编译错误。
        
    数值索引签名
    [IndexName: number]: Type
    //IndexName表示索引名,在数值索引签名中,索引名的类型必须为number类型;Type表示索引值的类型,它可以为任意类型。
    //一个接口中最多只能定义一个数值索引签名。数值索引签名约束了数值属性名对应的属性值的类型。
    interface A {
        [prop: number]: string;
    }
    例:interface A {
        [prop: number]: string;
    }
    const obj: A = ['a', 'b', 'c'];
    obj[0]; // string
    
可选属性与方法
PropertyName?: Type
PropertyName?(ParameterList): Type
例:
interface Foo {
    x?: string;
    y?(): number;
}
const a: Foo = {}
const b: Foo = { x: 'hi' }
const c: Foo = { y() { return 0; } }
const d: Foo = { x: 'hi', y() { return 0; } }

如果接口中定义了重载方法,那么所有重载方法签名必须同时为必选的或者可选的
例:
// 正确
interface Foo {
    a(): void;
    a(x: boolean): boolean;
    b?(): void;
    b?(x: boolean): boolean;
}
interface Bar {
    a(): void;
    a?(x: boolean): boolean;
    // ~
    // 编译错误:重载签名必须全部为必选的或可选的
}

只读属性与方法
//在接口声明中,使用readonly修饰符能够定义只读属性。readonly修饰符只允许在属性签名和索引签名中使用
readonly PropertyName: Type;
readonly [IndexName: string]: Type
readonly [IndexName: number]: Type

例:
interface A {
    readonly a: string;
    readonly [prop: string]: string;
    readonly [prop: number]: string;
}
//若接口中定义了只读的索引签名,那么接口类型中的所有属性都是只读属性。
例:
interface A {
    readonly [prop: string]: number;
    x: number;
}
const a: A = { x: 0 };
a.x = 1; //正确
a.x = 1; // 编译错误!不允许修改属性值

接口的继承
interface Style {
    color: string;
}
interface Shape {
    name: string;
}
interface Circle extends Style, Shape {
    radius: number;
}
const c: Circle = {
    color: 'red',
    name: 'circle',
    radius: 1
};
//如果子接口与父接口之间存在同名的类型成员,那么子接口中的类型成员具有更高的优先级。

//如果仅是多个父接口之间存在同名的类型成员,而子接口本身没有该同名类型成员,那么父接口中同名类型成员的类型必须是完全相同的,否则将产生编译错误。
例:
interface Style {
    draw(): { color: string };
}
interface Shape {
    draw(): { x: number; y: number };
}
interface Circle extends Style, Shape {}
//        ~~~~~~
//        编译错误
//解决这个问题的一个办法是,在Circle接口中定义一个同名的draw方法
//Circle接口中定义的draw方法一定要与所有父接口中的draw方法是类型兼容的。
例:
interface Circle extends Style, Shape {
    draw(): {color: string; x: number; y: number };
}

类型别名声明

type AliasName = Type
//type是声明类型别名的关键字;AliasName表示类型别名的名称;Type表示类型别名关联的具体类型。类型别名的首字母通常需要大写。
例:
type StringType = string;
type BooleanType = true | false;
type Point = { x: number; y: number; z?: number };

let a: Point;  //let a: { x: number; y: number; z?: number };

递归的类型别名
//若类型别名引用的类型为接口类型、对象类型字面量、函数类型字面量和构造函数类型字面量,则允许递归引用类型别名。
type T0 = { name: T0 };
type T1 = () => T1;
type T2 = new () => T2;
//若类型别名引用的是数组类型或元组类型,则允许在元素类型中递归地引用类型别名。
type T0 = Array<T0>;
type T1 = T1[];
type T3 = [number, T3];
//若类型别名引用的是泛型类或泛型接口,则允许在类型参数中递归的引用类型别名。
interface A<T> {
    name: T;
}
type T0 = A<T0>;
class B<T> {
    name: T | undefined;
}
type T1 = B<T1>;

类型别名与接口

区别之一,类型别名能够表示非对象类型,而接口则只能表示对象类型。
区别之二,接口可以继承其他的接口、类等对象类型,而类型别名则不支持继承。

    //若要对类型别名实现类似继承的功能,则需要使用一些变通方法。此例中的方法只适用于表示对象类型的类型别名。如果类型别名表示非对象类型,则无法使用该方法
    type Shape = { name: string };
    type Circle = Shape & { radius: number };
    function foo(circle: Circle) {
        const name = circle.name;
        const radius = circle.radius;
    }
区别之三,接口名总是会显示在编译器的诊断信息(例如,错误提示和警告)和代码编辑器的智能提示信息中,而类型别名的名字只在特定情况下才会显示出来。
区别之四,接口具有声明合并的行为,而类型别名则不会进行声明合并。

class Circle {
    radius: number;
    constructor() {
        this.radius = 1;
    }
}

//启用"strictPropertyInitialization":true,
//成员变量必须在声明时进行初始化或者在构造函数中进行初始化,否则将产生编译错误。
//确实想要通过调用某些方法来初始化类的成员变量。这时可以使用非空类型断言“!”来通知编译器该成员变量已经进行初始化,以此来避免产生编译错误。
例:
/**
* --strictNullChecks=true
* --strictPropertyInitialization=true
*/
class A {
    a!: number;
    // ~
    // 非空类型断言
    init() {
        this.a = 0;
    }
    constructor() {
        this.init();
    }
}

readonly属性
//在声明类的成员变量时,在成员变量名之前添加readonly修饰符能够将该成员变量声明为只读的。只读成员变量必须在声明时初始化或在构造函数里初始化。
class A {
    readonly a = 0;
    readonly b: number;
    readonly c: number; // 编译错误
    constructor() {
        this.b = 0;
    }
}

成员存取器
//成员存取器由get和set方法构成,并且会在类中声明一个属性。成员存取器的定义方式与对象字面量中属性存取器的定义方式是完全相同的。
//如果一个类属性同时定义了get方法和set方法,那么get方法的返回值类型必须与set方法的参数类型一致,否则将产生错误。
//如果一个类属性同时定义了get方法和set方法,那么get方法和set方法必须具有相同的可访问性。

索引成员
class A {
    x: number = 0;
    [prop: string]: number;
    [prop: number]: number;
}
//在类的索引成员上不允许定义可访问性修饰符,如public和private等。

成员可访问性
public
//类的公有成员没有访问限制,可以在当前类的内部、外部以及派生类的内部访问。类的公有成员使用public修饰符标识。
//在默认情况下,类的所有成员都是公有成员。因此,在定义公有成员时也可以省略public修饰符。
protected
//类的受保护成员允许在当前类的内部和派生类的内部访问,但是不允许在当前类的外部访问。类的受保护成员使用protected修饰符标识。
private
//类的私有成员只允许在当前类的内部被访问,在当前类的外部以及派生类的内部都不允许访问。类的私有成员使用private修饰符标识。
私有字段
//2020年1月,ECMAScript标准引入了一个新特性,那就是允许在类中定义私有字段。
//TypeScript语言也从3.8版本开始支持该特性。
//在字段标识符前添加一个“#”符号。不论是在定义私有字段时还是在访问私有字段时,都需要在私有字段名前添加一个“#”符号。

构造函数
//在构造函数上也可以使用可访问性修饰符。它描述的是在何处允许使用该类来创建实例对象。
//如果将构造函数设置成私有的,则只允许在类的内部创建该类的对象。
//与函数重载类似,构造函数也支持重载。
//我们将没有函数体的构造函数声明称为构造函数重载,同时将定义了函数体的构造函数声明称为构造函数实现。
//构造函数重载可以存在零个或多个,而构造函数实现只能存在一个。
例:
class A {
    constructor(x: number, y: number);
    constructor(s: string);
    constructor(xs: number | string, y?: number) {}
}
const a = new A(0, 0);
const b = new A('foo');

参数成员
//在构造函数参数列表中,为形式参数添加任何一个可访问性修饰符或者readonly修饰符,该形式参数就成了参数成员,进而会被声明为类的成员变量。
例:
class A {
    constructor(public x: number) {}
}
const a = new A(0);
a.x; // 值为0

继承
class DerivedClass extends BaseClass { }
//当派生类继承了基类后,就自动继承了基类的非私有成员。
//在派生类中可以重写基类的成员变量和成员函数。在重写成员变量和成员函数时,需要在派生类中定义与基类中同名的成员变量和成员函数。
//在派生类中,可以通过super关键字来访问基类中的非私有成员。当派生类和基类中存在同名的非私有成员时,在派生类中只能通过super关键字来访问基类中的非私有成员,无法使用this关键字来引用基类中的非私有成员。
//若派生类重写了基类中的受保护成员,则可以将该成员的可访问性设置为受保护的或公有的。也就是说,在派生类中只允许放宽基类成员的可访问性。
//由于派生类是基类的子类型,因此在重写基类的成员时需要保证子类型兼容性。

派生类实例化
//在派生类的构造函数中必须调用基类的构造函数,否则将不能正确地实例化派生类。在派生类的构造函数中使用“super()”语句就能够调用基类的构造函数。

实例化派生类时的初始化顺序如下:
1)初始化基类的属性。
2)调用基类的构造函数。
3)初始化派生类的属性。
4)调用派生类的构造函数。
//TypeScript中的类仅支持单继承,不支持多继承。

接口继承类
//TypeScript允许接口继承类。若接口继承了一个类,那么该接口会继承基类中所有成员的类型。
//接口不但会继承基类的公有成员类型,还会继承基类的受保护成员类型和私有成员类型。如果接口从基类继承了非公有成员,那么该接口只能由基类或基类的子类来实现。
例:
// 正确,A 可以实现接口 I,因为私有属性和受保护属性源自同一个类 A
class A implements I {
    private x: string = '';
    protected y: string = '';
}
// 接口 I 能够继承 A 的私有属性和受保护属性
interface I extends A {}
// 正确,B 可以实现接口 I,因为私有属性和受保护属性源自同一个类 A
class B extends A implements I {}
// 错误!C 不是 A 的子类,无法实现 A 的有属性和受保护属性
class C implements I {}

实现接口
interface A {}
interface B {}
class C implements A, B {}
//类的定义中声明了要实现的接口,那么这个类就需要实现接口中定义的类型成员。

静态成员
//类的定义中可以包含静态成员。类的静态成员不属于类的某个实例,而是属于类本身。类的静态成员使用static关键字定义,并且只允许通过类名来访问。
//类的public静态成员对访问没有限制,可以在当前类的内部、外部以及派生类的内部访问。
//类的protected静态成员允许在当前类的内部和派生类的内部访问,但是不允许在当前类的外部访问。
//类的private静态成员只允许在当前类的内部访问。

抽象类和抽象成员
//定义抽象类时,在class关键字之前添加abstract关键字即可。
例:abstract class A {}
//抽象类不能被实例化。也就是说,不允许使用new运算符来创建一个抽象类的实例。
//抽象类的作用是作为基类使用,派生类可以继承抽象类。派生类必须实现抽象类基类中的所有抽象成员。
//抽象类中允许(通常)包含抽象成员,也允许包含非抽象成员。
//抽象类中允许声明抽象成员,抽象成员不允许包含具体实现代码。
//抽象类中的抽象成员不能声明为private,否则将无法在派生类中实现该成员。

this类型
//this类型不允许应用于类的静态成员。

类类型
class Circle {
    radius: number;
    area(): number {
        return Math.PI * this.radius * this.radius;
    }
}
interface CircleType {
    radius: number;
    area(): number;
}
// 正确
const a: Circle = new Circle();
// 正确
const b: CircleType = new Circle();

类型进阶

泛型

例:
function identity<T>(arg: T): T {
    return arg;
}

形式类型参数声明
<TypeParameter, TypeParameter, ...>
//TypeParameter表示形式类型参数名
例:
function assign<T, U>(target: T, source: U): T & U {
    // ...
}

类型参数默认类型
<T = DefaultType>
//DefaultType为类型参数T的默认类型,两者之间使用等号连接。
例:
<T = boolean>
//类型参数的默认类型也可以引用形式类型参数列表中的其他类型参数,但是只能引用在当前类型参数左侧(前面)定义的类型参数。
例:
<T, U = T>

可选的类型参数
//在形式类型参数列表中,必选类型参数不允许出现在可选类型参数之后。
例:
<T = boolean, U> // 错误
<T, U = boolean> // 正确
//若一个类型参数的默认类型引用了其左侧声明的类型参数,则没有问题;若一个类型参数的默认类型引用了其右侧声明的类型参数,则会产生编译错误,因为此时引用的类型参数处于未定义的状态。
例:
<T = U, U = boolean> // 错误
<T = boolean, U = T> // 正确

实际类型参数
例:
function identity<T>(arg: T): T {
    return arg;
}
identity<number>(1);
identity<Date>(new Date());

泛型约束
<TypeParameter extends ConstraintType>
//TypeParameter表示形式类型参数名;extends是关键字;ConstraintType表示一个类型,该类型用于约束TypeParameter的可选类型范围。
例:
interface Point {
    x: number;
    y: number;
}
function identity<T extends Point>(x: T): T {
    return x;
}
// 正确
identity({ x: 0, y: 0 });
identity({ x: 0, y: 0, z: 0 });
identity({ x: 0 });
//        ~~~~~~~~
//        编译错误!类型 '{ x: number; }' 不能赋值给类型 Point

//定义泛型约束和默认类型,但默认类型必须满足泛型约束。
<TypeParameter extends ConstraintType = DefaultType>
<T extends number = 0 | 1>
例:
function f<T extends boolean>() {}
f<true>();
f<false>();
f<boolean>();
f<string>(); // 编译错误

泛型约束引用类型参数
<T, U extends T>
//形式类型参数不允许直接或间接地将其自身作为约束类型,否则将产生循环引用的编译错误。
例:
<T extends T> // 错误 
<T extends U, U extends T> // 错误

基约束
//规则一,如果类型参数T声明了泛型约束,且泛型约束为另一个类型参数U,那么类型参数T的基约束为类型参数U。
例:
<T extends U> // 类型参数T的基约束为类型参数U
规则二,如果类型参数T声明了泛型约束,且泛型约束为某一具体类型Type,那么类型参数T的基约束为类型Type。
例:
<T extends boolean>
//规则三,如果类型参数T没有声明泛型约束,那么类型参数T的基约束为空对象类型字面量“{}”。除了undefined类型和null类型外,其他任何类型都可以赋值给空对象类型字面量。
例:
<T> // 类型参数T的基约束为"{}"类型

泛型函数
定义泛型调用签名的语法:
    <T>(x: T): T
定义泛型构造签名的语法:
    new <T>(): T[];
注:如果一个函数既可以定义为非泛型函数,又可以定义为泛型函数,那么推荐使用非泛型函数的形式,因为它会更简洁也更易于理解。

泛型接口
interface MyArray<T> extends Array<T> {
    first: T | undefined;
    last: T | undefined;
}

泛型类型别名
type Nullable<T> = T | undefined | null;
//使用泛型类型别名定义树形结构
type Tree<T> = {
    value: T;
    left: Tree<T> | null;
    right: Tree<T> | null;
};
const tree: Tree<number> = {
    value: 0,
    left: {
        value: 1,
        left: {
            value: 3,
            left: null,
            right: null
        },
        right: {
            value: 4,
            left: null,
            right: null
        }
    },
    right: {
        value: 2,
        left: null,
        right: null
    }
};

联合类型

//联合类型由一组有序的成员类型构成。联合类型表示一个值的类型可以为若干种类型之一。
//例如,联合类型“string |number”表示一个值的类型既可以为string类型也可以为number类型。

联合类型字面量
type NumericType = number | bigint;
//改变成员类型的顺序不影响联合类型的结果类型。

联合类型的类型成员
例:
interface Circle {
    area: number;
    radius: number;
} 
nterface Rectangle {
    area: number;
    width: number;
    height: number;
}
type Shape = Circle | Rectangle;
//因为Circle类型与Rectangle类型均包含名为area的属性签名类型成员,所以联合类型Shape也包含名为area的属性签名类型成员。
//因此,允许访问Shape类型上的area属性。
declare const s: Shape;
s.area; // number
//因为radius、width和height类型成员不是 Circle类型和Rectangle类型的共同类型成员,因此它们不是Shape联合类型的类型成员。
declare const s: Shape;
s.radius; // 错误
s.width; // 错误
s.height; // 错误

//对于联合类型的属性签名,其类型为所有成员类型中该属性类型的联合类型。
例:
interface Circle {
    area: bigint;
}
interface Rectangle {
    area: number;
}
declare const s: Circle | Rectangle;
s.area; // bigint | number

//如果联合类型的属性签名在某个成员类型中是可选属性签名,那么该属性签名在联合类型中也是可选属性签名;
//否则,该属性签名在联合类型中是必选属性签名。
例:
interface Circle {
    area: bigint;
}
interface Rectangle {
    area?: number;
}
declare const s: Circle | Rectangle;
s.area; // bigint | number | undefined

索引签名联合类型
例:
interface T0 {
    [prop: string]: number;
}
interface T1 {
    [prop: string]: bigint;
}
type T = T0 | T1;
interface T0T1 {
    [prop: string]: number | bigint;
}
例:
interface T0 {
    (name: string): number;
}
interface T1 {
    (name: string): bigint;
}
type T = T0 | T1;
interface T0T1 {
    (name: string): number | bigint;
}
例:
interface T0 {
    new (name: string): Date;
}
interface T1 {
    new (name: string): Error;
}
type T = T0 | T1;
interface T0T1 {
    new (name: string): Date | Error;
}

交叉类型

//交叉类型在逻辑上与联合类型是互补的。
//联合类型表示一个值的类型为多种类型之一,而交叉类型则表示一个值同时属于多种类型。

交叉类型字面量
//交叉类型由两个或多个成员类型构成,各成员类型之间使用“&”符号分隔。
例:
interface Clickable {
    click(): void;
}
interface Focusable {
    focus(): void;
}
type T = Clickable & Focusable;

属性签名
例:
interface A {
    x: boolean;
    y?: string;
}
interface B {
    x?: boolean;
    y?: string;
}
A&B //{ x: boolean; y?: string; }

索引签名
例:
interface A {
    [prop: string]: string;
}
interface B {
    [prop: number]: string;
}
A&B //{ [prop: string]: string; [prop: number]: string; }

调用签名与构造签名
例:
interface A {
    (x: number): number;
}
interface B {
    (x: string): string;
}
A&B // { (x: boolean): boolean; (x: string): string; }
B&A// { (x: string): string; (x: boolean): boolean; }

交叉类型与联合类型

优先级
//当表示交叉类型的“&”符号与表示联合类型的“|”符号同时使用时,“&”符号具有更高的优先级。
A & B | C & D 等同于 (A & B) | (C & D)
//当表示交叉类型的“&”符号与表示联合类型的“|”符号与函数类型字面量同时使用时,“&”符号和“|”符号拥有更高的优先级。
() => bigint | number 等同于 () => (bigint | number)
//“&”符号如同数学中的乘法符号“×”,而表示联合类型的“|”符号则如同数学中的加法符号“+”
A & (B | C) ≡ (A & B) | (A & C)
(A | B) & (C | D) ≡ A & C | A & D | B & C | B & D

例:
T = (string | 0) & (number | 'a');
  = (string & number) | (string & 'a') | (0 & number) | (0 & 'a'); 
// 没有交集的原始类型的交叉类型是 'never' 类型 
  = never | 'a' | 0 | never; // 'never' 尾端类型是所有类型的子类型 
// 并且若某成员是其他成员的子类型,则可以从联合类型中消去 
  = 'a' | 0;

索引类型查询

//它只允许将字符串和Symbol值作为对象的键。
//索引类型查询获取的是对象的键的类型,因此索引类型查询的结果类型是联合类型“string |symbol”的子类型,因为只有这两种类型的值才能作为对象的键。
//但由于数组类型十分常用且其索引值的类型为number类型,因此编译器额外将number类型纳入了索引类型查询的结果类型范围。
keyof Type
例:
interface Point {
    x: number;
    y: number;
}
type T = keyof Point; // 'x' | 'y'

type KeyofT = keyof T;

//如果类型T中包含字符串索引签名,那么将string类型和number类型添加到结果类型KeyofT。
例:
interface T {
    [prop: string]: number;
}
// string | number
type KeyofT = keyof T;

//如果类型T中包含字符串索引签名,那么将string类型和number类型添加到结果类型KeyofT。
例:
interface T {
    [prop: number]: number;
}
// number
type KeyofT = keyof T;

//如果类型T中包含属性名类型为“unique symbol”的属性,那么将该“unique symbol”类型添加到结果类型KeyofT。
例:
const s: unique symbol = Symbol();
interface T {
    [s]: boolean;
}
// typeof s
type KeyofT = keyof T;
//因为“unique symbol”类型是symbol类型的子类型,所以该索引类型查询的结果类型仍是联合类型“string | number |symbol”的子类型。

当对any类型使用索引类型查询时,结果类型固定为联合类型“string | number | symbol”。
type KeyofT = keyof any; // string | number | symbol

当对unknown类型使用索引类型查询时,结果类型固定为never类型。
type KeyofT = keyof unknown; // never
注:如果想要在对象类型中声明属性名为symbol类型的属性,那么属性名的类型必须为“unique symbol”类型,而不允许为symbol类型。

联合类型属性查询
例:
type A = { a: string; z: boolean };
type B = { b: string; z: boolean };
type KeyofT = keyof (A | B); // 'z'

交叉类型属性查询
keyof (A & B) ≡ keyof A | keyof B
例:
type A = { a: string; x: boolean };
type B = { b: string; y: number };
type KeyofT = keyof (A & B); // 'a' | 'x' | 'b' | 'y'

索引访问类型
T[K]
例:
type T = { a: boolean; b: string };

映射对象类型声明

{ readonly [P in K]? : T }
//readonly是关键字,“?”修饰符表示该属性是否为可选属性,in是遍历语法的关键字;K表示要遍历的类型
//由于遍历的结果类型将作为对象属性名类型,因此类型K必须能够赋值给联合类型“string | number | symbol”
//P是类型变量,代表每次遍历出来的成员类型;T是任意类型,表示对象属性的类型,并且在类型T中允许使用类型变量P。
例:
type K = 'x' | 'y';
type T = number;
type MappedObjectType = { readonly [P in K]?: T };
此例中,映射对象类型MappedObjectType相当于如下对象类型:
{ readonly x?: number; readonly y?: number; }

映射对象类型解析
{ [P in K]: T }//类型K必须能够赋值给联合类型“string | number |symbol”。

若当前遍历出来的类型成员P为字符串字面量类型,则在结果对象类型中创建一个新的属性成员,属性名类型为该字符串字面量类型且属性值类型为T。
例:
01 // { x: boolean } 02 type MappedObjectType = { [P in 'x']: boolean };

若当前遍历出来的类型成员P为数字字面量类型,则在结果对象类型中创建一个新的属性成员,属性名类型为该数字字面量类型且属性值类型为T。
例:
// { 0: boolean }
type MappedObjectType = { [P in 0]: boolean };

若当前遍历出来的类型成员P为“unique symbol”类型,则在结果对象类型中创建一个新的属性成员,属性名类型为该“unique symbol”类型且属性值类型为T。
例:
const s: unique symbol = Symbol();
// { [s]: boolean }
type MappedObjectType = { [P in typeof s]: boolean };
例:
// { [x: string]: boolean }
type MappedObjectType = { [P in string]: boolean };

若当前遍历出来的类型成员P为number类型,则在结果对象类型中创建数值索引签名。
例:
// { [x: number]: boolean }
type MappedObjectType = { [P in number]: boolean };

若当前遍历出来的类型成员P为string类型,则在结果对象类型中创建字符串索引签名。
例:
// { [x: string]: boolean }
type MappedObjectType = { [P in string]: boolean };

应用
例:
type T = { a: string; b: number };
// { readonly a: string; readonly b: number; } 
type ReadonlyT = { readonly [P in keyof T]: T[P] };

内置的“Partial<T>”工具类型的定义
/**
* 将T中的所有属性标记为可选属性
*/
type Partial<T> = {
    [P in keyof T]?: T[P];
};
例:
type T = { a: string; b: number };
// { a?: string; b?: number; }
type OptionalT = Partial<T>;

内置的“Readonly<T>”工具类型的定义
/**
* 将T中的所有属性标记为只读属性
*/
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
例:
type T = { a: string; b: number };
// { readonly a: string; readonly b: number; }
type ReadonlyT = Readonly<T>;

条件类型的定义

T extends U ? X : Y

infer关键字

T extends infer U ? U : Y;

内置工具类型

Partial<T>
Required<T>
Readonly<T>
Record<K, T>
Pick<T, K>
Omit<T, K>
Exclude<T, U>
Extract<T, U>
NonNullable<T>
Parameters<T>
ConstructorParameters<T>
ReturnType<T>
InstanceType<T>
ThisParameterType<T>
OmitThisParameter<T>
ThisType<T>

类型查询

typeof 'foo'; // 'string'
例:
const a = { x: 0 };
function b(x: string, y: number): boolean {
    return true;
}
type T0 = typeof a; // { x: number }
type T1 = typeof a.x; // number
type T2 = typeof b; // (x: string, y: number) => boolean

类型断言

<T>类型断言
    <T>expr //T表示类型断言的目标类型。expr表示一个表达式。<T>类型断言尝试将expr表达式的类型转换为T类型。
    例:
    const username = document.getElementById('username');
    if (username) {
        (<HTMLInputElement>username).value; // 正确
    }
    
as T类型断言
    expr as T //as是关键字;T表示类型断言的目标类型;expr表示一个表达式。as T类型断言尝试将expr表达式的类型转换为T类型。
    例:
    const username = document.getElementById('username');
    if (username) {
        (username as HTMLInputElement).value; // 正确
    }

注:TypeScript中只支持<T>类型断言。后来,React[插图]框架开发团队在为JSX添加TypeScript支持时,发现<T>类型断言的语法与JSX的语法会产生冲突,因此,TypeScript语言添加了新的as T类型断言语法来解决两者的冲突。

!类型断言
expr! //非空类型断言尝试从expr表达式的类型中剔除undefined类型和null类型。

顶端类型与尾端类型

顶端类型:any类型和unknown类型。所有类型都是any类型和unknown类型的子类型。
尾端类型:never类型。never类型是所有类型的子类型。

原始类型

原始类型:numberbigintbooleanstringsymbolvoidnull、unde-fined、枚举类型以及字面量类型。
1.字面量类型是其对应的基础原始类型的子类型。
2.undefined类型和null类型分别只包含一个值,即undefined值和null值。它们通常用来表示还未初始化的值。undefined类型是除尾端类型never外所有类型的子类型,其中也包括null类型。
3.null类型是除尾端类型和undefined类型外的所有类型的子类型。

注:使用“// @ts-ignore”注释指令。该注释指令能够禁用针对某一行代码的类型检查