TypeScript

TypeScript

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

特点

可以在编译阶段就发现大部分错误,这总比在运行时候出错好

基础

// TypeScript 中,使用 : 指定变量的类型,: 的前后有没有空格都可以。
function alertName(): void {
    alert('My name is Tom');
}

原始数据类型

JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。
原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 Symbol。

  1. 布尔值boolean

    let isDone: boolean = false;

  2. 数值number

    let decLiteral: number = 6;

  3. 字符串string

    let myName: string = 'Tom';

  4. 空值void

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

    function alertName(): void {
        alert('My name is Tom');
    }
  5. nullundefined

    void 的区别是,undefinednull 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:

    let u: undefined = undefined;
    let n: null = null;
    
    let num: number = undefined;// 这样不会报错
    let u: void;
    let num: number = u;// 而 void 类型的变量不能赋值给 number 类型的变量

Any任意值

let myFavoriteNumber: any = 'seven';myFavoriteNumber = 7;

  1. 如果是一个普通类型,在赋值过程中改变类型是不被允许的,但如果是 any 类型,则允许被赋值为任意类型。
  2. 声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值
  3. 任意值上访问任何属性都是允许,也允许调用任何方法
  4. 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型

Object对象的类型 - 接口

在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。

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

interface Person { // 定义了一个接口 Person,接口一般首字母大写
    name: string; // 【确定属性】
    age: number;
}
let tom: Person = { // 定义对象变量tom,约束其形状(内部属性方法和数量)必须和接口 Person 一致,不多不少
    name: 'Tom',
    age: 25
};
interface Person {
        readonly id: number; //【只读属性】对象中的该字段 约束:只能在第一次赋值对象的时候被赋值该属性
    name: string;
    age?: number; // 【可选属性】 含义是该属性可以不存在。但仍不允许添加未定义的属性
      [propName: string]: any; // 【任意属性】任意取 string 类型的值。并且强调:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集,比如[propName: string]: string; 会让age 报错
}
let tom: Person = {
    name: 'Tom',
      gender: 'male'
};

Array数组的类型

  1. 「类型 + 方括号」let fibonacci: number[] = [1, 6, 2, 3, 5]; 不允许出现其他类型的项

  2. 数组范型(Array Generic) let fibonacci: Array<number> = [1, 1, 2, 3, 5];

  3. 用接口interface表示数组

    interface NumberArray {
        [index: number]: number; // 只要 index 的类型是 number,那么值的类型必须是 number
    }
    let fibonacci: NumberArray = [1, 1, 2, 3, 5];
  4. 用接口表示类数组

    function sum() {
        let args: number[] = arguments; // 报错,Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more;arguments 实际上是一个类数组,不能用普通的数组的方式来描述,而应该用接口
        let args: {
            [index: number]: number;
            length: number;
            callee: Function;
        } = arguments;
        let args: IArguments = arguments; // 事实上常见的类数组都有自己的接口定义,如 `IArguments`, `NodeList`, `HTMLCollection` 等 内置对象
    }
  5. 任意类型any

    let list:any[]=['xcatliu',25,{website:'https://xcatliu.com'}];

Function函数的类型

两种定义函数的方式 - 函数声明 和 函数表达式

  1. 函数声明

    // 1.函数声明
    function sum(x: number, y: number): number { // 要把输入和输出都考虑到约束数据类型
        return x + y;
    }
    sum(1, 2, 3); // 报错 入参个数必须不多不少
  2. 函数表达式

    // 2.函数表达式
    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;
    };// 注:在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。不同于ES6的箭头函数
  3. 接口 定义函数形状

    // 用接口定义函数的形状
    interface SearchFunc {
        (source: string, subString: string): boolean;
    }
    let mySearch: SearchFunc;
    mySearch = function(source: string, subString: string) {
        return source.search(subString) !== -1;
    }
// 【可选参数】:用?表示【可选参数】,可选参数必须接在必需参数后面
// 【默认值】:TypeScript 会将添加了【默认值】的参数识别为【可选参数】,不受「可选参数必须接在必需参数后面」的限制
// 【剩余参数】:ES6 中,可以使用 ...rest 的方式获取函数中的【剩余参数】(rest 参数),事实上,items 是一个数组。所以我们可以用数组的类型来定义它.rest 参数只能是最后一个参数
function buildName(firstName: string, midName: string = 'cat', array: any[], lastName?: string,  ...items: any[]) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
    items.forEach(function(item) {
        array.push(item);
    });
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
buildName('Tom', underfined, [], 1, 2, 3);
// 【重载】:允许一个函数接受不同数量或类型的参数时,作出不同的处理
function reverse(x: number): number;
function reverse(x: string): string; // 重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现
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('');
    }
} // TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

