TypeScript基础

150 阅读12分钟

第一章 快速入门

1、TypeScript简介

  • TypeScript是JavaScript的超集。
  • 它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。
  • TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。
  • TS完全兼容JS,换言之,任何的JS代码都可以直接当成JS使用。
  • 相较于JS而言,TS拥有了静态类型,更加严格的语法,更强大的功能;TS可以在代码执行前就完成代码的检查,减小了运行时异常的出现的几率;TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS。

2、TypeScript 开发环境搭建

  1. 下载Node.js

image.png

  1. 安装Node.js

  2. 使用npm全局安装typescript

    • 进入命令行
    • 输入:npm i -g typescript
  3. 创建一个ts文件

  4. 使用tsc对ts文件进行编译

    • 进入命令行
    • 进入ts文件所在目录
    • 执行命令:tsc xxx.ts

3、基本类型

类型声明

  • 类型声明是TS非常重要的一个特点
  • 通过类型声明可以指定TS中变量(参数、形参)的类型
  • 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错
  • 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值

语法:

            let 变量: 类型;
            let a: number;
            a = 10;
            
            
            let 变量: 类型 = 值;
            let c: boolean = false;
            
            
            function fn(参数: 类型, 参数: 类型): 类型{
                ...
            }
            function sum(a: number, b: number): number {
                return a + b;
            }
            let result = sum(123, 456);

自动类型判断

  • TS拥有自动的类型判断机制

  • 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型

  • 所以如果你的变量的声明和赋值是同时进行的,可以省略掉类型声明

  • 类型:

    类型例子描述
    number1, -33, 2.5任意数字
    string'hi', "hi", hi任意字符串
    booleantrue、false布尔值true或false
    字面量其本身限制变量的值就是该字面量的值
    any*任意类型
    unknown*类型安全的any
    void空值(undefined)没有值(或undefined)
    never没有值不能是任何值
    object{name:'孙悟空'}任意的JS对象
    array[1,2,3]任意JS数组
    tuple[4,5]元素,TS新增类型,固定长度数组
    enumenum{A, B}枚举,TS中新增类型

number

    let decimal: number = 6;
    let hex: number = 0xf00d;

boolean

    let isDone: boolean = false;

string

    let color: string = "blue";
    color = 'red';

字面量

  • 也可以使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围
    let color: 'red' | 'blue' | 'black';
    let num: 1 | 2 | 3 | 4 | 5;
    let sex: "male" | "female";

any

  • any可以赋值给任意变量
    let d: any = 4;
    d = 'hello';
    d = true;
    
    let s: string;
    // d的类型是any,它可以赋值给任意变量
    s = d;

unknown

  • unknown 实际上就是一个类型安全的any
  • unknown类型的变量,不能直接赋值给其他变量
    let notSure: unknown = 4;
    notSure = 'hello';
    
    
    let e: unknown;
    e = 10;
    let s: string;
    // s = e;  错误写法
    // unknown 实际上就是一个类型安全的any
    // unknown类型的变量,不能直接赋值给其他变量
    if (typeof e === "string") {
      s = e;
    }

void

  • void 用来表示空
    // void 用来表示空,以函数为例,就表示没有返回值的函数
    function fn(): void {}

never

  • never 表示永远不会返回结果
    // never 表示永远不会返回结果
    function error(message: string): never {

      throw new Error(message);
    }

object

  • { } 用来指定对象中可以包含哪些属性

    • 语法:{属性名:属性值,属性名:属性值}
    // 在属性名后边加上?,表示属性是可选的
    let obj:{ name: string, age:number, sex?: "male" | "female"}
    obj = { name: "lsh", age: 11, sex: "male"}
  • [propName: string]: any 表示任意类型的属性
    let c: {name: string, [propName: string]: any};
    c = {name: '猪八戒', age: 18, gender: '男'};
  • 设置函数结构的类型声明:
    • 语法:(形参:类型, 形参:类型 ...) => 返回值
    let d: (a: number ,b: number)=>number;
    // d = function (n1: string, n2: string): number{
    //     return 10;
    // }

