关于TypeScript

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

TypeScript优缺点

  • TypeScript 增加了代码的可读性和可维护性
  • TypeScript 非常包容

    typeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可
    即使不显式的定义类型,也能够自动做出类型推论
    可以定义从简单到复杂的几乎一切类型
    即使 TypeScript 编译报错,也可以生成 JavaScript 文件
    兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取
  • 有一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念,可能会增加开发成本

快速上手

命令行安装:

npm install -g typescript

hello world ,新建index.ts文件:

function sayHello(person: string) {
    return 'Hello, ' + person;
}

let user = 'Tom';
console.log(sayHello(user));

运行:

tsc index.ts

TypeScript 中,使用 : 指定变量的类型: 的前后有没有空格都可以。

TypeScript 只会进行静态检查,如果发现有错误,编译的时候就会报错。

但还是会生成对应的.js文件,如果要在报错的时候终止 js 文件的生成,可以在 tsconfig.json 中配置 noEmitOnError 即可。关于配置项

基础

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

原始数据类型

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

指定这些变量类型:


let isShow: boolean = false;   //布尔值  new Boolean(true) 不可 new出来是对象  可以Boolean()
let name: string = '君莫笑';   //字符串
let age: number = 5;   //数值
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;     //hex 十六进制
let binaryLiteral: number = 0b1010;  //binary 二进制 
let octalLiteral: number = 0o744;   //octal  八进制  OCT
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

let u: undefined = undefined;
let n: null = null;
// null和undefined是所有类型的子类型,也就是说其他类型可以设值为null或undefined(void除外)
let account: string = u;

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

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

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

let unusable: void = undefined;

任意值, 用any代替let sth: any = null;

类型推论

其实就是根据之前定义时的初值类型推断出变量类型,若没有赋值,则识别为any,如以下代码会报错:

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

联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种,使用 | 分隔每个类型

let myFavoriteNumber: string | number 
//允许 myFavoriteNumber 的类型是 string 或者 number,但是不能是其他类型

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

function getMyValue(val: string | number ) {
  // return val.length;  //报错
  return val.toString();
}

当变量赋值时,TypeScript会根据类型推论推断变量类型并调用相应的方法属性等:

myFavoriteNumber = 'number';
console.log('myFavoriteNumber: ', myFavoriteNumber.length);

myFavoriteNumber = 7;
// console.log('myFavoriteNumber: ', myFavoriteNumber.length); //报错

对象的类型——接口

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

面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)实现(implement)
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

接口一般首字母大写。有的编程语言中会建议接口的名称加上 I 前缀。

  1. 赋值的时候,变量的形状必须和接口的形状保持一致,多了属性和少了属性都会错误提示;
  2. 可选属性:如果不想要完全匹配一个形状,那么可以用可选属性,在属性后加?表示该属性在实例化时可有可无,可之后据情况添加;

    以上两种情况仍然不允许添加未定义的属性
  3. 未定义属性:使用 [propName: string] 定义了任意string 类型属性(只能是stringnumber)的值。 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:也就是说任意属性(若干个)的类型必须是声明属性的超集,任意类型必须包括已声明的所有类型包括可选属性
  4. 只读属性: 可以用 readonly 定义只读属性,即希望对象中的一些字段只能在创建的时候被赋值。

    注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
        interface IStudent {
          readonly id?: number,
          name: string;
          age: number;
          school: string;
          punishment?: boolean;  //可选属性
          [propName: string]: any;  //一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
        }
        
        let Jack: IStudent = {
          // id: 410184,
          name: 'jack',
          age: 18,
          school: '浙江大学',
        };
        // Jack.id = 5;   //只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候报错
        Jack.punishment = true;
        Jack.dad = 'zhanghao';
        console.log(Jack);

数组类型

  1. 「类型 + 方括号」表示法

    let fibonacci: number[] = [1, 1, 2, 3, 5]; //出现其他类型报错,push其他类型也会报错
    let arrAny: any[] = [ 3, 'zh', { name: 'zh' } ]; //任意类型的数组
    
  2. 数组泛型(Array Generic) Array<elemType> 来表示数组,关于泛型

    let arrNum: Array<number> = [1, 2, 3];
  3. 使用接口表示数组,其实只是构造了一个和数组结构差不多的对象。
        interface INumberArray {
          [index: number]: number;
        }
        let fibonacci: INumberArray = [1, 1, 2, 3, 5];
        let testObj: INumberArray = {
          1: 5,
          3: 6
        };  //都不会报错
        console.log(testObj.length);  //报错
          

4.类数组

interface IArguments {
    [index: number]: any;  //
    length: number;
}

function sum(a: number, b: number, c: number) {
    let args: IArguments = arguments;
    console.log(args); 
}
sum(3, 9, 18);//3 9 18

以上是我期待的结果,但是报错数字索引签名重复, 打印 agruments: [Arguments] { '0': 3, '1': 9, '2': 18 },看到键值是字符串,改IArguments接口 为

interface IArguments {
  [index: string]: any;
  length: number;
}

运行得预期。

函数类型

一般的ts函数定义:

function sum(x: number, y: number): number {
    return x + y;
}

函数表达式定义函数

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};

而不是

let mySum = function (x: number, y: number): number {
    return x + y;
};

在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型

注意,输入多余的(或者少于要求的)参数,是不被允许的,

可选参数:可在参数后加?表示可选参数,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了。
默认参数: 与js类似,不过TypeScript 会将添加了默认值的参数识别为可选参数。

function buildName(firstName: string = 'Tom', lastName?: string) {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');

剩余参数: 必须是最后一个参数。

重载: 重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。

比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'。
利用联合类型,我们可以这么实现:

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

然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
这时,我们可以使用重载定义多个 reverse 的函数类型

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

上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。

注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

Mr_zhang
395 声望10 粉丝

步步向“前”