联合类型xx|oo

取值可以为多种类型中的一种

let myFavoriteNumber: string | number;

/*访问联合类型的属性或方法的情况:当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法*/
function getString(something: string | number): string {
      return something.length;// 报错,因为length不是 string 和 number 的共有属性
    return something.toString();
}
/*联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型*/
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 被推断成了 string 类型
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 编译时报错,被推断成了 number 类型

类型推论

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

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

  2. 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查

类型断言

手动指定一个值的类型(通常在[联合类型中不确定哪个类型时只能访问所有类型共有的属性和方法]使用)<类型>值值 as 类型

function getLength(something: string | number): number {
    if ((<string>something).length) { // 或 if ((something as string).length)
        return (<string>something).length;
    } else {
        return something.toString().length;
    }
}

声明文件

声明语句:当我们想使用第三方库 jQuery时,我们需要使用 declare var来定义它的类型,例:declarevarjQuery:(selector:string)=>any;

声明文件:通常我们会把声明语句放到一个单独的文件(jQuery.d.ts)中,这就是声明文件,必须以 .d.ts为后缀;

推荐的是使用 @types统一管理第三方库的声明文件,例:npminstall@types/jquery --save-dev

手动写声明文件具体操作

创建一个 types目录,专门用来管理自己写的声明文件,将 foo的声明文件放到 types/foo/index.d.ts中。这种方式需要配置下 tsconfig.json中的 pathsbaseUrl字段。

{
    "compilerOptions": {
        "module": "commonjs",
        "baseUrl": "./",
        "paths": {
            "*": ["types/*"]
        }
    }
}

内置对象

Boolean Error Date RegExp

Document HTMLElement Event NodeList 等DOM、BOM对象

let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;

进阶

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) {
    // do something
}
handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick'
// index.ts(7,47): error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.

Tuple元组

数组合并相同类型的对象,而元组(Tuple)合并不同类型的对象,并且当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型

let tom:[string,number]=['Tom',25];

enum枚举

用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。枚举成员会被赋值为从 0开始递增的数字,同时也会对枚举值到枚举名进行反向映射

enumDays {Sun,Mon,Tue,Wed,Thu,Fri,Sat};

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

enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat}; // 给枚举项手动赋值; Days["Sat"] === 6
enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S"}; // 手动赋值的枚举项可以不是数字,此时需要使用类型断言来让 tsc 无视类型检查 (编译出的 js 仍然是可用的) Days["Fri"] = 12;
enum Days {Sun = 7, Mon = 1.5, Tue, Wed, Thu, Fri, Sat}; // 手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为1 Days["Tue"] === 2.5

enum Color {Red, Green, Blue = "blue".length}; // 计算所得项
  • 普通枚举

    即上述例子

  • 常数枚举const enum

    会在编译阶段被删除,并且不能包含计算成员,如果包含了计算成员会在编译阶段报错

  • 外部枚举declare enum

    只会用于编译时的检查,编译结果中会被删除。外部枚举与声明语句一样,常出现在声明文件中

  • 同时常数和外部也是可以的declare const enum

class

  • 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
  • 对象(Object):类的实例,通过 new生成
  • 面向对象(OOP)的三大特性:封装、继承、多态
  • 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
  • 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
  • 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 CatDog都继承自 Animal,但是分别实现了自己的 eat方法。此时针对某一个实例,我们无需了解它是 Cat还是 Dog,就可以直接调用 eat方法,程序会自动判断出来应该如何执行 eat
  • 存取器(getter & setter):用以改变属性的读取和赋值行为
  • 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public表示公有属性或方法
  • 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
  • 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
/**
在原生js中
*/
class Animal { // 使用 class 定义类
    constructor(name) { // 使用 constructor 定义构造函数
        this.name = name;
    }
      name = 'Jack'; // ES7 提案中 直接在类里面定义实例属性
        static num = 42; // ES7 提案中 使用 static 定义一个静态属性
    sayHi() {
        return `My name is ${this.name}`;
    }
      /*存取器*/
      get name() { // 使用 getter 和 setter 可以改变属性的赋值和读取行为
        return 'Jack';
    }
    set name(value) {
        console.log('setter: ' + value);
    }
      /*静态方法*/
      static isAnimal(a) { // 它们不需要实例化,而是直接通过类来调用 
        return a instanceof Animal;
    }
}
let a = new Animal('Jack'); // setter: Kitty; 构造并且走set方法
console.log(a.sayHi()); // My name is Jack
a.name = 'Tom'; // setter: Tom;
console.log(a.name); // Jack; 走get方法
Animal.isAnimal(a); // true; 走静态方法
a.isAnimal(a); // TypeError: a.isAnimal is not a function
/* 继承 */
class Cat extends Animal { // 使用 extends 关键字实现继承
    constructor(name) {
        super(name); // 子类中使用 super 关键字 调用父类的构造函数constructor
        console.log(this.name);
    }
    sayHi() {
        return 'Meow, ' + super.sayHi(); // 使用 super 关键字 调用父类的 sayHi()
    }
}
let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom

