类型别名用来给一个类型起个新名字。
使用 type
创建类型别名。
类型别名常用于联合类型。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n : NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
字符串字面量类型用来约束取值只能是某几个字符串中的一个。
类型别名 与 字符串字面量类型 都是使用 type
进行定义的。
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames){
// ...
}
// 使用 type 定义了一个字符串字面量类型 EventNames,它只能取三种字符串中的一种。
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
注意:在定义时,就应该初始化。当赋值或访问一个已知索引的元素时,会得到正确的类型。
// 定义元组
let tp: [string, number] = ['tuple', 22];
// 赋值
tp[0] = 'sssss';
tp[1] = 25;
// 获取元素
tp[0]
tp[1]
当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型。就是说添加其他类型的元素会报错。
比如在上面定义的元组中进行以下操作 tp.push(true)
就会报错,因为 tp 的类型只能是 string 或者 number。
枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。
enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat};
枚举成员会被赋值为从 0
开始递增的数字,同时也会对枚举值到枚举名进行反向映射。
enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat};
// 枚举值到枚举名进行反向映射
console.log(Days['Wed'] === 3); // true
console.log(Days[0] === 'Sun'); // true
我们可以给枚举项手动赋值,未手动赋值的枚举项会接着上一个枚举项递增。如果未手动赋值的枚举项与手动赋值的重复了,TypeScript是不会察觉到这一点的,后面的项会覆盖前面的项。
手动赋值的枚举项也可以为小数或负数,此后续未手动赋值的项的递增步长仍为 1
,在小数或负数的基础上加 1
;
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat}; // 未手动赋值的枚举项会接着上一个枚举项递增
枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。
// 常数项,默认第一项为0;
// 也可手动赋值第一项,后面的自递增。
enum Color {
Red = 1,
Green,
Blue
};
// 计算所得项
enum Color {
Red,
Green,
Blue = 'blue'.length
};
// 如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错。
// 因此计算所得项可以放在后面,否则其后面的项要手动赋值。
// 也就是说上面定义的 Color 枚举中的 Blue 后面还有新的项,其需要重新手动赋值,否则报错
常数枚举是使用 const enum
定义的枚举类型。
常数枚举与普通枚举的区别:它会在编译阶段被删除,并且不能包含计算成员。若包含计算成员,则会在编译阶段报错。
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
// 编译结果:
var directions = [0/*Up*/, 1/*Down*/, 2/*Left*/, 3/*Right*/];
外部枚举(Ambient Enums)是使用 declare enum
定义的枚举类型。
declare
定义的类型只会用于编译时的检查,编译结果中会被删除。
外部枚举与声明语句一样,常出现在声明文件中。
declare enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
// 编译结果
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
同时使用 declare
和 const
也是可以的。
declare const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
// 编译结果
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于引来了 class
.
TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。
new
生成;public
: 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的private
: 修饰的属性或方法是私有的,不能在声明它的类的外部访问protected
: 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的注意:TypeScript 编译之后的代码中,并没有限制 private 属性在外部的可访问性。
抽象类:抽象类是不允许被实例化的,只能用于被继承。
abstract
用于定义抽象类和其中的抽象方法。而且抽象类中的抽象方法必须被子类实现。
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');
给类加上 TypeScript 的类型很简单,与接口类似:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): string {
return `My name is ${this.name}`;
}
}
let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
class
定义类constructor
定义构造函数new
生成新实例extends
关键字实现继承super
关键字来调用父类的构造函数和方法getter
和 setter
可以改变属性的赋值和读取行为使用 static
修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用。
class Animal {
static isAnimal(a) {
return a instanceof Animal;
}
}
let a = new Animal('Jack');
// 调用方式,类名.静态方法
Animal.isAnimal(a); // true
static
关键字class Animal {
name = 'Jack'; // 实例属性
static num = 42; // 静态属性
constructor() {
// ...
}
}
接口的用途:
实现(implement)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements
关键字来实现。这个特性大大提高了面向对象的灵活性。
注意:一个类只能继承自另一个类;但一个类可以实现多个接口。
接口与接口之间可以是继承关系。
interface Alarm {
alert();
}
interface LightableAlarm extends Alarm {
lightOn();
lightOff();
}
接口可以继承类:
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
可以使用接口的方式来定义一个函数需要符合的形状:
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
有时候,一个函数还可以有自己的属性和方法:
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;
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
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']
createArray(3, 'p'); //在调用的时候,可以指定它具体的类型为 string。当然,也可以不手动指定,而让类型推论自动推算出来
// 在函数名后添加了 ,其中 T 用来指代任意输入的类型,在后面的输入 value: T 和输出 Array 中即可使用了。
定义泛型的时候,可以一次定义多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法。这时,我们可以对泛型进行只允许这个函数传入那些符合要求的变量。
interface Lengthwise {
length: number;
}
function logginIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
// 使用了 extends 约束了泛型 T 必须符合接口 Lengthwise 的形状,也就是必须包含 length 属性。
// 如果传入的arg不包含length,就会在编译阶段报错了。
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']
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; };
当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。
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;
}
如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型。
// 可以使用重载定义多个函数类型
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('');
}
}
接口中的属性在合并时会简单的合并到一个接口中,合并的属性的类型必须是唯一的。接口中方法的合并,与函数的合并一样。
interface Alarm {
price: number;
alert(s: string): string;
}
interface Alarm {
weight: number;
alert(s: string, n: number): string;
}
// 合并成
interface Alarm {
price: number;
weight: number;
alert(s: string): string;
alert(s: string, n: number): string;
}
类的合并与接口的合并规则一致。