TypeScript笔记

基本类型、变量声明、接口、类等

学习TypeScript官方文档的笔记。

基本类型

  1. Boolean

    1
    let isDone: boolean = false
  2. Number

    1
    2
    3
    4
    let decimal: number = 6;
    let hex: number = 0xf00d;
    let binary: number = 0b1010;
    let octal: number = 0o744;
  3. String

    1
    2
    let color: string = "blue";
    color = 'red';

    可以使用模板字符串

    1
    let sentence: string = `Hello, my name is ${ fullName }.`
  4. Array

    1
    2
    let list: number[] = [1, 2, 3];
    let list: Array<number> = [1, 2, 3];
  5. Tuple

    1
    2
    3
    4
    5
    6
    // Declare a tuple type
    let x: [string, number];
    // Initialize it
    x = ["hello", 10]; // OK
    // Initialize it incorrectly
    x = [10, "hello"]; // Error
  6. Enum

    1
    2
    enum Color {Red, Green, Blue}
    let c: Color = Color.Green; // 1

    枚举的数值默认从0开始,可以指定字段的值

    1
    2
    enum Color {Red = 1, Green, Blue}
    let c: Color = Color.Green; // 2
  7. Any
    一种动态类型

    1
    2
    3
    let notSure: any = 4;
    notSure = "maybe a string instead";
    notSure = false; // okay, definitely a boolean

    可以定义多类型数组

    1
    let a: any[] = [1, true, 'false']
  8. Void
    any的反义类型,可以作为无返回值的函数返回类型

    1
    2
    3
    function hasUser(): void {
    alert('ok')
    }

    若作为定义值的类型只能用于undefined和null

    1
    let unusable: void = undefined;
  9. Null & Undefined
    分别对应null和undefined类型,除了自己之外不发定义其他类型,不过可以赋值给其他类型,如number

  10. Never
    表示从未发生的类型,可作为函数的返回类型或者总是抛出异常的箭头函数的返回类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // Function returning never must have unreachable end point
    function error(message: string): never {
    throw new Error(message);
    }
    // Inferred return type is never
    function fail() {
    return error("Something failed");
    }
    // Function returning never must have unreachable end point
    function infiniteLoop(): never {
    while (true) {
    }
    }

接口

用来定义值的shape

第一个接口

1
2
3
4
5
6
7
8
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

可选属性

类似swift中的optional type

1
2
3
interface LabelledValue {
label?: string;
}

只读属性

设为只读的属性不可变

1
2
3
interface LabelledValue {
readonly label: string;
}

有一个ReadonlyArray用来定义只读数组类型

1
2
3
4
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
a = ro; // error!

readonly vs const

变量(variable)使用const,属性(property)使用readonly

函数类型

接口中定义的函数类型仅包含输入参数列表与返回类型,每个参数都需要名称与类型

1
2
3
interface SearchFunc {
(source: string, subString: string): boolean;
}

使用

1
2
3
4
5
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}

索引类型

1
2
3
4
5
6
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];

索引可以使用string或number,不过使用number作为key时js会自动转为string,因此需要保持一致性

1
2
3
4
5
6
7
8
9
10
11
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
// Error: indexing with a 'string' will sometimes get you an Animal!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}

类类型

类类型的实现

1
2
3
4
5
6
7
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) { }
}

静态的类接口和类实例的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick();
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute);
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

上面这个例子中ClockConstructor是构造器接口,ClockInterface是实例方法接口。因为createClock函数的第一个参数类型是ClockConstructor,因此在createClock(AnalogClock, 7, 32)语句中它会检查AnalogClock是否具有正确的构造器类型。

扩展接口

如同类,接口也可以扩展,扩展的接口会继承成员的属性,也可以同时扩展多个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

混合类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

扩展类的接口

当一个接口扩展自一个类时,会继承类的所有成员但不会继承它们的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
}
// Error: Property 'state' is missing in type 'Image'.
class Image implements SelectableControl {
select() { }
}
class Location {
}

SelectableControl包含Control的所有成员,Button和TextBox都继承自Control应该具有state成员无需实现,Button和Image实现了SelectableControl接口,均需实现select方法,而Image需要再次实现state成员因为它未继承自Control类。

