TypeScript相关知识点归纳

来源:互联网 发布:无约束最优化问题 编辑:程序博客网 时间:2024/06/11 18:35

前言

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.tsdeclare 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); // Jacka.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); // Jacka.name = 'Tom';//errorclass 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 的概念,所以这里只给出一个简单的释义,详细内容可以点击链接深入理解。

  • Never(中文版):永远不存在值的类型,一般用于错误处理函数
  • Variable Declarations(中文版):使用 letconst 替代 var,这是 ES6 的知识
  • this:箭头函数的运用,这是 ES6 的知识
  • Using Class Types in Generics(中文版):创建工厂函数时,需要引用构造函数的类类型
  • Best common type(中文版):数组的类型推论
  • Contextual Type(中文版):函数输入的类型推论
  • Type Compatibility(中文版):允许不严格符合类型,只需要在一定规则下兼容即可
  • Advanced Types(中文版):使用 & 将多种类型的共有部分叠加成一种类型
  • Type Guards and Differentiating Types(中文版):联合类型在一些情况下被识别为特定的类型
  • Discriminated Unions(中文版):使用 | 联合多个接口的时候,通过一个共有的属性形成可辨识联合
  • Polymorphic this types(中文版):父类的某个方法返回 this,当子类继承父类后,子类的实例调用此方法,返回的 this 能够被 TypeScript 正确的识别为子类的实例。
  • Symbols(中文版):新原生类型,这是 ES6 的知识
  • Iterators and Generators(中文版):迭代器,这是 ES6 的知识
  • Namespaces(中文版):避免全局污染,现在已被 ES6 Module 替代
  • Decorators(中文版):修饰器,这是 ES7 的一个提案
  • Mixins(中文版):一种编程模式,与 TypeScript 没有直接关系,可以参考 ES6 中 Mixin 模式的实现

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

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

原创粉丝点击