TypeScript相关知识点归纳

前言

TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持,它由 Microsoft 开发,代码开源于 GitHub 上。

第一个版本发布于 2012 年 10 月,经历了多次更新后,现在已成为前端社区中不可忽视的力量,不仅在 Microsoft 内部得到广泛运用,而且 Google 的 Angular2 也使用了 TypeScript 作为开发语言。
TypeScript :官方手册及其非官方中文版
前置知识:JavaScript、ECMAScript6

简介

官网的定义:

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open source.

翻译成中文即是:

TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。TypeScript 是开源的。

优势:

  1. 增加代码可读性和可维护性
  2. 拥有活跃的社区
  3. TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可
  4. 即使不显式的定义类型,也能够自动做出类型推论
  5. 可以定义从简单到复杂的一切类型
  6. 即使 TypeScript 编译报错,也可以生成 JavaScript 文件
  7. 兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以> 编写单独的类型文件供 TypeScript 读取

TypeScript的安装:

npm install -g typescript

TypeScript文件编译,使用tsc命令:

tsc hello.ts

支持TypeScript的编辑器:

Visual Studio Code(推荐使用)
Sublime Text
Atom
WebStorm
Vim
Emacs
Eclipse
Visual Studio 2015
Visual Studio 2013

基础

  • 原始数据类型
  • 任意值
  • 类型推论
  • 联合类型
  • 接口
  • 数组
  • 函数
  • 类型断言
  • 声明文件
  • 内置对象

原始数据类型

JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。

原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 Symbol。

布尔值

布尔类型,使用boolean定义

let isDone: boolean = false;
//或者
let createdByBoolean: boolean = Boolean(1);

布尔对象,使用Boolean构造函数

let createdByNewBoolean: Boolean = new Boolean(1);
数值

数值类型,使用number定义

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法(ES6)
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法(ES6)
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

数值对象,使用Number构造函数

字符串

字符串类型,使用string定义:

let myName: string = 'Xcat Liu';
let myAge: number = 25;

// 模板字符串
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`;
// 模板字符串编译结果
//var sentence = "Hello, my name is " + myName + //".\nI'll be " + (myAge + 1) + " years old next //month.";

其中 ` 用来定义 ES6 中的模板字符串,${expr} 用来在模板字符串中嵌入表达式。

空值

JavaScript 没有空值(Void)的概念,在 TypeScirpt 中,可以用 void 表示没有任何返回值的函数:

function alertName(): void {
  alert('My name is xcatliu');
}

声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:

let unusable: void = undefined;
Null 和 Undefined

在 TypeScript 中,可以使用 null 和 undefined 来定义这两个原始数据类型:

let u: undefined = undefined;
let n: null = null;

undefined 类型的变量只能被赋值为 undefined,null 类型的变量只能被赋值为 null。

与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量,而 void 类型的变量不能赋值给 number 类型的变量:

// 这样不会报错
let num: number = undefined;
// 这样也不会报错
let u: undefined;
let num: number = u;

任意值

  • 如果是一个普通类型,在赋值过程中改变类型是不被允许的,但
    如果是 any 类型,则允许被赋值为任意类型。

  • 任意值(Any)用来表示允许赋值为任意类型。

  • 在任意值上访问任何属性都是允许的,也允许调用任何方法。

  • 声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值

  • 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型

let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
let anyThing: any = 'hello';
console.log(anyThing.myName);
anyThing.setName('Jerry Lee').sayHello();

类型推论

如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。

TypeScript 2.1 中,编译器会考虑对 myFavoriteNumber 的最后一次赋值来检查类型。

let myFavoriteNumber = 'seven';
//等价于let myFavoriteNumber: string = 'seven';

联合类型

  • 联合类型(Union Types)表示取值可以为多种类型中的一种。
  • 联合类型使用 | 分隔每个类型。
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

这里的 string | number 的含义是,允许 myFavoriteNumber 的类型是 string 或者 number,但是不能是其他类型。

访问联合类型的属性和方法:

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法。

function getString(something: string | number): string {
  return something.toString();
  //ERROR return something.length;
}

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型

接口

TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

interface Person {
  name: string;
  age: number;
}

使用接口类型赋值的时候,变量的形状必须和接口的形状保持一致。属性数量和类型保持一致,不允许添加未定义的属性。

let xcatliu: Person = {
  name: 'Xcat Liu',
  age: 25,
};

可选属性,有时我们希望不要完全匹配一个形状,那么可以用可选属性。使用”?”

interface Person {
  name: string;
  age?: number;
}

let xcatliu: Person = {
  name: 'Xcat Liu',
};

任意属性,有时候我们希望一个接口允许有任意的属性

interface Person {
  name: string;
  age?: number;
  [propName: string]: any;
}

let xcatliu: Person = {
  name: 'Xcat Liu',
  website: 'http://xcatliu.com',
};

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

interface Person {
  name: string;
  age?: number;
  [propName: string]: string;
}

let xcatliu: Person = {
  name: 'Xcat Liu',
  age: 25,//error,25是number类型,不是string的子属性
  website: 'http://xcatliu.com',
};