一个简单的类

1
2
3
4
5
6
7
8
9
10
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.log('Woof! Woof!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();

public、private与protected修饰符

默认是public

TS中所有的类成员默认都是public的,也可以显示的通过public修饰符定义

1
2
3
4
5
6
7
class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}

private

成员使用private标记后就不能从类外访问了

1
2
3
4
5
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // Error: 'name' is private;

包含public成员的类实例与包含private的类实例是不可比较的(not compatible)

protect

使用protected的效果如private即使是派生类的实例也不能访问,不过在实例方法中可以被使用。

构造函数也可以使用protected标记,意味着在它所包含的类之外不能被实例化,不过可以被扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee can extend Person
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // Error: The 'Person' constructor is protected

只读修饰符

可以使用readonly来标记只读的成员,这些成员只能在声明时或构造函数中初始化。

1
2
3
4
5
6
7
8
9
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name is readonly.

访问器

TS中也可以在类中设置setter和getter访问器,比如下面这个例子

1
2
3
4
5
6
7
8
class Employee {
fullName: string;
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
console.log(employee.fullName);
}

如果emplyee随意改变fullname的话会让我们很困扰,可以添加访问器在进行一个验证的操作,改写成如下这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let passcode = "secret passcode";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
}
else {
console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
console.log(employee.fullName);
}

访问器需要将编译器设为ES5及更高版本,并且访问器会自动被当做只读类型,这在从你代码生成.d.ts文件时很有帮助,使用者一看就知道哪些属性是他们不能修改的。

静态属性

前面所说的都是对于类的实例成员,在实例化后的对象才会起作用。可以使用static定义类中的静态成员,所有实例可以使用this中的名称来访问静态成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number;}) {
let xDist = (point.x - Grid.origin.x);
let yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor (public scale: number) { }
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

抽象类

抽象类是可能会有其他派生类的基础类。抽象类与接口不同的是它可以包含成员的实现细节,可以使用abstract定义抽象类或者类中的抽象方法。

抽象类中的抽象函数不包含实现并且必须在派生类中被实现。

1
2
3
4
5
6
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("roaming the earth...");
}
}

高级玩法

构造函数

将类作为接口使用

1
2
3
4
5
6
7
8
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};

函数

简介

跟JS一样,TS中也分函数声明与函数表达式

1
2
3
4
5
6
// Named function(=函数声明)
function add(x, y) {
return x + y;
}
// Anonymous function (=函数表达式)
let myAdd = function(x, y) { return x + y; };

函数类型

添加类型

可以给函数的输入参数或返回值设置类型

1
2
3
4
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number { return x + y; };

编写函数类型

1
2
let myAdd: (x: number, y: number) => number =
function(x: number, y: number): number { return x + y; };

为了增加可读性可写成下面这样

1
2
let myAdd: (baseValue: number, increment: number) => number =
function(x: number, y: number): number { return x + y; };

在设置函数类型的部分中若无返回值需要设为void而不是置空。

推断类型

从编写的类型中可以自动推断出函数的类型

1
2
3
4
5
// myAdd has the full function type
let myAdd = function(x: number, y: number): number { return x + y; };
// The parameters 'x' and 'y' have the type number
let myAdd: (baseValue: number, increment: number) => number =
function(x, y) { return x + y; };

可选与默认参数

使用?将一个参数标记为可选参数,可选参数必须放在必需参数后面(存在必需参数的情况下)。

1
2
3
4
5
6
7
8
9
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right

在参数列表中赋值来设置参数的默认值

1
2
3
4
5
6
7
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result4 = buildName("Bob", "Adams"); // ah, just right

不像可选参数,有默认值的参数可以放在必需参数的前面,若想使用默认参数的默认值,需要传入undefined

1
2
3
4
5
6
7
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"

其他参数

编译器会在使用了省略号的参数上构建一个参数数组,也可以在编写函数类型时使用省略号

1
2
3
4
5
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

this

this和箭头函数

类似ES6

this参数

可以指定this参数的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function(this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);

重载

定义重载的函数类型,在下面的例子中为同一个函数提供了多个函数类型作为重载列表,前两行为pickCard的重载列表,而pickCard(x): any不是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

