1.概述

本质上是向JavaScript添加了可选的静态类型和基与类的面对对象的编程,同时支持诸如接口、命名空间、装饰器等属性。

2.基本类型

在TypeScript中提供一下基本的数据类型

  • 布尔类型(boolean)
  • 数字类型(number)
  • 字符串类型(string)
  • 数组类型(array)
  • 元组类型(tuple)
  • 枚举类型(enum)
  • 任意类型(any)
  • null undefined
  • void
  • never

js有的暂时不介绍

2.1 元组类型

元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同。
let x: [string, number] = ['angular', 25];

2.2 枚举类型

枚举是一个可被命名的整型常数的集合,枚举类型为集合成员赋予有意义的名称,增强可读性
比如说 有固定选项的 男女 红绿灯

  1. enum Color { Red, Green, Blue };
  2. let c: Color = Color.Blue;
  3. console.log(c); // 输出: 2

枚举默认下标是0,可以手动修改

  1. enum Color { Red = 2, Blue, Green = 6 };
  2. let c: Color = Color.Blue;
  3. console.log(c) // 输出 3

2.3 任意类型

任意值是TypeScript针对编程时类型不明确的变量使用的一种数据类型,常用于一下三种情况

  • 变量的值会动态变化时,比如用户的输入或第三方代码库,任意值可以让这些变量跳过编译阶段的类型检查
  • 改现有代码时,任意值允许在编译时可选择的包含或移除类型检查
  • 定义存储各种类型数据的数组时

    2.4 null 和undefined

    默认情况下, null和undefined时其他类型的子类型,可以赋值给其他类型,赋值后的类型就变成了null或undefined,致力于类型校验的TypeScript设计者们显然不希望这种类型变化给开发者带来额外的困扰

2.5 void

void表示没有任何类型,一个函数没有返回值时,意味着返回值的类型是void

2.6 never

never是其他类型的子类型,代表从不会出现的值。这意味着声明never类型的变量只能被never类型所赋值, 在函数中通常表现为抛出异常或无法执行到终点。

变量声明参考这里 基本与ES6差不多

3.函数

  1. function max(x: number, y: number): number {
  2. return x > y ? x : y;
  3. }
  4. let maxB = function (x: number, y: number): number { return x > y ? x: y };

3.1 可选参数 默认参数 剩余参数

3.1.1 可选参数

TS中调用函数的参数都是必须传的,但会遇到实际情况需要决定这个元素是否需要传入,可以在参数名旁边添加一个?

  1. function max(x: number, y?: number): number {
  2. if (y) {
  3. return x > y ? x : y;
  4. } else {
  5. return x
  6. }
  7. }

3.1.2 默认参数

还可以在给某给参数设置默认值,当被调用的时候如果没有给这个参数传值或者值为undefined时,这个参数的值就是默认值

  1. function max(x: number, y = 4): number {
  2. return x > y ? x : y;
  3. }

3.1.3 剩余参数

有时我们不知道有多少个参数传进来或者需要同时操作多个参数的时候,使用剩余参数,所有传进来的参数放到一个变量里面

  1. function sum(x: number, ...restOfNumber: number[]): number {
  2. let result = x;
  3. for(let i = 0;i < restOfNumber.length;i++){
  4. result += restOfNumber[i];
  5. }
  6. return result;
  7. }
  8. let result = sum(1,2,3,4,5);

剩余参数可以理解为个数不限的可选参数,参数个数为0到多个。剩余参数是一个真正的数组

3.2函数重载

函数重载通过为同一个函数提供多个函数类型定义来达到实现多种功能的目的。

  1. function css(config: {});
  2. function css(config: string, value: string);
  3. function css(config: any, value?: any) {}

这个函数有三个重载方法,编译器会根据参数类型来判断该调用哪个函数。TS的函数重载通过查找重载列表来实现匹配,根据定义的优先顺序来依次匹配,所以在实现重载方法时,建议把最精确的定义放在最前面。

4 类

传统JS的使用函数和基与原型(prototype)继承创建可重用的类,TS支持使用基与类的面对对象编程。

  1. class Car {
  2. engine: string;
  3. constructor(engine: string) {
  4. this.engine = engine;
  5. }
  6. drive(distanceInMeters: number = 0) {
  7. }
  8. }

4.1 继承与多态

封装、继承、多态是面对对象的三大特性。把汽车的行为写到一个类中,就是所谓的封装。在TypeScript中使用extends关键字可以实现继承

  1. class MotoCar extends Car {
  2. constructor(engine: string){ super(engine); }
  3. }
  4. class Jeep extends Car {
  5. constructor(engine: string){ super(engine); }
  6. drive(distanceInMeters: number = 100) {
  7. }
  8. }

MotoCar和Jeep是Car的子类,通过extends来继承父类,子类可以访问父类的属性和方法,也可以重写父类的方法。Jeep的drive()方法重写了Car的drive()方法,这有drive()方法在不同类中具有不同的功能,就是多态。

