TypeScript学习

1. TypeScript介绍

Typescript是由微软公司开发的用于扩展JavaScript语言的一种语言。目前浏览器不能直接解析typescript,需要将TS转变为JS文件才可以,TS可以被编译为任意版本的JS。

2. TS的开发环境搭建

  1. 下载nodeJS,nodeJS推荐使用nvm进行管理。
  2. 使用npm全局安装typescriptnpm i -g typescript
  3. 使用tsc将TS文件编译为JS文件,常用的语法为:tsc -w test.ts,此时在对应的目录下会新建编译后的JS文件,-w用来监听源文件的变化,实时进行编译。

3. TS基础知识

3.1 基本类型

TS可以在声明变量时为变量指明对应的类型。以下四种方式均可以让TS知道声明的变量对应的数据类型,从而进行限制。 类型声明时也可以使用|进行连接,其表示变量可以被赋予多种数据类型。

let name:string;
let name:string='zahngsan';
// 变量声明和赋值同时进行,TS可自动对变量进行类型检查
let name="123"

类型声明通常用于函数形参以及函数返回值。

function(name:string,age:number):number{
}

我们可以指定的类型包括:

  • number
  • string
  • boolean
  • 字面量
  • any,any表示任意类型,相当于关闭该变量的TS类型检测
  • unknown,表示未知类型。unknown类型的变量不能赋值给其他变量。
    • 如果想要为其他变量赋值的话,常用的有两种解决方法:
      • 断言,使用变量 as type
      • 使用<type>变量
  • void,用于函数中,表示函数无返回值。
  • never,用于函数表示永远不会返回结果
  • object,表示返回对象。一般很少使用,可以直接使用{}来限定对象中的属性。如果在声明的过程中设置了若干属性,在对象赋值的时候如果缺少某一属性值,会发生报错。此时可通过以下方式将对应的属性设置为可选属性。
let b = {name:string, age?:number}

除此之外,如果在对象中有很多属性不确定的情况,也可以使用[propName:string]:any来声明任意类型的属性。

let obj:{name:string,[propName:string]:any};
  • Function,Function其实也和Object无很大差别。如果我们想要设置函数结构的类型,可以使用类似箭头函数的结构来实现。
let fun =(a:number, b:bumber)=>number
  • array,array的话,TS我们通常用来存储相同类型的内容。数组有两种表示方式。
//表示定义字符串类型的数组1let arr:string[];
(2let arr1:Array<number>;
  • tuple,元组。即固定长度的数组。
// 声明固定长度的内容
let h:[string,string]
  • enum,枚举。枚举通常用来存储一些固定的值,比如性别、状态等。可以将所有可能的情况列出来,开发人员可以直接观察到变量表示的意思,而不用记忆变量不同值代表的不同含义
enum Gender={
    Male=0,
    Female=1
}

let student:{name:string,gender:Gender};
student={
    name:'xiaoli',
    gender:Gender.Male
}

3.2 TS编译选项

  1. tsc origin.tsx -w,可以监听对应TS文件的变化,实时编译为JS文件
  2. 上述指令只能对单个文件进行编译。如果想要监听所有TS文件变化并进行编译,需要在项目的tsconfig.json中进行配置。当项目下创建了该文件以后,使用tsc就可以监听所有TS文件的编译情况了。

tsconfig.json中可以写注释,是ts编译器的配置文件。

下面介绍tsconfig.json支持的配置项:

  1. include include表示项目中的哪些TS文件会被编译器编译。

  2. exclude exclude,表示这些路径下的TS文件不会被编译成JS。

  3. extends extends定义被继承的配置文件,即我们当前的json文件会继承其他文件中的配置选项。

  4. files files表示需要编译哪些文件。

  5. compilerOptions,编译器的选项。

    • target:表示JS被编译的版本。默认值是ES3,可支持ES6、ES 2016....
    • module:指定要使用的模块化规范。包括ES6。
    • lib:指定项目中要使用的库。一般不需要改动
    • outDir:编译得到的JS文件的输出目录。一般为‘/dist’
    • outFile:可以将编译后得到的JS合并到一个文件中。
    • allowJs:是否对JS文件进行编译,默认是false
    • checkJs:是否检查JS代码
    • removeComments:是否移除注释
    • noEmit:不生成编译后的文件
    • noEmitOnError:当TS代码中有错误时,不编译为JS文件
    • 语法相关的属性
      • alwaysStrict:用来设置编译后的文件是否使用严格模式。默认是false。JS严格模式会在文件头部添加"use strict",当启动模块功能时,则默认进入严格模式
      • noImplicitAny:检查是否不允许隐式的any
      • noImplicitThis:检索是否不允许类型不明确的this
      • strictNullCheck:严格的检查空值。boolean类型
      • strict:所有严格检查的总控制,一般设置为true
{
    "include":[
    //**表示任意目录,*表示任意文件
        "./src/**/*"
    ],
   "compilerOptions":{
        "target":"ES6",
        "module":" ES6",
        "lib":["XX"],
        "outDir":"./dist",
        "outFile":"./dist/app.js"
    }

}

4. 面向对象

对象是我们对现实中的事物进行抽象的。一个事物处于程序的世界的时候,那么就是以对象的事物去存在的。我们要操作某一个事物,首先得拿到这个对象,去完成这个对象上的行为【也就是api】,从而完成某一操作。 对象通常由属性和方法组成。

4.1 类(class)

类可以理解为对象的模型,类是将对象中的一些共有的属性或者方法抽象出来。其中构造函数是我们向外部提供的接口,可以将外部传递的值存入到类的内部。

class 类名
{
    // 定义实例属性
    属性名:类型;
    // 静态属性定义类属性[不需要new对象就可以使用的属性]
    static age:number;
    constructor(参数:类型){
        this.属性名=参数
    }
    方法名(){
    }
}

属性包括两种方式:

  1. 实例属性

直接在类中定义的属性属于实例属性,需要将对象new,出来然后在对象上访问才能生效。

  1. 静态属性

静态属性使用static修饰,不需要创建对象,可以直接Student.name直接访问到内容。

方法的话直接使用方法名(){}进行定义。

4.2 构造函数和this

构造函数为创建对象提供可多样化,每次new一个class类,都能获得属性不同的对象。 构造函数使用constructor表示,会在new 的时候执行。[这里涉及到new时发生了什么?]

class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        // 在构造函数中,this就是我们当前新建的那个对象
        this.name = name;
        this.age = age;
        console.log('构造函数被执行了')
    }
}
let p1 = new Person('xiaozhag', 14);
let p2 = new Person('frfr', 15);

