Typescript基础学习(后续更新)

1 基础

1.1 安装

全局安装 typescript

npm i -g typescript

避坑

  1. 不要使用如下命令安装 typescript

    npm i -g tsc
    # 如果已经安装,请到node安装目录中卸载相关node包和cmd等配置
    
  2. 如果出现如下问题:

    image-20230402110043099

    请使用 npm i -d @types/node,将其添加到 node 依赖中,该问题的出现是由于使用 tsc 命令将 .ts 文件转成 .js 文件时,出现了函数名重复,不影响正常使用

全局安装 ts-node

npm i -g ts-node

初始化一个 ts 工程

tsc --init

编译 ts(将 ts => js)

tsc fileName.ts

官方靶场地址:https://www.typescriptlang.org/zh/play?#code/FAAhQ

1.2 TS 简介

TS(全称 “TypeScript”)是以 Javascript 为基础构建的语言,TS 相较于 JS 有如下特点:

  1. TS 是 JS 的超集
  2. TS 可以在任何支持 JS 平台中执行
  3. TS 扩展了 JS 并添加了类型
  4. TS 不能直接被 JS 解析器直接执行,需要先进行编译转换

1.3 类型声明

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、string、boolean、null、undefined、object、symbol、bigint

字面量声明

/* 
 * 直接使用字面量进行类型声明
 */
// 使用场景1:限制 a 的值只能为字面量 10(不常用)
let a: 10   
a = 10 

// 使用场景2:联合字面量(有点类似于枚举类型)
let b: 'male' | 'female'
b = 'male'
b = 'female'

// 使用场景3:联合字面量(可选数据类型)
let c: boolean | string
c = 'hello'
c = true

any 和 unknown 类型

any 表示任意类型,一个变量设置类型为 any 后(显式设置 any)相当于对该变量关闭了 TS 的类型检测,当我们声明变量如果不指定类型,那么 TS 默认会将该变量解析为 any 类型(隐式设置)

let a: any
a = 10
a = "ts"

但是我们不推荐使用 any 类型,因为这即与 JS 并无分别了,并且 any 类型存在一个极大的隐患,就是会将其他的变量 “污染”,例如:

let a: any
a = 10
let b: boolean
b = a
console.log(typeof b)   // 此时 b 成为了 number 类型

取而代之,我们可以如果在不知道某个变量具体的类型时,可以使用 “安全” 的 unknown 类型,该类型不会存在变量污染的问题

let a: unknown
a = 'hello'
let b: boolean
b = a   // 非法操作

unknown 配合断言

JS 中的断言用于告诉解析器变量的实际类型

语法如下:

// 语法1
变量 as 类型

// 语法2
<类型>变量

这里感觉有点像泛型,与 Java 中 assert 有所不同

如果我们真的希望 unknown 类型能够赋给其他类型,那么我们可以使用断言

let a: unknown
a = 'hello'
let b: string
let c: string

b = a as string
c = <string>a

void 类型

void 常见于表示函数的返回值为空,当然也可以给一个变量声明为 void ,但是一旦我们给某变量声明了 void 类型,那么我们就只能为其赋予 undefined

// 函数的返回类型为 void
function fn(a:number, b:number): void {
    console.log(a, b)
}

// 为变量声明void类型,赋值 undefined 
let a: void
a = undefined

never 类型

never 类型表示的是永远不存在的值的类型

而值永远不存在有两种情况:

  • 函数执行时抛出异常,无法到达 return(即使没有 return 语句)
  • 函数陷入死循环
// 抛出异常
function fn1(): never {
  throw new Error("执行错误")
}

// 死循环
function fn2(): never {
  while (true) {}
}

除此之外,never 类型只能赋给 never 类型,不能赋给其他类型,同时,像 nullundefined 也不能赋给 never

object 类型

由于在 JS/TS 中,一切皆对象,因此我们为一个变量类型声明为 object 是没有任何意义的,相当于没有给该变量做语法检查

let a: object
a = {}
a = function() {}

我们更加常用的搭配(或写法)是如下写法:

// 表示指定 a 为一个对象,并且必须且只能包含 name 属性,类型为 string
let a: { name: string }
a = { name: "zs" }

// 如果我们在属性名后面添加 "?",表示该属性是可选的
let b: { name: string, age?: number }
b = { name: 'ls', age: 18}

// 添加可变参数, 其中 propName 名字可自定义,后面分别表示属性类型和属性值类型,unknown 就代表了属性值可以为任意类型
let c: { name: string, [propName: string]: unknown }
c = { name: 'zs', age: 18, gender: '男'}

进阶:限制函数的类型声明

// 使用 Function 定义函数的类型(不建议)
let a: Function