泛型

例子

假如有一个恒等函数

1
2
3
function identity(arg: any): any {
return arg;
}

改写成

1
2
3
function identity<T>(arg: T): T {
return arg;
}

两种调用方法:

1
2
let output = identity<string>("myString"); // type of output will be 'string'
let output = identity("myString"); // type of output will be 'string'

泛型类型变量

1
2
3
4
5
6
7
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;

泛型类型

1
2
3
4
5
6
7
8
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
// can change generic type parameter name
let myIdentity: <U>(arg: U) => U = identity;
// can write generic as a call signature of a object literal type
let myIdentity: {<T>(arg: T): T} = identity;

改写成一个泛型接口

1
2
3
4
5
6
7
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;

泛型类

泛型类如同泛型接口,在类名后面的尖括号中写出泛型类型参数列表

1
2
3
4
5
6
7
8
9
10
11
12
13
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
// make generic type parameter as number
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
// make generic type parameter as string
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
alert(stringNumeric.add(stringNumeric.zeroValue, "test"));

泛型约束

在下面这段代码中,并不能保证每种输入参数都含由length属性,因此需要将T应具有的行为约束写到参数列表中

1
2
3
4
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}

创建一个接口来描述约束,并结合extends关键字来实现

1
2
3
4
5
6
7
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}

由于现在泛型函数被约束了,使用不包含length属性的类型会发生错误

1
2
3
loggingIdentity(3); // Error, number doesn't have a .length property
// 需要输入必须的参数
loggingIdentity({length: 10, value: 3});

在泛型约束中使用类型参数

可以声明一个受其他类型参数约束的类型参数,比如下面这个K参数受约束于T,K必须是T的键

1
2
3
4
5
6
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

在泛型中使用类类型

在使用泛型创建工厂模式时,需要参考构造函数来决定类的类型

1
2
3
function create<T>(c: {new(): T; }): T {
return new c();
}

较为高级的用法就是通过原型函数推断类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!

枚举

数值枚举

使用enum关键字定义枚举。数值型枚举默认的起始值是0,可以修改起始值

1
2
3
4
5
6
enum Direction {
Up = 1,
Down, // 2
Left, // 3
Right,// 4
}

使用枚举:通过调用枚举的属性来访问其中的成员,使用枚举名称声明类型

1
2
3
4
5
6
7
8
enum Response {
No = 0,
Yes = 1,
}
function respond(recipient: string, message: Response): void {
// ...
}
respond("Princess Caroline", Response.Yes)

数值枚举中可以使用计算与常量成员

字符串枚举

字符串枚举中的每一项都要使用字符串字面量进行常量初始化,与一般的枚举在运行时有略微的不同

1
2
3
4
5
6
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}

字符串枚举没有自增行为,有更好的关联性,比如在debug时数值枚举的成员值不具有意义,而字符串枚举则包含了有意义并可读的值

异构枚举

枚举中也可以混用数值与字符串

1
2
3
4
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}

不建议使用混合的异构类型,请了解下运行时枚举的行为

计算与常量成员

一个枚举成员会在下列情况中被当做常量

  1. 枚举中的第一个成员并且没有初始值,会被分配0值
  2. 若无初始值并且前一个成员是数值常量,则该成员的值为前一个成员的数值加一
  3. 枚举值使用常量枚举表达式进行初始化,常量枚举表达式由Typescript的一系列表示式组成
    1
    2
    3
    4
    5
    6
    7
    8
    9
    enum FileAccess {
    // constant members
    None,
    Read = 1 << 1,
    Write = 1 << 2,
    ReadWrite = Read | Write,
    // computed member
    G = "123".length
    }

组合枚举与枚举成员类型

枚举成员字面量:一个无初始值的常量枚举成员,或者使用字符串字面量(“foo”)、数值字面量(1)与任何一元操作符与数值字面量的组合(-100)。

组合类型使用|来分离(number | string)