只读属性,对象中的一些字段只能在创建的时候被赋值,用 readonly 定义只读属性。

注意:只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候

interface Person {
  readonly id: number;
}
//第一次给对象赋值,激活只读约束
let xcatliu: Person = {
  id: 89757,
};

xcatliu.id = 9527;//error

数组

定义数组:有多种定义方式
1. 「类型 + 方括号」表示法

let fibonacci: number[] = [1, 1, 2, 3, 5];

数组的项中不允许出现其他的类型
2. 使用数组泛型(Generic) Array 来表示数组

let fibonacci: Array<number> = [1, 1, 2, 3, 5];
  1. 用接口表示数组
interface NumberArray {
  [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
//NumberArray 表示:只要 index 的类型是 number,那么值的类型必须是 number。
  1. any 表示数组中允许出现任意类型
let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }];
  1. 类数组
    常见的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection (内置对象)等
function sum() {
  let args: IArguments = arguments;
}

函数

(1)定义函数的两种方式:函数申明,函数表达式

  1. 函数申明
function sum(x: number, y: number): number {
  return x + y;
}

调用时输入多余的(或者少于要求的)参数,是不被允许的。

  1. 函数表达式
let mySum = function (x: number, y: number): number {
  return x + y;
};

上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 mySum,是通过赋值操作进行类型推论而推断出来的。

(2)接口中的函数

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

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

(3)函数中参数
1. 可选参数(使用”?”表示)

function buildName(firstName: string, lastName?: string) {}

注意:可选参数后不允许出现必须参数(参数默认值除外)
2. 参数默认值

function buildName(firstName: string = 'Xcat', lastName: string) {}

在 ES6 中,TypeScript 会将添加了默认值的参数识别为可选参数,此时不受可选参数位置限制
3. 剩余参数(rest参数)

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
  });
}
//items 是一个数组。所以我们可以用数组的类型来定义它
function xpush(array: any[], ...items: any[]) {
  items.forEach(function(item) {
    array.xpush(item);
  });
}

**注意:**rest参数只能是函数中最后一个参数
(4)函数重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {...}

类型断言

类型断言(Type Assertion)可以用来绕过编译器的类型推断,手动指定一个值的类型(即程序员对编译器断言)。

类型断言不是类型转换。

(1)语法:

<类型>值

// 或

值 as 类型

// 在TSX语法 (React的JSX语法的TS版)中必须用后一种

(2)例子:使用类型断言,将一个联合类型的变量指定为一个更加具体的类型。

function toBoolean(something: string | number): boolean {
  return <string>something;
}

function getLength(something: string | number): number {
  let length=something.length;//error
  let xlength=something.length;//OK
}

注意:只能断言成联合类型中存在的类型。

声明文件

当时用第三方库时,我们需要引用它的声明文件。

申明语句:使用declare关键字来定义类型

如:我们需要使用第三方库Jquery获取一个id是foo的元素

declare var jQuery: (string) => any;//类型声明
jQuery('#foo');//单独书写时typescript并不识别$或者jquery

通常我们会将类型声明放在一个单独的文件中,约定声明文件以.d.ts为后缀。

// jQuery.d.ts

declare var jQuery: (string) => any;

使用声明文件:用「三斜线指令」表示引用了声明文件

/// <reference path="./jQuery.d.ts" />

jQuery('#foo');

TypeScript2.0推荐使用@types来管理,即使用nm安装对应声明模块

如:npm install @types/jquery --save-dev

官方搜索声明文件链接:http://microsoft.github.io/TypeSearch/

内置对象

内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。

这些定义文件都在 TypeScript 核心库的定义文件中:

ES标准提供的内置对象有:
Boolean、Error、Date、RegExp 等

DOM 和 BOM 提供的内置对象有:
Document、HTMLElement、Event、NodeList 等

注意: Node.js不是内置对象的一部分,需要引入声明文件:
npm install @types/node --save-dev

进阶

  • 类型别名
  • 字符串字面量类型
  • 元组
  • 枚举
  • 类与接口
  • 泛型
  • 声明合并
  • 扩展阅读

类型别名

使用 ‘type’ 创建类型别名,类型别名用来给一个类型起个新名字。类型别名常用于联合类型。

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {...}

字符串字面量

字符串字面量类型用来约束取值只能是某几个字符串中的一个。

类型别名与字符串字面量类型都是使用 ‘type’ 进行定义。

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'

元组

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