// 使用箭头函数来限制函数的定义
let b: (a: number, b: number) => number // 表示函数 b 中应有两个参数,并且均为 number 类型,返回值也为 number 类型

b = function(a: number) {
    return a
}

b = function(a: number, b: number) {
    return a + b
}

b = function(a: number, b: number, c: number) { // error
    return a + b + c;
}

array 类型

// 声明一个 number 类型的数组
let a: number[]

// 声明一个 string 类型的数组
let b: string[]

// 声明一个 string 类型的数组
let c: Array<number>

tuple 类型

tuple 是固定长度的数组,属于 TS 的扩展类型

// 声明一个 tuple,里面有且只能包含两个 string 类型的元素
let h: [string, string]
h = ['hello', 'world']

enum 类型

enum 也是 TS 的一个扩展类型,和其他语言的 enum 所表示的含义相同,用于表示有限集合

enum Gender {
    MALE,
    FEMALE
}

let person: { name: string, gender: Gender }
person = {
    name: 'Alice',
    gender: Gender.MALE
}

console.log(person.gender === Gender.MALE)

1.4 联合类型 && 交叉类型

联合类型

我们在之前已经提到到联合类型,该类型表示取值可以为众多类型的一种,使用 | 分隔

// 基本使用
let myInfo: string | number
myInfo = '张三'
myInfo = 18

// 常与 undefined 结合使用
let lng: number | undefined = 12.89

// 限制字面量的选择范围
let season: 'spring' | 'summer' | 'autumn' | 'winter'
season = 'spring' 

交叉类型

交叉类型刚好和联合类型相反,交叉类型是将多个类型合并为一个类型,使用 & 定义,当然交叉类型并不适用于基本数据类型,如下:

let i: string & number  // 错误使用

不可能存在一个变量既是 string 类型又是 number 类型

正确的使用方法如下:

let o: { name: string } & { age: number }
o = { name: 'zs', age: 18}

1.5 type 关键字

type 关键字用于给类型起别名,如下:

type MyString = string
let s: MyString = 'zhangsan'

注:我们通过 type 仅仅只是为变量类型起别名,而不是真正地创建了一个新的类型,这点我们可以通过 typeof 进行验证

应用场景(其实编程领域的很多应用场景都源于程序员的 “懒”)

// 当自定义的变量类型特别长时,我们可以为其起别名
type myCustomType = 1 | 2 | 3 | 4 | 5
let m: myCustomType
let n: myCustomType

1.6 null、undefined && number、bigint

nullundefined 都代表空,但区别在于

  • null 是有指向的,对应的地址为 0x00000000,该地址(内存空间)并不指向任何内容,换言之,你有一个垃圾桶,但垃圾桶是空的;
  • undefined 没有指向,它表示变量没有被初始化,换言之,你连垃圾桶都没有

nullundefined 本身都是数据类型,有人可能发现通过 typeof null 得到的明明是一个 object,但这其实是一个 “美丽的误会”,是设计者当初由于快速开发产生的 bug 诞生的,具体原因可以自查。

typeof null // object
typeof undefined // undefined

严格判断和非严格判断

null == undefined      // true
null === undefined     // false
!!null === !!undefined // true

第三种双 ! 代表将其转换为布尔值,等价于 Boolean(null) === Boolean(undefined)

当然,nullundefined 类型是不能相互赋值的

bigintnumber

bigint 这种数据类型所对应的是内置对象 BigInt ,在介绍之前请注意以下几点:

  1. BigInt != Number,两者还是具有比较大的区别的,BigInt 解决了 Number 整数溢出问题,但是不能使用 Math 内置对象进行运行,也不能和 Number 混合运算,但是两者可以强转(注意精度丢失问题)
  2. BigInt 支持基本运算,如 +-*/%** 和除 >>> 外的位运算,因为 BigInt 是有符号的,并且不支持单目运算符
  3. BigInt 结果带小数的运算会被取整
  4. BigIntNumber 严格不相等(很显然,两者类型都不同),但宽松相等

定义 BigInt

// 定义方式1:末尾捎带 n
const m = 1n

// 定义方式2:使用 BigInt() 构造器
const n: bigint = BigInt(1)

1.7 symbol

symbol 是一个非常有意思的数据类型,它是的类型正如其名 ,代表一个 “标识”,同时它是一个基本数据类型(primitive data type)。symbol 类型的值通过 Symbol() 得到,无法通过 new 创建而得。

symbol 的应用场景:用于解决变量同名覆盖问题

// 定义方式1:通过 Symbol 定义,括号内为描述信息(可不写,但推荐写,方便后期调试使用)
let a: symbol = Symbol('变量a')
let b: symbol = Symbol('变量b')
console.log(a, b)