array

  • 数组的类型声明:

    • 类型[]

    • Array<类型>

    // string[] 表示字符串数组
    let e: string[];
    e = ['a', 'b', 'c'];

    // number[] 表示数值数值
    let f: number[];
    
    let g: Array<number>;
    g = [1, 2, 3];

tuple

  • 元组,元组就是固定长度的数组
    • 语法:[类型, 类型, 类型]
    let x: [string, number];
    x = ["hello", 10]; 
  • enum

  • 枚举
    enum Color {
      Red = 1,
      Green = 2,
      Blue = 4,
    }
    let c: Color = Color.Green;


    enum Gender {
      Male = 0,
      Female = 1
    }

    let i: { name: string, gender: Gender };
    i = {
      name: '孙悟空',
      gender: Gender.Male // 'male'
    }

类型断言

  • 类型断言,可以用来告诉解析器变量的实际类型
  • 语法:
    • 变量 as 类型
    • <类型>变量
    e = 'hello';
    // 第一种
    s = e as string;
    // 第二种
    s = <string>e;

类型的别名

  • 自定义一个类型,其他变量可以设置为该类型
    type myType = 1 | 2 | 3 | 4 | 5;
    let k: myType;
    let l: myType;
    let m: myType;

    k = 2;

4、编译选项

4-1 自动编译文件

  • 编译文件时,使用 -w 指令后,TS编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译。

  • 示例:

    • tsc xxx.ts -w
      

4-2 自动编译整个项目

  • 如果直接使用tsc指令,则可以自动将当前项目下的所有ts文件编译为js文件。

  • 但是能直接使用tsc命令的前提时,要先在项目根目录下创建一个ts的配置文件 tsconfig.json

  • tsconfig.json是一个JSON文件,添加配置文件后,只需只需 tsc 命令即可完成对整个项目的编译

4-3 配置选项:

include

  • 定义希望被编译文件所在的目录

  • 默认值:["**/*"]

  • 示例:

    •   ** 表示任意目录
        * 表示任意文件
      "include":["src/**/*", "tests/**/*"]
      
    • 上述示例中,所有src目录和tests目录下的文件都会被编译

exclude

  • 定义需要排除在外的目录

  • 默认值:["node_modules", "bower_components", "jspm_packages"]

  • 示例:

    • "exclude": ["./src/hello/**/*"]
      
    • 上述示例中,src下hello目录下的文件都不会被编译

extends

  • 定义被继承的配置文件

  • 示例:

    • "extends": "./configs/base"
      
    • 上述示例中,当前配置文件中会自动包含config目录下base.json中的所有配置信息

files

  • 指定被编译文件的列表,只有需要编译的文件少时才会用到

  • 示例:

  • "files": [
        "core.ts",
        "sys.ts",
        "types.ts",
        "scanner.ts",
        "parser.ts",
        "utilities.ts",
        "binder.ts",
        "checker.ts",
        "tsc.ts"
      ]
    
  • 列表中的文件都会被TS编译器所编译

4-4 编译器的选项

  • 编译选项是配置文件中非常重要也比较复杂的配置选项

  • 在compilerOptions中包含多个子选项,用来完成对编译的配置

4-4-1 项目选项