4.2 修饰符

在TypeScript里,每个成员默认为public,可以被自由的访问

  1. class Car {
  2. public engine: string;
  3. public constructor(engine: string) {
  4. this.engine = engine;
  5. }
  6. public drive(distanceInMeters: number = 0) {
  7. }
  8. }

4.2.1 privete 修饰符

当类成员被标记为private时,就不能在外部访问它

  1. class Car {
  2. private engine: string;
  3. constructor(engine: string) {
  4. this.engine = engine;
  5. }
  6. }
  7. new Car('petrol').engine;// 报错 engine属性是私有的,只能在类内部访问

4.2.2 protected 修饰符

protected修饰符与private修饰符很相似,protected成员在派生类中仍然可以访问。

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

注意,我们不能在 Person类外使用 name,但是我们仍然可以通过 Employee类的实例方法访问,因为 Employee是由 Person派生而来的。

构造函数也可以被标记成 protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。

readonly 修饰符
readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。

参数属性
给构造函数参数添加一个访问限定符。参数属性可以方便地让我们在一个地方定义并初始化类成员。

4.3 存取器

TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
下面来看如何把一个简单的类改写成使用 get和 set。 首先,我们从一个没有使用存取器的例子开始。

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

我们可以随意的设置 fullName,这是非常方便的,但是这也可能会带来麻烦。
下面这个版本里,我们先检查用户密码是否正确,然后再允许其修改员工信息。 我们把对 fullName的直接访问改成了可以检查密码的 set方法。 我们也加了一个 get方法,让上面的例子仍然可以工作。

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

我们可以修改一下密码,来验证一下存取器是否是工作的。当密码不对时,会提示我们没有权限去修改员工。
对于存取器有下面几点需要注意的:
首先,存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有 get不带有 set的存取器自动被推断为 readonly。 这在从代码生成 .d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。

4.4 静态属性

创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。 在这个例子里,我们使用 static定义 origin,因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候,都要在 origin前面加上类名。 如同在实例属性上使用 this.前缀来访问属性一样,这里我们使用 Grid.来访问静态属性。

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

4.5 抽象类

抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。

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

抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含 abstract关键字并且可以包含访问修饰符。

  1. abstract class Department {
  2. constructor(public name: string) {
  3. }
  4. printName(): void {
  5. console.log('Department name: ' + this.name);
  6. }
  7. abstract printMeeting(): void; // 必须在派生类中实现
  8. }
  9. class AccountingDepartment extends Department {
  10. constructor() {
  11. super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
  12. }
  13. printMeeting(): void {
  14. console.log('The Accounting Department meets each Monday at 10am.');
  15. }
  16. generateReports(): void {
  17. console.log('Generating accounting reports...');
  18. }
  19. }
  20. let department: Department; // 允许创建一个对抽象类型的引用
  21. department = new Department(); // 错误: 不能创建一个抽象类的实例
  22. department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
  23. department.printName();
  24. department.printMeeting();
  25. department.generateReports(); // 错误: 方法在声明的抽象类中不存在

5 接口

TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

5.1 属性类型接口

  1. interface FullName {
  2. firstName: string;
  3. secondName: string;
  4. }
  5. function printLabel(name: FullName){}
  6. let myObj = { age: 10, firstName: 'Jim', secondName: 'Raynor' }

上面的接口FullName包含两个属性,firstName和secondName,都是字符串类型。

  • 传给printLabel()方法的对象只要‘形式上’满足接口的要求即可,例如上例中的对象myObj必须包含一个firstName和secondName
  • 接口的类型检查器不会去检查属性的顺序,但要确保相应的属性存在且类型匹配

    5.2 函数类型接口

    1. interface encrypt {
    2. (val: string, salt: string): string
    3. }
    4. let md5: encrypt;
    5. md5 = function(val: string, salt: string){
    6. let encryptValue = doMd5(val, salt);
    7. return encryptValue
    8. }

    注意一下两点

  • 函数的参数名: 使用参数个数需与接口定义的参数相同,对应位置变量的数据类型需保持一致,参数名可以不一样

  • 函数返回值:函数的返回值类型与接口定义的返回值类型要一致

5.3 可索引类型

与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]或ageMap[“daniel”]。 可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。

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

TypeScript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用”100”(一个string)去索引,因此两者需要保持一致。

5.4 类类型接口

  1. interface Animal {
  2. name: string;
  3. setName(n: string): void;
  4. }
  5. class Dog implements Animal {
  6. name: string;
  7. setName(n: string){
  8. this.name = n;
  9. }
  10. constructor(n: string){}
  11. }

可以在接口中描述一个方法并在类里去具体实现它的功能。

5.5 接口扩展

和类一样,接口也可以实现互相扩展,既能将成员从一个接口复制到另一个里面,这样可以更灵活地将接口拆分到可复用的模块里

  1. interface Animal {
  2. eat(): void;
  3. }
  4. interface Person extends Animal {
  5. talk(): void;
  6. }
  7. class Programmer {
  8. coding(): void {}
  9. }
  10. class ITGirl extends Programmer implements Person {
  11. eat(){}
  12. talk(){}
  13. coding(){}
  14. }

