TypeScript

常见类型

  1. 布尔
  2. 数字 number
  3. 字符串 string
  4. 数组
  5. 元组 let x :[string,number] = [’hello’,10]
  6. 枚举 enum
  7. Any 不检查,直接通过编译
  8. void 没有任何类型,只能赋值为undefined 和 null
  9. null undefined
  10. Never 永远不存在类型, 必会抛出异常或者根本不会有返回值
  11. Object非原始类型
  12. 类型断言 尖括号、as

接口

正常值

1
2
3
4
5
6
7
8
9
10
interface LabelledValue {
label: string;
option?:string;
readonly name:string;
}

function read(conf:LabelledValue){
}

read({label:"123.",name:"liming",color:"red"})
  1. 添加 ? 为可选,可不传递
  2. 指挥检查是否存在,不会检查顺序
  3. readonly 只读,不可改变
  4. 如果一个对象字面量存在任何“目标类型”不包含的属性时,会报错,如上面的color

函数

1
2
3
4
5
6
7
8
9
interface SearchFunc {
(source: string, subString: string): boolean;
// name:string;
}
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
let result = src.search(sub);
return result > -1;
}
  1. 函数的入参不需要和接口定义的名字一样
  2. 如果不在入参处设置类型断言,会逐个进行匹配

可索引类型

1
2
3
4
interface StringArray {
[index: number]: string;
readonly [name:string]: number;
}
  1. 当遇到number类型的时候,需要返回string类型数据
  2. 两种索引签名,number、string
  3. 你可以将索引签名设置为只读,这样就防止了给索引赋值:

类类型

1
2
3
4
5
6
7
8
9
10
11
12
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}

class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
  1. 不检查私有成员,只检查公有

接口继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
太过于邪恶,避之

接口继承类

  1. 继承类的成员,但是不继承实现
    1. 比如类中有一个state:”fulfilled“,会继承state,但不会继承fulfilled
  2. 继承 ”继承类的接口“ 的类必须再次实现类的属性

修饰符

  • public
  • private
  • protected 可以在派生类中访问(即子类)
  • readonly 只读

存取器

1
2
3
4
5
6
7
8
9
10
11
class Employee {
private _fullName: string;

get fullName(): string {
return this._fullName;
}

set fullName(newName: string) {
console.log(newName);
}
}
  1. 存取器要求设置为ES5或者更高,不支持降级到ECMA3
  2. 只带有get的存取器默认推断为readonly

抽象类

1
大致和java相同

★★高级技巧★★

声明类时

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

let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet());

创建了一个构造函数,会在new创建实例的时候调用。

上面的代码被编译之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
let Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
})();

let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());

回顾:使用new函数会发生什么?

  1. 创建一个空对象,成为newInstance(新的实例)
  2. 如果构造函数的prototype是一个对象,那么把newInstance的原型指向prototype,否则newInstance为一个普通对象,原型为Object.prototype
  3. 使用给定参数执行构造函数,并把newInstance绑定为this上下文
  4. 如果构造函数返回非原始值,则返回值为new的结果,否则返回newInstance

函数

类型推断:

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

x 和 y会被自动推断为 number类型

可选参数和默认参数

  1. 使用 ?修饰 比如 name?:string

  2. 可选参数必须放到默认参数之后

  3. 剩余参数

    1
    2
    3
    function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
    }

重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x:any): 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 };
}
}
  1. 被重载的函数不能声明方法体
  2. 只有一个方法可以声明方法体,并且重复的入参必须为any
  3. 它按照顺序查找重载列表,尝试使用第一个满足的定义。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。

泛型

1
2
3
function identity<T>(arg: T): T {
return arg;
}
  1. 使用尖括号定义
  2. 意义:arg的类型和identity函数返回的值的类型一样
1
2
3
4
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
  1. 泛型参数名只要在数量上和使用方式上能对应就行

泛型类

1
2
3
4
5
6
7
8
class Person<T>{
zeroValue: T;
name:T;
}

let liu = new Person<string>();
liu.name="asdf"
liu.zeroValue="adfa"
  1. 类有两部分,静态部分和实例部分,泛型指的是实例部分的类型

泛型约束

1
2
3
4
5
6
7
8
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;
}
  1. 如果我们想获得arg的长度,可以让泛型T继承一个带有length属性的接口
  2. 如果T为number会报错,string则不会
  3. 或者
1
2
3
loggingIdentity<string>('hello')
loggingIdentity<number>(234) //报错
loggingIdentity({length: 10})

在泛型里使用类类型

1
2
3
function create<T>(c: {new(): T; }): T {
return new c();
}
  1. 在此处,new()代表一个无参的构造函数
  2. 所以此处的入参c的意义为:一个含有无参的构造函数的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//***类型标注***,表示一个没有参数的构造函数。
function create<T>(c: {new(): T; }): T {
console.log(c)
return new c();
}