target

  • 设置ts代码编译的目标版本

  • 可选值:

    • ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext
  • 示例:

    • "compilerOptions": {
          "target": "ES6"
      }
      
    • 如上设置,我们所编写的ts代码将会被编译为ES6版本的js代码
  • lib

    • 指定代码运行时所包含的库(宿主环境)

    • 可选值:

      • ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext、DOM、WebWorker、ScriptHost ......
    • 示例:

      • "compilerOptions": {
            "target": "ES6",
            "lib": ["ES6", "DOM"],
            "outDir": "dist",
            "outFile": "dist/aa.js"
        }
        
  • module

    • 设置编译后代码使用的模块化系统

    • 可选值:

      • CommonJS、UMD、AMD、System、ES2020、ESNext、None
    • 示例:

      • "compilerOptions": {
            "module": "CommonJS"
        }
        
  • outDir

    • 编译后文件的所在目录

    • 默认情况下,编译后的js文件会和ts文件位于相同的目录,设置outDir后可以改变编译后文件的位置

    • 示例:

      • "compilerOptions": {
            "outDir": "dist"
        }
        
      • 设置后编译后的js文件将会生成到dist目录
  • outFile

    • 将所有的文件编译为一个js文件

    • 默认会将所有的编写在全局作用域中的代码合并为一个js文件,如果module制定了None、System或AMD则会将模块一起合并到文件之中

    • 示例:

      • "compilerOptions": {
            "outFile": "dist/app.js"
        }
        
  • rootDir

    • 指定代码的根目录,默认情况下编译后文件的目录结构会以最长的公共目录为根目录,通过rootDir可以手动指定根目录

    • 示例:

      • "compilerOptions": {
            "rootDir": "./src"
        }
        
  • allowJs

    • 是否对js文件编译,认是false
      •    "allowJs": true,    
        
  • checkJs

    • 是否对js文件进行检查

    • 示例:

      • "compilerOptions": {
            "allowJs": true,
            "checkJs": true
        }
        
  • removeComments

    • 是否删除注释
    • 默认值:false
  • noEmit

    • 不对代码进行编译
    • 默认值:false
  • sourceMap

    • 是否生成sourceMap
    • 默认值:false

4-4-2 严格检查

  • strict

    • 启用所有的严格检查,默认值为true,设置后相当于开启了所有的严格检查
  • alwaysStrict

    • 总是以严格模式对代码进行编译
  • noImplicitAny

    • 禁止隐式的any类型
  • noImplicitThis

    • 禁止类型不明确的this
  • strictBindCallApply

    • 严格检查bind、call和apply的参数列表
  • strictFunctionTypes

    • 严格检查函数的类型
  • strictNullChecks

    • 严格的空值检查
  • strictPropertyInitialization

    • 严格检查属性是否初始化

4-4-3 额外检查

  • noFallthroughCasesInSwitch

    • 检查switch语句包含正确的break
  • noImplicitReturns

    • 检查函数没有隐式的返回值
  • noUnusedLocals

    • 检查未使用的局部变量
  • noUnusedParameters

    • 检查未使用的参数

4-4-4 高级

  • allowUnreachableCode

    • 检查不可达代码

    • 可选值:

      • true,忽略不可达代码
      • false,不可达代码将引起错误
  • noEmitOnError

    • 有错误的情况下不进行编译
    • 默认值:false

第二章:面向对象

1、类(class)

1-1 类的基本使用

  • 定义类:
    class 类名 {
        属性名: 类型;

        constructor(参数: 类型){
            this.属性名 = 参数;
        }

        方法名(){
            ....
        }
    }
  • 示例:
        class Person{
            name: string;
            age: number;
            
            constructor(name: string, age: number){
                this.name = name;
                this.age = age;
            }
            
            sayHello(){
                console.log(`大家好,我是${this.name}`);
            }
        }
  • 使用类:
        const p = new Person('孙悟空', 18);
        p.sayHello();

1-2 实例属性、实例方法

  • 直接定义的属性是实例属性,需要通过对象的实例去访问
        class Person{
            // 实例属性
            name: string;
            age: number;
            
             // 实例方法
            /*
            * 如果方法以static开头则方法就是类方法,可以直接通过类去调用
            * */
            sayHello(){
                console.log('Hello 大家好!');
            }
        }
        const p1 = new Person();
        p1.name  //通过对象的实例去访问
        p1.sayHello();