6.装饰器

装饰器是一种特殊类型的声明,它可以被附加到类声明、方法、属性或参数上。装饰器由@符号紧接着一个函数名称,形如@expression,expression求值后必须是一个函数,在函数执行的时候装饰器的声明方法会被执行。装饰器是用来给附着的主体进行装饰,添加额外的行为。

6.1 方法装饰器

方法装饰器是在声明一个方法之前被声明的(紧贴着方法声明),它会被应用到方法的属性描述符上,可以用来监视、修改或替换方法定义。
方法装饰器会在运行时被当做函数调用,传入以下三个参数

  • target: 类的原型对象
  • propertyKey: 方法的名字
  • descriptor: 成员属性描述

    1. class TestClass {
    2. @log
    3. testMethod(arg: string) {
    4. return "logMsg" + arg;
    5. }
    6. }

    以下是@log的实现

    1. function log(target: Object, propertyKey: string,
    2. descriptor: TypedPropertyDescriptor<any>) {
    3. let origin = descriptor.value;
    4. descriptor.value = function(...args: any[]) {
    5. let result = origin.apply(this, args);
    6. return result;
    7. }
    8. return descriptor;
    9. }

    6.2 类装饰器

    一个类的装饰器的声明只在类声明的前面,类装饰器被应用于(be applied to)类的构造函数(the constructor of the class) 和可以被用来(can be used to)观察,修改或替换一个类的定义。不能在声明文件中(declaration file)和在任何其他环境上下文中(例如在声明类上)使用类装饰器。
    类装饰器的表达式将在运行时作为函数调用,装饰器的构造函数作为它的唯一参数。如果类装饰器返回了一个value,它将用提供的构造函数替换类的构造函数。
    note 如果选择了返回一个新的构造函数,您必须要注意维护原始原型。在运行时应用装饰器的逻辑不会为你做这件事。 ```typescript @sealed class BugReport { type = “report”; title: string; constructor(t: string){

    1. this.title = t;

    } }

function sealed(constructor: Function){ Object.seal(constructor); Object.seal(constructor.prototype) }

  1. <a name="gmzIs"></a>
  2. ## 6.3 参数装饰器
  3. 声明一个参数之前被声明的,它用于类的构造函数或方法声明。参数装饰器在运行时会被当作函数来调用。<br />包含三个参数
  4. - target: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  5. - propertyKey: 参数名称
  6. - parameterIndex: 参数在函数参数列表中的索引
  7. ```typescript
  8. class userService {
  9. login(@inject name: string) {}
  10. }
  11. function inject(target: any, properKey: string | symbol, parameterIndex: number) {
  12. }

6.4 属性装饰器

包含两个个参数

  • target: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • propertyKey: 属性名称

属性装饰器用来修饰类属性,声明和被调用方式跟其他装饰器类似,具体内容可以看文档
6.5装饰器组合
在TS中当多个装饰器应用在同一个声明上的时候,会进行如下的步骤

  • 从左到右(从上到下)依次执行装饰器函数,得到返回结果。
  • 返回结果会被当作函数,从左到右(从上到下)依次调用。 ```typescript function Component(component) { return (target: any) => {
    1. return target;
    } }

function Directive(directive){ return (target) => { return target; } } @Component({ selector: ‘person’, template: ‘person.html’ }) @Directive() class Person {}

  1. <a name="BC7pe"></a>
  2. # 7 泛型
  3. 在实际开发中,我们定义的API不仅需要考虑功能是否健全,还要考虑它的复用性,更多的时候需要支持不特定的数据类型,而泛型就是用来实现这样的效果。<br />比如有一个最小堆算法,需要同时支持数字和字符串,可以通过把集合类型改为任意类型来实现,但这样等于放弃了类型检查,我们希望的是返回的类型需要和参数类型一致
  4. ```typescript
  5. class MinHeap<T> {
  6. list:T[] = [];
  7. add(element: T): void {}
  8. min():T{
  9. return this.list.length ? this.list[0]: null;
  10. }
  11. }
  12. let heap1 = new MinHeap<number>();
  13. heap1.add(3);
  14. heap1.add(5);
  15. let heap2 = new MinHeap<string>();
  16. heap2.add('a');
  17. heap2.add('c');

在上面的例子中分别声明一个适用于数字类型和字符串类型的最小堆实例,给MinHeap类增加了类型变量T,用于捕获用户输入的数据类型以便于后边的跟踪和使用。
泛型也支持函数

  1. function zip<T1, T2>(l1: T1[], l2: T2[]): [T1, T2][] {
  2. let len = Math.min(l1.length, l2.length);
  3. let ret = [];
  4. for(let i = 0;i < len;i++){
  5. ret.push([l1[i], l2[i]]);
  6. }
  7. return ret;
  8. }