修饰符

TypeScript 可以使用三种访问修饰符和一个关键字

  • public 默认,修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public
  • private修饰的属性或方法是私有的,不能在声明它的类的外部访问
  • protected修饰的属性或方法是受保护的,它和 private类似,区别是它在子类中也是允许被访问的
  • readonly 只读属性关键字,相比private可以访问不可赋值,只允许出现在属性声明或索引签名中
class Animal {
    public name;
      /* public constructor (public name) { // 修饰符还可以使用在构造函数参数中,等同于类中定义该属性,使代码更简洁。 */
    public constructor(name) { // 当构造函数修饰为 private 时,该类不允许被继承或者实例化;当构造函数修饰为 protected 时,该类只允许被继承:
        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;
    }
}
class Cat extends Animal {
    constructor(name) {
        super(name); 
        console.log(this.name); // 报错,在子类中也是不允许访问的;而如果是用 protected 修饰,则允许在子类中访问:
    }
}
let a = new Animal('Jack');
console.log(a.name); // Jack 虽然也报错,但是需要注意的是,TypeScript 编译之后的代码中,并没有限制 private 属性在外部的可访问性
a.name = 'Tom'; // 报错,不能在类外赋值

/* readonly */
class Animal {
    // readonly name;
    public constructor(public readonly name) { // 当和其他访问修饰符同时存在的话,需要写在其后面
        this.name = name;
    }
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom'; // 报错

抽象类abstract class

  • 抽象类 不允许被实例化
  • 抽象类中的抽象方法必须被子类实现
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

类实现接口

实现(implements)是面向对象中的一个重要概念。

一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements关键字来实现。这个特性大大提高了面向对象的灵活性。

接口(interfaces)不止用于定义对象的类型,和定义函数形状,在这里还可以用于实现(implements)。

interface Alarm {
    alert();
}
interface Light {
    lightOn();
    lightOff();
}
class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}
class Car implements Alarm, Light { // 一个类可以实现多个接口
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

/* 接口继承接口 */
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 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> { // 在函数名后添加 <T>,其中 T 用来指代任意输入的类型,在后面的输入 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']
/* 定义多个类型参数 */
// function createArray<T = string>(length: number, value: T): Array<T> { // 可以给泛型参数指定默认类型
function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]

泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它(泛型变量)的属性或方法,这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length属性的变量

interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T { // 使用了 extends 约束泛型 T 必须符合接口 Lengthwise 的形状,也就是必须包含 length 属性
    console.log(arg.length);
    return arg;
}
/* 例2 多个类型参数之间也可以互相约束 */
function copyFields<T extends U, U>(target: T, source: U): T { // 要求 T 继承 U,保证 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 });

泛型接口

含有泛型的接口定义一个函数的形状

interface CreateArrayFunc {
    <T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc;
/* 把泛型参数提前到接口名上
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 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;
}

// 并且合并的重名属性的类型必须是相同的否则报错
interface Alarm {
    price: number;
}
interface Alarm {
    price: number;  // 虽然重复了,但是类型都是 `number`,所以不会报错
    weight: number;
}

类合并

有处矛盾,待更新


   转载规则


《TypeScript》 Bryan Who 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
小米-刷机 小米-刷机
小米4 刷机最近因为旧的安卓备用机硬件瓦特了,现在二手不到200,维修费用70,算了,又入手一个刷机神器-小米4,几年不碰刷机了,这次摸索花了一天时间,折腾! 一、刷入recovery(TWRP) 官网找到自己的设备 坑:小米4代号是can
2019-09-02
下一篇 
gulp gulp
静态资源打包工具 Gulp介绍// 使用gulp自动 将静态文件目录 上传到服务器 const gulp = require('gulp'); const ftp = require( 'vinyl-ftp' ); gulp.task('
2019-08-29
  目录