1-3 静态属性(类属性)、静态方法(类方法)

  • 使用static开头的属性是静态属性(类属性),可以直接通过类去访问

        class Player {
          name: String;
          // 静态属性
          static age: Number = 20;

          constructor(name: string) {  //没有age参数,静态属性age不能修改
            this.name = name;
          }
          
           // 静态方法
           static sayHello(){
             console.log('Hello 大家好!');
           }
        }

        const cr7 = new Player('cr7')
        // cr7.age = 20;    // 不可以通过对象的实例去访问
        Player.age = 18;    //直接通过类去访问
        Player.sayHello();  //直接通过类去访问

1-4 只读属性

  • readonly开头的属性表示一个只读的属性无法修改
        readonly name: string = '孙悟空';

        cr7.name = 'cr7' //报错,不可以修改
        
        
       // 在属性前使用static关键字可以定义类属性(静态属性)
       static readonly age: number = 18;
       Player.age = 20;  //报错,不可以修改
        

1-5 构造函数

  • constructor 被称为构造函数
  • 构造函数会在对象创建时调用

通过构造函数,创建出不同得对象

class Dog{
    name: string;
    age: number;

    constructor(name: string, age: number) {
        // 在实例方法中,this就表示当前当前的实例
        // 在构造函数中当前对象就是当前新建的那个对象
        // 可以通过this向新建的对象中添加属性
        this.name = name;
        this.age = age;
    }

    bark(){
        // 在方法中可以通过this来表示当前调用方法的对象
        console.log(this.name);
    }
}

const dog = new Dog('小黑', 4);
const dog2 = new Dog('小白', 2);

dog.bark();   // 小黑
dog2.bark();  // 小白

2、继承

2-1 基本使用

  • 使用继承后,子类将会拥有父类所有的方法和属性
  • 通过继承可以将多个类中共有的代码写在一个父类中,这样只需要写一次即可让所有的子类都同时拥有父类中的属性和方法
  • 如果希望在子类中添加一些父类中没有的属性或方法,直接在子类加就行
  • 如果在子类中添加了和父类相同的方法,则子类方法会覆盖掉父类的方法,这种子类覆盖掉父类方法的形式,我们称为方法重写
// 定义一个Animal类
    class Animal{
        name: string;
        age: number;

        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }

        sayHello(){
            console.log('动物在叫~');
        }
    }

    /*
    * Dog extends Animal
    *   - 此时,Animal被称为父类,Dog被称为子类
    * */
    
    // 定义一个表示狗的类
    // 使Dog类继承Animal类
    class Dog extends Animal{

        run(){
            console.log(`${this.name}在跑~~~`);
        }

        sayHello() {
            console.log('汪汪汪汪!');
        }

    }

    // 定义一个表示猫的类
    // 使Cat类继承Animal类
    class Cat extends Animal{
        sayHello() {
            console.log('喵喵喵喵!');
        }
    }

    const dog = new Dog('旺财', 5);
    const cat = new Cat('咪咪', 3);
    dog.sayHello();        //汪汪汪汪!
    dog.run();             //旺财在跑~~~
    cat.sayHello();        //喵喵喵喵!

2-2 super关键字

  • 如果在子类中写了构造函数,在子类构造函数中使用super关键字必须对父类的构造函数进行调用
    class Animal {
        name: string;
        constructor(name: string) {
            this.name = name;
        }
        sayHello() {
            console.log('动物在叫~');
        }
    }

    class Dog extends Animal{
        age: number;

        constructor(name: string, age: number) {
            // 如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
            super(name); // 调用父类的构造函数
            this.age = age;
        }
        sayHello() {
            // 在类的方法中 super就表示当前类的父类
            //super.sayHello();
            console.log('汪汪汪汪!');
        }

    }

3、抽象类

  • 抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
  • 使用abstract开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要实现
    /*
    *   以abstract开头的类是抽象类,
    *       抽象类和其他类区别不大,只是不能用来创建对象
    *       抽象类就是专门用来被继承的类
    *
    *       抽象类中可以添加抽象方法
    * */
    abstract class Animal {
        name: string;

        constructor(name: string) {
            this.name = name;
        }

        // 定义一个抽象方法
        // 抽象方法使用 abstract开头,没有方法体
        // 抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
        abstract sayHello():void;
    }

    class Dog extends Animal{
        sayHello() {
            console.log('汪汪汪汪!');
        }

    }

    const dog = new Dog('旺财');
    dog.sayHello();