当枚举中的所有成员均有枚举字面量值时,会出现个有趣的功能:枚举成员是可以作为类型的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum ShapeKind {
Circle,
Square,
}
interface Circle {
kind: ShapeKind.Circle;
radius: number;
}
interface Square {
kind: ShapeKind.Square;
sideLength: number;
}
let c: Circle = {
kind: ShapeKind.Square,
// ~~~~~~~~~~~~~~~~ Error!
radius: 100,
}

类型是不能比较的,TypeScript的编译器会捕捉这种错误

1
2
3
4
5
6
7
8
9
10
enum E {
Foo,
Bar,
}
function f(x: E) {
if (x !== E.Foo || x !== E.Bar) {
// ~~~~~~~~~~~
// Error! Operator '!==' cannot be applied to types 'E.Foo' and 'E.Bar'.
}
}

运行时中的枚举

在runtime中枚举是被当做对象进行操作的,比如下面这个枚举

1
2
3
enum E {
X, Y, Z
}

这个枚举可以作为下面这个函数的参数

1
2
3
4
5
function f(obj: { X: number }) {
return obj.X;
}
// Works, since 'E' has a property named 'X' which is a number.
f(E);

const 枚举

使用const关键字定义的枚举中只能包含常量枚举表达式,不能使用计算成员

1
2
3
4
const enum Enum {
A = 1,
B = A * 2
}

环境枚举

环境枚举(ambient enum)用来描述已存在的枚举类型,避免声明同一枚举对象内重复的枚举成员

1
2
3
4
5
declare enum Enum {
A = 1,
B,
C = 2
}

环境枚举与非环境枚举的一个显著区别是,在非环境枚举中,没有初始化的成员会根据前一个成员是否具有常量而使自身也作为常量成员,但是在环境枚举中,没有初始化的成员会被作为计算成员。

类型推断

基础

在无类型标注时会自动推断并提供类型信息

1
let x = 3

x变量的类型会被推断为number,类型推断会在变量初始化、设定默认值与设定函数返回值时发生。

通常情况下,类型推断都是直接明了的,不过在某些情况下会有细微的差别。

最优共同类型

当需要从多个表达式中推断类型时,会执行一个最优共同类型(Best common type)的过程。比如下面这个例子

1
let x = [0, 1, null];

x中既包含number与null两种类型,最佳共同类型算法会考虑每个候选类型,推断出一种符合每一种候选类型的共同类型。

不过有时不存在所有候选类型都继承的父类型,比如

1
let zoo = [new Rhino(), new Elephant(), new Snake()];

我们想让zoo被推断为Animal[],但是数组中的的对象并不是严格的Animal类型,为了解决这个问题需要显式的设定类型

1
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

当找不到最优共同类型时,推断的结果为一个组合数组类型(Rhino | Elephant | Snake)[]

上下文类型

上下文类型的推断发生在当表达式的类型由它所处的位置决定时。比如

1
2
3
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); //<- Error
};

若上下文类型表达式包含显式的类型信息,则会忽视上下文类型,比如重写上面的例子:

1
2
3
window.onmousedown = function(mouseEvent: any) {
console.log(mouseEvent.button); //<- Now, no error is given
};

使用显式类型标记的函数被覆盖上下文类型。

很多情况都包含上下文类型,比如调用函数时的arguments、赋值的右侧表达式、类型断言、对象与数组字面量的成员、返回语句等。

类型兼容

TypeScript中的类型兼容是基于结构类型(structural typing)的,结构类型中的类型由相关类型的成员决定,相对的是名义类型(nominal typing)。

1
2
3
4
5
6
7
8
9
interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
// OK, because of structural typing
p = new Person();

例子

1
2
3
4
5
6
7
interface Named {
name: string;
}
let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: "Alice", location: "Seattle" };
x = y;

在判断是否可以将y赋值给x时,编译器会检查每个x中的属性是否在y中有兼容属性。在上面这个例子中,y中包含name属性因此通过了检查。

对于函数的调用参数同样

1
2
3
4
function greet(n: Named) {
alert("Hello, " + n.name);
}
greet(y); // OK

函数的兼容

比较元类型和对象类型的兼容性是很直观的,不过如何判断两个函数是兼容的呢

1
2
3
4
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
x = y; // Error