// 定义方式2:通过 Symbol.for
let c: symbol = Symbol.for('symbol变量')
let d: symbol = Symbol.for('symbol变量')
console.log(c === d)        // true

上述两者使用有差别,Symbol() 每次会返回一个新的 symbol,而 Symbol.for() 每次会根据 for 中的描述信息放在一个全局 symbol 注册表,key 为 description,value 为 symbol 值,如果 description 相同则直接返回从注册表中返回

我们取出上述 symbol 的描述的方法有如下方式,对于全局注册,可以使用 .descriptionSymbol.for(key) 的方式获取;对于局部注册,只能使用 .description 的方式获取

console.log(a.description)      // 变量a
console.log(c.description)      // symbol变量

console.log(Symbol.for(a))      // error
console.log(Symbol.keyFor(c))   // symbol变量

应用场景:字符串解耦

我们日常生活中,经常会出现这么一种情况,一个班级可能出现同名的情况的,那么我们在登分的时候就会出现困扰(当然,我们在管理系统中肯定要用学号作为 key 以示区分),解决方式如下:

const stu_1 = {
    key: Symbol(),
    name: '张三'
}

const stu_2 = {
    key: Symbol(),
    name: '张三'
}

const grade = {
    [stu_1.key]: {css: 99, js: 100},
    [stu_2.key]: {css: 20, js: 30}
}

console.log(grade[stu_1.key])

1.8 包装类型和原始类型

这个概念不仅存在于 Java,在 JS/TS 中同样也有,例如:

原始类型包装类型
numberNumber
stringString
booleanBoolean
symbolSymbol

示例

const a: number = 1
const b: Number = a // 装箱操作
const c: number = b.valueOf() // 拆箱操作

1.9 断言

TS 无法动态进行类型检测,所有的检查都是在编译阶段进行的,因此会出现有时候自己清楚使用的变量的类型,但是编译器不清楚的情况,在这时就需要用到断言断言类似于告诉编译器 “相信自己就是这个类型”,应用如下:

// as 断言
const arr: number[] = [1, 2, 3, 4]
const greater2: number = arr.find(v => v > 2) as number

// 尖括号写法
const str: unknown = 'Hello World'
console.log((<string>str).length)

1.9.1 非空断言

当编译器根据上下文无法判断变量的类型,我们可以使用后缀操作符 ! 表示该变量不是 nullundefined 类型

let str: null | undefined | string = 'abc'
console.log(str!.toString())
let x: number
init()
console.log(x!) // 告诉编译器 x 会被明确赋值
function init() {
    x = 10
}

1.10 函数相关

1.10.1 函数定义

/**
 * @param a number 类型
 * @param b number 类型
 * @returns number 类型
 */
function add(a: number, b: number): number {
    return a + b
}

1.10.2 函数表达式

let sub = (a:number, b: number): number => a - b

1.10.3 可选参数

function buildName(firstName: string, lastName?: string): string {
    if(lastName) {
        return lastName + '-' + firstName
    } else {
        return firstName
    }
}

1.10.4 剩余参数

function flat(arr: any[], ...items: any[]) {
    items.forEach(v => arr.push(v))
    return arr
}

const a = [1, 2, 3]
const res = flat(a, 'js', 'ts')
console.log(res)    // [ 1, 2, 3, 'js', 'ts' ]
console.log(a)      // [ 1, 2, 3, 'js', 'ts' ]

1.10.5 参数默认值

function concat(a: string, b: string = 'cat'): string {
    return a + b
}

1.10.6 函数重载

type myType = string | number | undefined
function plus(a: myType, b: myType): myType {
    if(typeof a === 'number' && typeof b === 'number') return a + b
    if(typeof a === 'string' && typeof b === 'string') return a + b
}

console.log(plus(1, 2))     // 3
console.log(plus('a', 'b')) // ab

1.11 元组相关

元组和数组相似,但是不同的是:

  1. 元组有固定的长度,而 JS 中的数组可以动态扩展
  2. 元组中可以存放不同的数据类型,而数组中元素的类型相同
let myTuple: [number, string] = [1, '张三']
myTuple = [2, '李四']

1.11.1 元组解构

let stu: [number, string] = [1, '张三']
const [id, username] = stu
console.log(`id: ${id}`)
console.log(`username: ${username}`)

1.11.2 可选元组

let person: [number, string, string?]
person = [1, 'Alice']
person = [2, 'Bob', 'male']

1.11.3 剩余元组

let person: [number, ...string[]]

1.11.4 只读元组

const point: readonly [number, number] = [10, 20]

2 TS 编译选项

2.1 编译选项