4、接口

  • 接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法
  • 同时接口也可以当成类型声明去使用

4-1 类型声明

    // 接口也可以当成类型声明去使用
    interface myInterface {
        name: string;
        age: number;
    }

    interface myInterface {
        gender: string;
    }

    const obj: myInterface = {
     name: 'sss',
     age: 111,
     gender: '男'
    };

4-2 定义一个类结构

  • 接口中的所有的属性都不能有实际的值
  • 接口中所有的方法都是抽象方法
    //   接口可以在定义类的时候去限制类的结构
    //   接口只定义对象的结构,而不考虑实际值
    interface myInter{
        name: string;

        sayHello():void;
    }

    /*
    * 定义类时,可以使类去实现一个接口,
    *   实现接口就是使类满足接口的要求
    * */
    class MyClass implements myInter{
        name: string;

        constructor(name: string) {
            this.name = name;
        }

        sayHello(){
            console.log('大家好~~');
        }

    }

5、属性的封装

  • TS可以在属性前添加属性的修饰符
    • public 修饰的属性可以在任意位置访问(修改) 默认值
    • private 私有属性,私有属性只能在类内部进行访问(修改)
      • 通过在类中添加方法使得私有属性可以被外部访问
    • protected 受包含的属性,只能在当前类和当前类的子类中访问(修改)
    // 定义一个表示人的类
    class Person{
        private _name: string;
        private _age: number;

        constructor(name: string, age: number) {
            this._name = name;
            this._age = age;
        }

        /*
        *   getter方法用来读取属性
        *   setter方法用来设置属性
        *       - 它们被称为属性的存取器
        * */

        // 定义方法,用来获取name属性
        // getName(){
        //     return this._name;
        // }
        //
        // // 定义方法,用来设置name属性
        // setName(value: string){
        //     this._name = value;
        // }
        //
        // getAge(){
        //     return this._age;
        // }
        //
        // setAge(value: number){
        //     // 判断年龄是否合法
        //     if(value >= 0){
        //         this._age = value;
        //     }
        // }

        // TS中设置getter方法的方式
        get name(){
            // console.log('get name()执行了!!');
            return this._name;
        }

        set name(value){
            this._name = value;
        }

        get age(){
            return this._age;
        }

        set age(value){
            if(value >= 0){
                this._age = value
            }
        }
    }
    
    const per = new Person('孙悟空', 18);
    per.name = '猪八戒';
    per.age = -33;
    
    
    // protected修饰符例子
    class A{
        protected num: number;

        constructor(num: number) {
            this.num = num;
        }
    }

    class B extends A{

        test(){
            console.log(this.num);
        }

    }

    const b = new B(123);
    // b.num = 33;  //爆错,只可以在当前类或当前类的子类中访问

6、泛型

  • 在定义函数或是类时,如果遇到类型不明确就可以使用泛型

定义函数

function fn<T>(a: T): T{
    return a;
}

// 可以直接调用具有泛型的函数
let result = fn(10); // 不指定泛型,TS可以自动对类型进行推断
let result2 = fn<string>('hello'); // 指定泛型

// 泛型可以同时指定多个
function fn2<T, K>(a: T, b: K):T{
    console.log(b);
    return a;
}
fn2<number, string>(123, 'hello');

interface Inter{
    length: number;
}

// T extends Inter 表示泛型T必须是Inter实现类(子类)
function fn3<T extends Inter>(a: T): number{
    return a.length;
}


定义类

class MyClass<T>{
    name: T;
    constructor(name: T) {
        this.name = name;
    }
}

const mc = new MyClass<string>('孙悟空');