console.log(p1)
console.log(p2)

this表示当前对象.

4.3 继承

4.3.1 基础概念

继承就是将一些共有的内容提取出来。后续其他类可通过extends关键字将公共部分的内容存放到自身中。继承衍生出来两个内容:父类和子类。下面的代码中Person称为父类,Student称为子类。子类可以获取父类所有的方法和属性。

class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        // TODO 在构造函数中,this就是我们当前新建的那个对象
        this.name = name;
        this.age = age;
        console.log('构造函数被执行了')
    }
    run() {
        console.log(`${this.name}跑了`)
    }
}

class Student extends Person {}
let p1 = new Student('张老师', 35);
console.log(p1)
p1.run();

如果父类中的方法不能完全满足子类功能,则可以重写父类中的方法,让其仅在当前子类生效。

注意:在实际项目开发过程中,尽量不要改动之前声明的类型。如果已有类无法满足我们的实际需求,可以新创建一个类去继承原有类型,在新类中去做改动。

4.3.2 super

super我们理解为就是父类的含义。通常情况下,在子类中如果不写constructor构造函数,默认我们在创建子类对象时,就已经调用了父类的构造函数。

但是如果在子类中我们直接写constructor构造函数,则会产生Must call super constructor in derived class before accessing 'this' or returning from derived constructor错误,此时调用super,才能保证继承成功。

4.3.3 抽象类

抽象类是一个专门用来被继承的类型。使用abstract来声明,定义的抽象类不能创建实例对象。抽象类中可以用来声明抽象方法,抽象方法中只定义方法的结构体,而不具体实现方法。

子类在继承父类的时候,必须重新抽象方法,否则会产生错误。

abstract class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    // 子类继承方法时,必须对该方法进行重写。
    abstract sayHello(): void;
}
// 不能进行创建,会报错无法创建抽象类的实例。
let person = new Person('xiaoming');

4.5 接口

上述中我们定义了抽象类,抽象类我们可以理解为约定了一些规范内容,需要后代去按照这个规范去做事情。同样,我们可以使用接口来完成这样一件时间。

接口是用来定义一个类的结构。关于接口需要注意几个点:

  1. 接口在同一作用域内可以重复声明,但是type不可以
  2. 接口可以在定义类的时候去限制类的类型
  3. 接口不能有实际值,只用来定义对象的结构
  4. 接口中所有方法都是抽象方法
  5. 实现接口使用关键字implements,实现接口必须把接口所有属性和方法全部都实现。
// 普通类型声明
type myType = {
    name?: string;
    age?: number;
    sex?: string;
}
/**
 * 类型声明
 */
interface muInterface { 
    name?: string;
    age?: number;
    sex?: string;
}
// 在声明一些属性时,如果不确定是否一定存在,可以使用可选属性来定义
const obj: myType={ 
    name: '123',
    age: 12,
}

4.6 泛型

在遇到类型无法确定的情况下,我们可以使用泛型。

function Person<T>(name: T) {
    console.log(name)
}
Person<number>(1)//指定T的类型是number
Person('zhangsan')//会自动匹配类型
  1. 可以直接调用具有泛型的函数
  2. 不指定泛型,TS可以自动对类型进行推断
  3. 指定泛型,指定什么类型就只能赋值对应类型的变量
  4. 泛型可以同时指定多个
  5. T extends Inter 表示泛型T必须是Inter的实现类。