class Anim {
constructor() {}
// constructor(name:string) {} 则会报错
}

console.log(create(Anim) instanceof Anim); //true

//create(Anim)会创造一个animal对象

小思考题

枚举

数字枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Animal {
Cat = 2,
Dog, //3
Rabbit=6,
Lion //7
}

console.log(Animal.Cat === 2)

enum E {
A = getSomeValue(),
B, // error! 'A' is not constant-initialized, so 'B' needs an initializer
}
  1. 索引从0开始,下面的比上面的增加1
  2. 如果某一个成员不是constant值,那么下面的成员必须初始化

字符串枚举

即使用字符串进行初始化

1
2
3
4
5
6
7

enum Animal {
Cat = "Cat",
Dog = "Dog1",
}

console.log(Animal.Cat) // Cat
  1. 没有自增长行为,可以序列化

异构

即数字、字符串混合,不建议

联合枚举与枚举成员的类型

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,
}
  1. 枚举成员成为了类型,意义为:这里Circle接口的kind只能为ShapeKind.Circle类型
  2. 枚举类型成为了枚举成员的类型联合

★★反向映射★★

1
2
3
4
5
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

★★可能会将编译为★★

1
2
3
4
5
6
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a]; // "A"

或许添加两个输出会更加直观

1
2
3
4
5
6
7
8
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a]; // "A"
console.log(Enum[0]) //A
console.log(Enum["A"]) //0

枚举类型被编译为一个Enum对象,包含了正向name→value 和反向映射 value → name , 不会为字符串枚举成员生成反向映射

例子:

1
2
3
4
5
6
7
8
enum Enum {
A = 1,
B,
C = 2
}

console.log(Enum.B === Enum.C) // true
console.log(Enum[2]) // C

过程

  1. 生成一个Enum对象
  2. 设置A的正向映射为1,同时设置1的反向映射为A
  3. 设置B的正向映射为2,同时设置2的反向映射为B
  4. 设置C的正向映射为2,同时设置2的反向映射为C,覆盖了原来的B
1
2
3
4
5
(function (Enum) {
Enum[Enum["A"] = 1] = "A"
Enum[Enum["B"] = 2] = "B"
Enum[Enum["C"] = 2] = "C"
}(Enum || (Enum = {})))

类型推论

最佳通用类型

1
2
let x = [0, 1, null];
x : (number|null)[]
1
2
let zoo = [new Rhino(), new Elephant(), new Snake()];
(Rhino | Elephant | Snake)[]

上下文类型

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

根据上文的window.onmousedown推断出mouseEvent的类型为MouseEvent

1
mouseEvent:any

使用注解抵消上下文类恶行

类型兼容性

  1. 如果x要兼容y,y至少具有与x相同的属性
1
2
3
4
5
6
7
8
interface Named {
name: string;
}

let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: 'Alice', location: 'Seattle' };
x = y;

过程:编译器递归检查x中的每个属性,看是否在y中能找到属性名一样且类型相同的属性

比较两个函数

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

y = x; // OK
x = y; // Error

理解

  1. y赋值给x可以,因为同时传递b和s之后,b的值可以赋值给a,也满足x的条件
  2. x赋值给y不行,因为只传递a之后,无值可用
  3. 编译时不会赋值尝试,只会根据类型进行判断
1
2
3
4
5
let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});

x = y; // OK
y = x; // Error, because x() lacks a location property
  1. 把y赋值给x那么参数依然可以调用name(x中所有的值
  2. 源函数的返回值类型必须是目标函数返回值类型的子类型

函数参数双向协变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum EventType { Mouse, Keyboard }

interface Event {
timestamp: number;
}

interface MouseEvent extends Event {
// @ts-ignore
x: number
// @ts-ignore
y: number
}

function listenEvent(eventType: EventType, handler: (n: Event) => void) {
/* ... */
}

// 1
listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + ',' + e.y));

// 2
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.x + ',' + e.y)));

  1. 1处报错,因为MouseEvent是Event的衍生类,不能“覆盖”Event
  2. 可以传入Event,之后使用 as 或者 <> 显示判断类型

可选参数和剩余参数

  1. 可选参数 ?. ,剩余参数 …args
  2. 可选参数可以当作无限个可选参数
1
2
3
4
5
6
7
8
9
function invokeLater(args: any[], callback: (...args: any[]) => void) {
/* ... Invoke callback with 'args' ... */
}

// Unsound - invokeLater "might" provide any number of arguments
invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));

// Confusing (x and y are actually required) and undiscoverable
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));

此时,args为x,y….

高级类型

交叉类型

  1. 多个类型合并为一个类型
  2. Person & Serializable & Loggable ,同时有这三种类型的成员
1
2
3
4
5
6
7
8
9
10
11
12
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}