自动编译文件:使用 tsc -w 命令,TS 编译器会自动监视文件的变化

自动编译整个项目

如果我们想要在直接通过一个命令 tsctsc -w 编译项目中的所有文件,需要在项目根目录下创建 tsconfig.json

2.2 配置选项

重要配置项

  • files - 设置要编译的文件的名称;
  • include - 设置需要进行编译的文件,支持路径模式匹配;
  • exclude - 设置无需进行编译的文件,支持路径模式匹配;
  • compilerOptions - 设置与编译流程相关的选项

compilerOptions 中相关配置

{
  "compilerOptions": {
  
    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).

    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any 类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
  }
}

3 使用 webpack 打包 ts 代码

3.1 环境搭建

如果我们想要实现将我们的 ts 项目使用 webpack,那么就必须要使用 npm init -y 进行项目初始化

安装必要的依赖:

npm i -D webpack webpack-cli typescript ts-loader

通过 tsc --init 快速生成 tsconfig.json 配置文件

同时在项目根目录下新建一个 webpack.config.js 文件

4 面向对象(重要)

4.1 类基础

类的定义

class Person {
  private name: string
  private age: number

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

  public getName(): string {
    return this.name
  }

  public getAge(): number {
    return this.age
  }

  public setName(name: string): void {
    this.name = name
  }

  public setAge(age: number): void {
    this.age = age
  }

  static sayHello() {
    console.log('Hello, every!!!')
  }
}

默认可见性为 public

类的使用

const p = new Person("张三", 18)
Person.sayHello()
p.getName() // 张三

4.2 构造器

在类中使用 constructor 关键字定义一个构造器方法

特别注意:在 JS/TS 中,一个类仅能有一个构造器方法

class Cat {
  name: string
  age: number

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

上述代码等同于下面的代码:

class Cat {
  constructor(public name: string, public age: number) {}
}

4.3 封装

所谓 “封装”,实质上就是将属性和方法都封装到一个对象容器中以供使用

默认情况下,对象中成员属性和成员方法的可见性修饰符都是 public,也就是说可以直接进行修改的,成员的修饰符有如下几类:

  1. static:使用 static 修饰的属性或方法在挂载在类中,当类被加载完成后静态成员也就随之被加载,使用方法:类名.属性类名.方法
  2. readonly:修改成员属性,使用该修饰符后,成员属性无法被修改
  3. 可见性修饰符:
    • public(default)
    • protected
    • private

4.4 继承

class Animal {
    name: string
    age: number

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

class Dog extends Animal {
    /**
     * 子类继承父类
     *  1. 如果子类没有构造器,那么创建子类对象时调用的父类的构造器
     *  2. 如果子类中有构造器,且父类中不存在无参构造器,那么子类中必须通过super显式调用父类构造器
     */
    skill: string
    constructor(name: string, age: number, skill: string) {
        super(name, age)
        this.skill = skill
    }
}

4.5 多态

多态,涉及动态绑定,即父类引用指向子类,当调用子类时,表现子类的特性

interface Person {
  name: string
  say(): void
}

class Student implements Person {
  constructor(public name: string) {}

  say() {
    console.log('Hello, I am a student')
  }
}

class Teacher implements Person {
  constructor(public name: string) {}

  say() {
    console.log('Hello, I am a teacher')
  }
}

let p: Person = new Student('Alice') 
p.say()
p = new Teacher('Bob')
p.say()

4.6 重写

重写:即在继承过程中,子类覆盖父类的方法

4.7 抽象类

抽象类是专门用来被其他类所继承的,抽象类没有实例,使用关键字 abstract 定义

和 Java 类似,拥有抽象方法的类一定是抽象类,但抽象类中不一定有抽象方法

abstract class Animal {
    abstract say(): void

    hunt() {
        console.log('hunt food');
    }
}

4.8 接口(interface)

接口是对单继承机制的扩展,主要用来扩展类的行为或者提供一个规范

interface Person {
  name: string
  say(): void
}

class student implements Person {
  constructor(public name: string) {}

  say() {
    console.log('Hello')
  }
}

5 泛型(Generic)

泛型的出现是为了制造一个模板类或者一个模板函数

// 泛型函数示例
function add<T>(a: T, b: T): T | undefined {
    if(typeof a === 'string' && typeof b === 'string') return a + b as T
    else if (typeof a === 'number' && typeof b === 'number') return a + b as T
    else return undefined
}

console.log(add<number>(1, 2))
console.log(add<string>('1', '2'))
// 泛型类示例
class Person<T, K> {
    name: T
    age: K
    
    constructor(name: T, age: K) {
        this.name = name
        this.age = age
    }
}

let p: Person<string, number> = new Person('zs', 19)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值