判断x是否可以赋给y,x中的参数必须在y的参数中有兼容类型,注意与参数的名称无关。由于x中的参数都有在y中的兼容类型,因此可以将x赋给y。第二种赋值中,由于y中的第二个参数在x的参数列表中不存在对应兼容类型,因此不允许赋值。

考虑一下对于返回值是如何判断的

1
2
3
4
let x = () => ({name: "Alice"});
let y = () => ({name: "Alice", location: "Seattle"});
x = y; // OK
y = x; // Error because x() lacks a location property

类型系统强制源函数的返回类型必须是目标类型的一个子集

枚举

枚举与数字相互兼容,不同枚举类型的枚举值不兼容

1
2
3
4
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };
let status = Status.Ready;
status = Color.Green; //error

类与对象字面量和接口很像,有一点不同的是:它同时具有静态与实例类型。当比较两个类类型的对象时,仅比较实例中的成员,静态成员与构造器不影响兼容性。

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
feet: number;
constructor(name: string, numFeet: number) { }
}
class Size {
feet: number;
constructor(numFeet: number) { }
}
let a: Animal;
let s: Size;
a = s; //OK
s = a; //OK

类中的private和protected成员会影响兼容性

泛型

由于TypeScript是结构类型系统,只有当类型参数作为成员的类型时才会影响结果类型(PS:若不存在成员则不影响)。

1
2
3
4
5
interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
x = y; // okay, y matches structure of x

在上面的x和y是兼容的,因为它们的结构使用类型参数时没有什么不同 ,在Empty中添加一个成员看看效果如何:

1
2
3
4
5
6
interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y; // error, x and y are not compatible

这时,包含一个指定类型参数的泛型就如同一个非泛型类型。

PS:之后直接前往 https://www.tslang.cn/ 继续学习。。

参考

文章目录
  1. 1. 基本类型
  2. 2. 接口
    1. 2.1. 第一个接口
    2. 2.2. 可选属性
    3. 2.3. 只读属性
      1. 2.3.1. readonly vs const
    4. 2.4. 函数类型
    5. 2.5. 索引类型
    6. 2.6. 类类型
    7. 2.7. 扩展接口
    8. 2.8. 混合类型
    9. 2.9. 扩展类的接口
  3. 3.
    1. 3.1. 一个简单的类
    2. 3.2. 继承
    3. 3.3. public、private与protected修饰符
      1. 3.3.1. 默认是public
      2. 3.3.2. private
      3. 3.3.3. protect
    4. 3.4. 只读修饰符
    5. 3.5. 访问器
    6. 3.6. 静态属性
    7. 3.7. 抽象类
    8. 3.8. 高级玩法
      1. 3.8.1. 构造函数
      2. 3.8.2. 将类作为接口使用
  4. 4. 函数
    1. 4.1. 简介
    2. 4.2. 函数类型
      1. 4.2.1. 添加类型
      2. 4.2.2. 编写函数类型
      3. 4.2.3. 推断类型
    3. 4.3. 可选与默认参数
    4. 4.4. 其他参数
    5. 4.5. this
      1. 4.5.1. this和箭头函数
      2. 4.5.2. this参数
    6. 4.6. 重载
  5. 5. 泛型
    1. 5.1. 例子
      1. 5.1.1. 泛型类型变量
    2. 5.2. 泛型类型
    3. 5.3. 泛型类
    4. 5.4. 泛型约束
      1. 5.4.1. 在泛型约束中使用类型参数
      2. 5.4.2. 在泛型中使用类类型
  6. 6. 枚举
    1. 6.1. 数值枚举
    2. 6.2. 字符串枚举
    3. 6.3. 异构枚举
    4. 6.4. 计算与常量成员
    5. 6.5. 组合枚举与枚举成员类型
    6. 6.6. 运行时中的枚举
      1. 6.6.1. const 枚举
    7. 6.7. 环境枚举
  7. 7. 类型推断
    1. 7.1. 基础
    2. 7.2. 最优共同类型
    3. 7.3. 上下文类型
  8. 8. 类型兼容
    1. 8.1. 例子
    2. 8.2. 函数的兼容
    3. 8.3. 枚举
    4. 8.4.
    5. 8.5. 泛型
  9. 9. 参考
|