联合类型

  1. 表示可以是几个类型之一
  2. number | boolean | string 表示可以是这三个类型之一
  3. 如果一个值是联合类型,只能访问共有的成员
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Bird {
move()
dance()
}

interface Fish {
move()
eat()
}

let ani: Bird | Fish
ani.move()
ani.dance() //wrong

类型保护和区分类型

使用联合类型时用来区分不同类型

  1. 类型断言
1
2
3
4
5
6
7
8
let pet = getSmallPet();

if ((<Fish>pet).swim) {
(<Fish>pet).swim();
}
else {
(<Bird>pet).fly();
}
  1. 类型保护
1
2
3
4
5
6
7
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}

if(isFish(pet)){
pet.swim();
}
  1. pet is Fish :类型谓词

  2. 这样可以判断在if分支中一定是Fish类型

  3. typeof、instanceof

可选参数和可选属性

  1. 使用了—strictNullChecks 可选参数会自动加上 | undefined
1
2
3
4
5
6
7
function f(x: number, y?: number) {
return x + (y || 0);
}
f(1, 2);
f(1);
f(1, undefined);
f(1, null); // error, 'null' is not assignable to 'number | undefined'

类型别名

给类型起一个新的名字

1
type Name = string;
  1. 不会新建一个类型
  2. 创建了一个新的名字来引用那个类型
  3. 接口类型可以是泛型
1
type Container<T> = { value: T };
  1. 在属性里引用自己
1
2
3
4
5
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}

☆☆接口和类型别名☆☆

interface和type

  1. 接口创建了一个新的名字,可以在其他地方使用,类型别名不创建新的名字,而是类型的引用

  2. 类型别名不能被或者extends、implements其他类型,interface对于拓展是开放的

  3. 多个同名的interface会合并(相同属性声明为不同类型时候会报错),type则会报错

  4. type可以表示联合类型和交叉类型,interface则会报错

    1
    2
    type Animal = "fish" | "cow"
    interface Animal = "fish" | "cow" //报错
  5. type 可以表示元组、枚举、基本类型(如 string、number、boolean)、字面量类型等,而 interface 不能。

可辨识类型

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
26
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
interface Triangle {
kind: "triangle";
width: number
}
type Shape = Square | Rectangle | Circle | Triangle;
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
default : return assertNever(s) //检查是否是Never
}
}

索引类型

让编译器可以检查使用了动态属性名的代码

1
2
3
4
5
interface Person {
name?: string
}

let personProps: keyof Person = "age"

personProps是Person中的一个属性,

type、interface、class都可以使用

映射类型

1
2
3
4
5
6
type Readonly<T> = {
readonly [P in keyof T]: T[P];
} ///把T中所有的类型变为只读的
type Partial<T> = {
[P in keyof T]?: T[P];
} ///把T中所有的类型变为可选的
  1. keyof T表示T中所有属性名的联合类型
  2. P in keyof T 类型映射,遍历T的所有属性名,为每个属性名P创建一个新的类型
  3. T[P] 表示属性P在T中的类型
  4. readonly 表示这个属性是只读的

Symbol

一些内置的Symbol:

  1. Symbol.hasIntance 会被instanceof运算符调用,构造器对象用来识别一个对象是否是实例
1
2
3
4
5
6
7
8
class MyObject {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}

console.log([] instanceof MyObject); // 输出:true
console.log({} instanceof MyObject); // 输出:false
  1. Symbol.iterator

    1
    2
    3
    4
    5
    6
    7
    8
    class MyObject {
    static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
    }
    }

    console.log([] instanceof MyObject); // 输出:true
    console.log({} instanceof MyObject); // 输出:false
  2. Symbol.toPrimitive

    1
    ToPrimitive抽象调用,对象转化为相应的原始值

声明合并

编译器将针对同一个名字的两个独立生命合并为单一声明

ts声明会创建:命名空间、类型、值。

接口

  1. 非函数成员应该是唯一的
  2. 同名函数会被当成重载,在后面声明的接口优先级更高(出现在靠前的位置)
  3. 出现特殊的函数签名的时候,即,有一个参数是单一的字符串字面量,会被提升到最顶端

命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace Animals {
export class Zebra { }
}

namespace Animals {
export interface Legged { numberOfLegs: number; }
export class Dog { }
}

///等价于
namespace Animals {
export interface Legged { numberOfLegs: number; }

export class Zebra { }
export class Dog { }
}

模块扩展

1
2
3
4
5
6
7
8
9
10
// observable.js
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}

// map.js
import { Observable } from "./observable";
Observable.prototype.map = function (f) {
// ... another exercise for the reader
}

编译器不会进行提示,通知编译器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// observable.ts stays the same
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
interface Observable<T> {
map<U>(f: (x: T) => U): Observable<U>;
}
}
Observable.prototype.map = function (f) {
// ... another exercise for the reader
}

// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map(x => x.toFixed());