原始数据类型

  • boolean
  • number
  • string
  • void: 只能为定义为 void 的变量赋值为 undefinednull
  • null / undefined: 是所有类型的子类型
  • any: 定义为 any 的变量可以被赋值为任意类型的值,可以使用任意方法,任意属性也能被访问
  • 联合类型: 可以使用 string | number 来定义联合属性,当不能确定一个联合属性类型的变量到底是哪一个类型的时候,只能访问此联合类型的所有类型里共有的属性或方法

定义的时候没有赋值的变量都会被类型推断为 any

使用 type 关键字可以用来创建类型别名和字符串字面量

1
2
3
type Name = string
type NameResolver = () => string
type NameOrResolver = Name | NameResolver
1
2
3
4
5
6
7
type EventNames = 'click' | 'scroll' | 'mousemove'
function handleEvent(ele: Element, event: EventNames) {
// do something
}

handleEvent(document.getElementById('hello'), 'scroll') // 没问题
handleEvent(document.getElementById('world'), 'dbclick') // 报错,event 不能为 'dbclick'

接口

可选属性

1
2
3
4
interface Person {
name: string,
age?: number,
}

任意属性

1
2
3
4
5
interface Person {
name: string,
age?: number,
[propName: string]: any,
}

一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性

1
2
3
4
5
6
interface Person {
name: string,
age?: number,
// error
[propName: string]: string,
}

只读属性

1
2
3
4
5
6
interface Person {
readonly id: number,
name: string,
age?: number,
[propName: string]: any,
}

只读属性只能在对象初始化的时候赋值,后面再赋值会报错

接口定义数组

1
2
3
4
5
interface MyArray {
(index: number): number
}

const _myArray: MyArray = [1, 2, 3]

接口定义函数

1
2
3
4
5
6
7
8
interface SearchFunc {
(source: string, subString: string): boolean
}

let mySearch: SearchFunc
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1
}

接口定义混合类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
let mySum = function(x: number, y: number): number {
return x + y
}

let mySum: (x: number, y: number) => number = function(x: number, y: number): number {
return x + y
}

mySum 的类型会通过等号右边的函数表达式推断得到,所以上下两个函数表达式等价
这里需要区分 es6 里的 =>Typescript 中的 =>, ts 中只是用来做函数的定义。

1
2
3
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName
}

TypeScript 会将添加了默认值的参数识别为可选参数,此时就不受 可选参数必须接在必需参数后面 的限制

1
2
3
4
5
6
7
8
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item)
})
}

let a = []
push(a, 1, 2, 3)

剩余参数可以用数组来进行定义

1
2
3
4
5
6
7
8
9
function reverse(x: number): number
function reverse(x: string): string
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''))
} else if (typeof x === 'string') {
return x.split('').reverse().join('')
}
}

可以将精确的函数定义写在前面,多类型的函数实现写在后面来进行函数的重载。
其实这就是声明的合并,适用于接口、函数、类,合并的属性的类型必须是唯一的。

元组

数组合并了相同类型的对象,而元组合并了不同类型的对象

1
2
let tuple: [string, number] = ['a', 25]
let tuple1: [string, number] = ['a', 25, 'b']

类似第二种赋值,是一种越界的赋值,越界的元素会被定义为元组中每个类型的联合类型。所以第三项的类型为 string | number

多数与 C++ 类特性相识,这里提一点特殊的

1
2
3
4
5
6
7
class Animal {
name = 'Jack'

constructor() {
// ...
}
}

ES7 提案中的实例属性在 Typescript 中实现了,可以不在构造函数当中定义变量

1
2
3
4
5
6
7
class Animal {
static num = 42

constructor() {
// ...
}
}

ES7 提案中的静态属性在这里也是支持的

需要注意的是,Typescript 编译之后的代码中,并没有限制 private 属性在外部的可访问性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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')

抽象类与其他面向对象语言也类似,抽象类不能被实例化,抽象方法必须在子类中进行实现

类与接口

多个类之间的公用方法可以抽象在接口中,然后在多个类中自己实现。这是在保证类只继承自另一个类的时候,还能有很高的灵活性(一个类可以实现多个接口)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Alarm {
alert()
}

class Door {
}

class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert')
}
}

class Car implements Alarm {
alert() {
console.log('Car alert')
}
}

当然接口也是可以继承接口

1
2
3
4
5
6
7
8
interface Alarm {
alert()
}

interface LightableAlarm extends Alarm {
lightOn()
lightOff()
}

接口也可以继承类

1
2
3
4
5
6
7
8
9
10
class Point {
x: number
y: number
}

interface Point3d extends Point {
z: number
}

let point3d: Point3d = {x: 1, y: 2, z: 3}

泛型

1
2
3
4
5
6
7
8
9
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(3, 'x') // ['x', 'x', 'x']

在函数名后先申明泛型,然后在函数的申明和实现中进行使用,类似于 C++ 的模板函数进行使用。更好用的是利用泛型约束来对参数类型进行更灵活的约束。

1
2
3
4
5
6
7
8
9
10
interface Lengthwise {
length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}

loggingIdentity(7) // Error

这样可以约束传入的参数必须包含 length

1
2
3
4
5
6
7
8
9
10
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 }

copyFields(x, { b: 10, d: 20 })

多个参数也可以互相约束,约束 U 中不会出现 T 中不存在的字段,非常好用。
在使用接口来定义一个函数的输入输出类型的时候,也可以使用泛型来定义。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 普通
interface SearchFunc {
(source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}

// 泛型
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>;
}

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

createArray(3, 'x'); // ['x', 'x', 'x']

// 泛型参数提前到接口名上
interface CreateArrayFunc<T> {
(length: number, value: T): Array<T>;
}

let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

同样在泛型也能用于类的定义

1
2
3
4
5
6
7
8
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

只要泛型的申明提前到接口名,类名上的时候就需要在实例化的时候带上泛型的类型
Typescript 2.3 以后,可以使用泛型参数的默认类型

1
2
3
4
5
6
7
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}

Comments

⬆︎TOP