元组起源于函数编程语言(如 F#),在这些语言中频繁使用元组。

(1)定义元组:
let tuple: [string, number];

let xtuple: [string, number] = ['Xcat Liu', 25];
(2)元组的赋值:

1.定义元组时赋值

let xtuple: [string, number] = ['Xcat Liu', 25];

2.使用元组索引赋值,索引从0开始,可只赋值其中一项

xcatliu[0] = 'Xcat Liu';

3.直接对元组赋值,需要提供所有元组类型中指定的项。

let xcatliu: [string, number];
xcatliu = ['Xcat Liu', 25];

4.越界元素赋值

当赋值给越界的元素时,它类型会被限制为元组中每个类型的联合类型。

let xcatliu: [string, number];
xcatliu = ['Xcat Liu', 25, 'http://xcatliu.com/'];
//'http://xcatliu.com/'满足联合类型 string | number
(3)元组元素的访问

1.使用索引访问

let xcatliu: [string, number] = ['Xcat Liu', 25];
xcatliu[0].slice(1);
xcatliu[1].toFixed(2);

2.访问越界元素

当访问一个越界的元素,也会识别为元组中每个类型的联合类型。(如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的属性或方法-联合类型)

let xcatliu: [string, number];
xcatliu = ['Xcat Liu', 25, 'http://xcatliu.com/'];

console.log(xcatliu[2].slice(1));//error

// index.ts(4,24): error TS2339: Property 'slice' does not exist on type 'string | number'.

枚举

(1)定义

使用 ‘enum’ 关键字来定义枚举类型;

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
(2)枚举项赋值

1.默认赋值

枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射。如编译结果所示

var Days;
(function (Days) {
  Days[Days["Sun"] = 0] = "Sun";
  Days[Days["Mon"] = 1] = "Mon";
  Days[Days["Tue"] = 2] = "Tue";
  Days[Days["Wed"] = 3] = "Wed";
  Days[Days["Thu"] = 4] = "Thu";
  Days[Days["Fri"] = 5] = "Fri";
  Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));

2.手动赋值

(1)使用数字类型

enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};

此时,未手动赋值的枚举项会接着上一个枚举项以1递增(使用小数或负数也如此),即:Tue=2,Wed=3,Thu=4,Fri=5,Sat=6。

枚举项的值重复仍可通过编译,只是值被覆盖了。

enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};
//原先Days[3]的值为Sun,现在为Wed

(2)不使用数字

手动赋值的枚举项可以不是数字,此时需要使用类型断言来让tsc无视类型检查 (编译出的js仍然是可用的)。

enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S"};

编译结果:

var Days;
(function (Days) {
    Days[Days["Sun"] = 7] = "Sun";
    Days[Days["Mon"] = 8] = "Mon";
    Days[Days["Tue"] = 9] = "Tue";
    Days[Days["Wed"] = 10] = "Wed";
    Days[Days["Thu"] = 11] = "Thu";
    Days[Days["Fri"] = 12] = "Fri";
    Days[Days["Sat"] = "S"] = "Sat";
})(Days || (Days = {}));
3.枚举项类型

枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。

(1)常数项
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
(2)计算所得项
enum Color {Red, Green, Blue = "blue".length};
常数枚举

使用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 */];
外部枚举

(1)使用 declare enum 定义外部枚举。

(2)declare 定义的类型只会用于编译时的检查,编译结果中会被删除。

(3)外部枚举与声明语句一样,常出现在声明文件中。

declare enum Directions {
  Up,
  Down,
  Left,
  Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

上例的编译结果是:

(4)declare 和 const 可以同时使用。

var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
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中类的用法

访问修饰符:public、private、protected

public,允许直接访问实例的属性:

class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom

private修饰的属性是无法直接存取的,所修饰的属性和方法不允许在子类中访问:

class Animal {
  private name;
  public constructor(name) {
    this.name = name;
  }
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';//error

class Cat extends Animal {
  constructor(name) {
    super(name);//error
    console.log(this.name);
  }
}

protected 修饰的属性或方法,允许在子类中被访问。

抽象类

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

类与接口

一个类可以实现多个接口
interface Alarm {
  alert();
}

interface Light {
  lightOn();
  lightOff();
}

class Car implements Alarm, Light {
  alert() {
    console.log('Car alert');
  }
  lightOn() {
    console.log('Car light on');
  }
  lightOff() {
    console.log('Car light off');
  }
}
接口继承接口
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};

泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

泛型使用在函数上

(1)定义返回值

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

createArray<string>(3, 'x'); // ['x', 'x', 'x']

(2)多个类型参数

定义泛型的时候,可以一次定义多个类型参数:

function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

泛型约束

(1)在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:

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

interface Lengthwise {
  length: number;
}

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

loggingIdentity(7);//error,传入的 arg 不包含 length

(2)多个类型参数之间也可以互相约束:

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 });

上例中,我们使用了两个类型参数,其中要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段。

泛型接口

(1)反省参数在接口中的函数上:

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

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

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

(2)泛型参数在接口名上:

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

let createArray: CreateArrayFunc<any>;//注意这里需要定义泛型的类型
createArray = function<T>(length: number, value: T): Array<T> {
  let result = [];
  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 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;
}

扩展阅读

此处记录了官方手册中文版)中包含,但是本书未涉及的概念。

我认为它们是一些不重要或者不属于 TypeScript 的概念,所以这里只给出一个简单的释义,详细内容可以点击链接深入理解。

参考书:https://github.com/xcatliu/typescript-tutorial

参考入门教程:http://es6.ruanyifeng.com/

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值