TypeScript笔记

什么是TS

官网介绍:TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。可以在任何浏览器、计算机、操作系统上运行,并且是开源的。 最终会编译成JavaScript语法在浏览器中运行。

在这里插入图片描述

类型系统,从可从两个角度来考虑

  • 类型安全:可分强类型弱类型语言,

    • 强类型:在语言层面上,就约束着函数的实参类型必须与形参类型相同,另外不允许隐式类型转换。
    • 弱类型:在语言层面上,不要求函数的实参类型与形参类型是相同的,允许隐式类型转换。
  • 类型检查:可分静态类型动态类型语言

    • 静态类型:一个变量声明时它的类型就是明确的(编译阶段),中间过程是不允许再修改变量类型了,其中java就是典型的静态类型语言。
    • 动态类型:一个变量在允许阶段才能明确变量的类型,而且可以随意修改变量的类型,其中js就是典型的动态类型语言。
// 强类型 kotlin
fun sum(a:Int,b: Int){
	return a + b
}
// 在调用时,函数实参类型必须形参是一样的,
sum(1,2) // 对
sum("1",2) // 错  在调用时就直接报错了,不允许隐式类型转换,类型不匹配
var c = 2 // 声明时Int型
c ="2" // 错 ,不允许修改变量类型

// 弱类型 js
funtion sum2(a ,b){
	return a + b
}
// 在调用时,函数实参类型可以形参是不一样的
sum(1,2) // 对
sum("1",2) // 虽然调用时不会报错  但和我们预期是不一样的 输出12 ,是字符串拼接结果 

let d = 3
d = "3"  // 对 允许修改变量类型


从类型系统角度看,JavaScript是一个弱类型且动态类型语言,灵活度是相当的高,正因为这种灵活多变的任性 ,从而丢失了类型系统的安全可靠性,在编码时注意要类型的安全性,如果在开发环境没有及时发现,可能在生产环境导致程序的异常。

JavaScript是没有编译阶段,可以直接运行,因此具有灵活多变的特性,也带来很大的缺陷,比如:

  • 类型异常只有在运行时才可以发现。
  • 类型不明确导致的结果不符合预期。
// 类型异常只有在运行阶段才会被发发现
let obj = {}
console.log(obj.name) // undefined

// 类型不明确导致的结果不符合预期
function sum(a, b) {
    return a + b
}
console.log(sum("1", 2)) // 输出12 预期是3

而TS正好可以弥补JavaScript这种任性,在编码时能够确定类型的一致性,然后还可以保持着JavaScript的灵活度。强类型的优势:

  • 类型安全,在编码时,能确保类型的一致性,让错误在编译时更早的暴露。
  • 代码编写更智能,更准确,提示更友好,只会提示对象存在的属性或函数。
  • 可以减少类型的判断,比如弱语言接收的任意类型,需要对类型判断逻辑更多,而强类型语言就可以减少不必要的类型判断。
  • 重构更安全可靠。

前期准备

安装TS

一、在安装ts之前,首先得安装node.js环境。

二、在项目根目录执行npm init -y生成package.json项目配置文件。

三、安装ts

全局安装,每个项目都使用同一个版本,不建议全局安装,不利用项目维护

npm install -g typescript

本地安装,在当前项目安装指定版本的ts,一般都是在开发环境才使用,因为运行时ts会编译成js。

npm install typescript -D

安装成功会在项目根目录下生成一个node_modules目录,在bin下有ts的命令符tsc,可以用命令tsc -v查看

在这里插入图片描述

在IDE安装ts-node可以直接运行ts代码

另外也可以在package.json文件可以看到ts的版本号

{
  "devDependencies": {
    "typescript": "^4.9.3"
  },
 // .....
}

TS配置文件

在根目录执行tsc --init命令生成tsconfig.json配置文件;如果根目录执行tsc --init命令无效,可以重启项目再次尝试;或者进入..\node_modules\.bin,再次执行命令 tsc --init,这样tsconfig.json就会生成在..\node_modules\.bin目录下,我们需要把文件移到项目根目录下。

tsconfig.json部分配置:

https://www.tslang.cn/docs/handbook/tsconfig-json.html

{
  "compilerOptions": {
	 /*ts编译后所采用的es标准 */
    "target": "es2016",  

	/*输出的代码采用模块化方式*/
    "module": "commonjs",                            
    /*指定源代码的根文件夹*/
    "rootDir": "src",  
	"sourceMap": true,    
	/*编译后代码输出的位置(文件夹)*/
    "outDir": "dist",   
  }
}

如果一个目录下存在一个tsconfig.json文件,那么它意味着这个目录是TypeScript项目的根目录

基础类型

原始类型

  • 字符串string
  • 数字number:数字(浮点型、整型)、NaN
  • 布尔boolean
  • void:没有任何类型,比如函数的没有返回值类型时
  • undefined:本身作用不大
  • null:本身作用不大
// 基础类型:原始类型
// 声明变量或形参的类型,具体类型是定义为后置,与kotlin是类似的
const a: string = "foo" // 字符串
const b: number = 100 // 数字 (包括了浮点数)
const isDone: boolean = true // 布尔值

const c: void = undefined //  void 默认值是 undefined
const d: null = null
const e: undefined = undefined

// Symbol是es2015(es6+)标准库中的类型,如果"target": "es5" 就会报错
// 如果要使用es6的特性,将target配置成es2015
// 或者在不修改target的情况,在lib中引入标准库es2015
// 标准库就是内置对象所对应的声明,比如Promise、Symbol是ES6标准库中的对象
// 如果修改了tsconfig.json配置,运行还是报错,说明没有读取到我们修改后的配置文件,还是项目内置的配置即最原始配置es5
const s: symbol = Symbol("test")

console.log(s)

其中undefined** 、**null可以是任何类型的子类型,也就是可以给类型变量赋值。当然是有提前的,如果启动了严格模式或者空检查,在编译器中会提示报错,如下:

在这里插入图片描述

在TS配置文件中关闭严格模式strict和空检测strictNullChecks

在这里插入图片描述

正常开发时是不会同时关闭strict、strictNullChecks,官方建议是开启strictNullChecks,如果一个函数的形参接收number或者string、null、undefined类型,在严格模式下是不允许number是不允许为null的,此时我们可以使用联合类型:number | string | null | undefined

// 联合类型
function foo(a: number | string | null | undefined): void {
    // 字符串模板`${..}`
    console.log(`foo: ${a}`)
}
foo(100)
foo("11")
foo(null)
foo(undefined)

object类型

// 基础类型:Object类型
// 是指非原始类型的其他类型都是object类型,比如:数组、函数、对象等等

// export {} 消除ts文件变量重名的语法报错
export {}

let a: object = function () {
}
let b: object = []
// 约束字面量对象,= 两边声明和赋值结构必须保持一致
let o: { name: string, age: number } = {name: "li", age: 12}


// 报语法错误
// let x:object = 100

数组类型

定义数组类型有两种方式,第一种:在中括号[]前声明类型,第二种:数组泛型Array<number>

// 数组类型
// 有两种方式定义声明,第一种:在中括号[]前声明类型,第二种:数组泛型Array<number>

// 第一种:声明具体的类型数组
let list: number[] = [1, 2, 4]
list.push(22)
// list.push("33") // 报错,只能是数字类型


// let list = [1, 2, ""] // 联合类型数组,可以存放不同类型元素


// 第二种

let list2: Array<number> = [1, 2, 3]

如果数组不声明具体类型,就可以存放任何类型的元素,就是一个联合类型数组。

元组类型

元组类型Tuple:表示一个已知元素数量和类型的数组,各元素的类型不必相同

// 元组:表示一个已知元素数量和类型的数组,各元素的类型不必相同

// [number, string] : 表示已知元素数量和类型的数组,初始化值必须对应数量和类型
// 有点类似联合类型
let tuple: [number, string] = [12, "lili"]

// 访问元组,既然是数组,当然是可以通过下标访问
console.log(tuple[0], tuple[1])
//  通过解构也可访问元组
let [age, name] = tuple
console.log(age, name)


// 元组的类型也可以是空类型,可以不初始化值
let tuple2: [number, string?] = [12]

枚举

在没有枚举的情况,JS定义常量,通常会用const修饰字面量对象,将常量定义成对象属性;这样有很强的语义化。

const orderState = {
    Delivered: 1,
    Received: 2,
    Returned: 3
}

console.log(orderState.Delivered)

在TS提供了枚举类型,可以很好替换掉字面量常量对象的方式,具有更强的可读性。

// 数字枚举,默认是从0开始,逐个+1递增,如果第一个设置了初始值,会以这个初始值为起点,逐个+1递增
enum orderState {
    Delivered = 1, Received, Returned
}

console.log(orderState)

// 字符串枚举,要手动赋值
 enum order {
    Delivered = "已发货", Received = "已收货", Returned = "退货中"
}

console.log(order)

函数类型

先看看JavaScript函数的弊端:

// js
function add(x, y) {
    return x + y
}

// js函数的形参可以传任何类型的实参,返回值也是不确定的,根据形参来变化
// 类型的不确定性,会在运行时埋下安全隐患
add(1, true)

add()函数可以传入任何的实参类型,在编译期是不会提示报错的,如果我们期望是求和运算,传入实参不是数值,在运行时是达不到期望值的,因此JavaScript的类型不确定性,会给我们的代码在运行时埋下很大的安全隐患。

下面用TypeScript函数来改造:

// ts:声明形参类型和返回值类型确定了函数类型
// 返回值类型是后置的,这点与kotlin是相似的
// 参数类型:(x:number,y:number) => number
// 箭头左边是参数类型,右边是返回值类型,如果返回值没有类型,则用void表示
function add(x: number, y: number): number {
    return x + y
}

// 参数类型 (x: number, y: number) => number
let myAdd = function (x: number, y: number): number {
    return x + y
}
myAdd(1,200.0)
// myAdd(1,"ddd") // 会推断实参类型,这里报错

// 没有返回值的函数类型:(s:string) => void
function print(s: string): void {
    console.log(s)
}

在ts函数中,函数是有类型的,包括了参数类型返回值类型两个部分,当没有返回值时,则用void表示。

函数类型的表达:

在这里插入图片描述

如上面的myAdd()函数类型是:(x:number,y:number) => number,箭头左边是参数类型,右边是返回值类型。

在这里插入图片描述

会发现将冒号改成箭头,就是一个函数类型。

可选参数和默认参数

除了实参类型要求是一样的,在TypeScript里的每个函数参数都是必须的,传递给一个函数的参数个数必须与函数期望的参数个数一致。

在JavaScript中函数传入的参数都是可选的,没传参时默认值是undefined

在TypeScript函数实现可选参数,有两种方法:

  • 可选参数?修饰
  • 定义默认参数
//------------------- 可选参数和默认参数 ---------------

// TypeScript里的每个函数参数都是必须的,传递给一个函数的参数个数必须与函数期望的参数个数一致。

// ts也是箭头函数
// let buildName = (firstName: string, lastName: string) => firstName +" "+ lastName

function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName
}

// 形参的实参类型必须传入两个,且类型是要与其一样的。
console.log(buildName("lisi", "bob"))

// 在JavaScript中函数传入的参数都是可选的,没传参时默认值是undefined
// 在TypeScript要实现参数是可选的,可以在形参名称后面用?修饰,可选参数必须放在参数后面

function buildName2(firstName: string, lastName?: string) {
    return firstName + " " + lastName
}

let result2 = buildName2("bob")
let result22 = buildName2("bob", "sr")

// 除了在形参名后面?修饰声明是可选参数,还可以用默认参数实现参数是可选的
// 默认参数不要求放在参数后面
function buildName3(firstName: string, lastName: string = "sr") {
    return firstName + " " + lastName
}

let result33 = buildName3("bob")
let result333 = buildName3("bob", "sr")

function buildName4(firstName: string = "lili", lastName: string = "sr") {
    return firstName + " " + lastName
}

buildName4()
buildName4("lili", "bob")
buildName4(undefined, "bob")

可选参数和默认参数的区别是,可选参数?修饰必须放在参数的后面,而默认参数是没有这个要求的。

默认参数和可选参数的函数本质还是同一个函数类型,比如buildName2()buildName3()函数的类型都是(firstName: string, lastName?:string) => string

在这里插入图片描述

剩余参数

如果要函数需要接收多个不确定数量的同类型参数,默认参数和可选参数都是不适合的,它们只能表示某一个参数,在JavaScript里,可以通过arguments来访问所有传入的参数。在TypeScript里没有这个arguments,但可以通过剩余参数来达到目的。

剩余参数和JavaScript是相似的,在参数名称前面添加省略号...,用类型数组接收。

function buildName5(firstName: string, ...restName: string[]): string {
    return firstName + " " + restName.join(" ")
}

buildName5("bob", "haha", "niu")

any任意类型

// JavaScript参数是可以接收任何类型的数据,由于TypeScript是有类型的,
// 如果要兼容JavaScript代码,必须要有一个类型来表示任意类型,这个类型就是any
// 和kotlin是一样的,any是所有类型的父类型。

function stringify(obj: any) {
    return JSON.stringify(obj)
}

console.log(stringify([1, 2, "3"]))
console.log(stringify({a: "11", b: "22"}))

类型断言

// 由于有任意类型any,当参数用any接收时,如果我们需要是某个具体类型时,这个就需要将any断言成具体类型
// 类型断言as并不是将类型强转,这与kotlin是不同的

// 比如求和函数
function sum(params:any){

    if (typeof params == "string"){
        console.log("字符串 " +params)

    }

    if (typeof params == "number"){
        console.log("数值 "+params)
    }
    // 类型断言
    let a = params as number
    console.log(a + 100)

}

sum("abc")
sum(22)

高级类型

交叉类型

交叉类型是指两个对象的属性并集合成一个新类型,前提是其中一个对象的属性或方法在另外一个对象都具有,也就是该类型具有所有类型的特性。

class Student {
    name: string
    age: number
}


class Teacher {
    name: string
    age: number
    _occupation: string


    constructor(occ: string) {
        this._occupation = occ
    }

    log() {
        console.log("occupation: " + this._occupation)
    }


}

// 定义交叉类型,使用 &
let result: Teacher & Student;
// result = new Student() // 报错 不能赋值Student类型,Student没有age、_occupation log
result = new Teacher("老师")
result.log()
console.log(result)

// 再次赋值
result = {
    name: "lili",
    age: 25,
    _occupation: "美术老师",
    log: function () {

    },
    // 不能有其他方法或属性,报错
    // test:function (){
    //
    // }
}

console.log(result)

let getUserInfo= function (info : Student & Teacher){
    console.log(info.name)
    console.log(info.age)
    console.log(info._occupation)
    info.log()
}

getUserInfo(result)

// getUserInfo(Student()) // 报错

日志:

老师
Teacher { _occupation: '老师' }
{ name: 'lili', age: 25, _occupation: '美术老师', log: [Function: log] }

lili
25
美术老师

Teacher字面量对象类型具有所有的属性和方法,而Student是不满足该条件,因此是不能对交叉类型result赋值。

联合类型

联合类型是指两个类型的交集,取值可以为多种类型中的一种。
比如定义变量:

// 可能是string或者number类型,其中一种即可
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

如果联合类型是对象,那么只能访问两者公共的属性或方法:

//联合类型如果是对象,只能访问两者公共的属性或方法

class Student {
    name: string ="学生"
    age: number = 16

}


class Teacher {
    name: string = "王老师"
    age: number = 25
    private readonly occupation: string


    constructor(occ: string) {
        this.occupation = occ
    }

    log() {
        console.log("occupation: " + this.occupation)
    }


}

let st : Student | Teacher
st = new Student()
console.log(st)
// 虽然是st的实例是指向Teacher对象,但只能方法联合类型的共同属性age和name,不能访问log()方法
st = new Teacher("教美术")
console.log(st)
// st.log() // 报错


function getUserInfo(): Student | Teacher {
    return new Student()
}

console.log(getUserInfo()) //只能访问 name 与 age属性

function getUserInfo2(): Student | Teacher {
    return new Teacher("教数学")
}

console.log(getUserInfo2())// 访问不到log()方法

日志:

Student { name: '学生', age: 16 }
Teacher { name: '王老师', age: 25, occupation: '教美术' }
Student { name: '学生', age: 16 }
Teacher { name: '王老师', age: 25, occupation: '教数学' }

与交叉类型不同的是,联合类型定义可以使用原始类型,而交叉类型不能用原始类型声明。

接口

接口是对行为的抽象,具体实现是由类来实现。

一个函数如果有多个形参,除了用剩余参数来接收,通常还会用对象来接收,除此还可以用接口的方式来接收实参,可以约束参数的类型。

// 用对象接收多个参数
function test(params: {
    name: string,
    age: number,
    sex: number,
    occ?: string,
} = {name: "", age: 0, sex: 0}) {
    console.log(params)
}

test({name: "lili", age: 20, sex: 1})

// 用接口接收多个参数
interface People {
    name: string;
    age: number;
    readonly sex: number; // 只读属性
    action?: string;// 可选属性
}

function printUser(params: People) {
    console.log(params)
}

// 对象类型要显式声明
let people: People = {name: "lisi", age: 18, sex: 1}
// people.sex = 0 // 只读属性,重新赋值会报错
let people2 = {name: "lili"}
printUser(people)
// printUser(people2) // ide会报错,缺少属性

当接口显式声明类型时,可以用字面量对象给变量赋值

let people: People = {name: "lisi", age: 18, sex: 1}

只读属性关键字readonly 声明,可选属性在变量名称加?

字面量对象或class类的属性都是可以这样

readonly sex: number; // 只读属性,只能赋值一次,通常在创建对象时赋值
action?: string;// 可选属性

在Java等面向对象中,对象的属性在编译期就已经确定了,ts作为前端语言,如果不能实现动态化添加属性,就失去js的灵活性了,因此ts提供了动态属性,动态属性又称任意属性。

动态属性用中括号声明[占位符]:

占位符就是声明属性名称是用字符串表示,基本是固定的。

interface Animal {
    name: string;
    // 动态属性
    [propName: string]: string;

}

使用 [propName: string] 定义了动态属性取 string 类型的值:

let dog: Animal = {name: "狗",}
// 设置动态属性,其中 action 和 sex 就是属性名
dog.action = "跑"
// dog.sex = 1 // 与动态属性的值类型与声明类型不匹配会报错
dog.sex = "1"
console.log(dog)

// 访问动态属性
console.log(dog.sex) // 对象.属性名
console.log(dog['sex']) // 对象[属性名] ,类似索引的方式

访问动态属性,用对象实例.属性名对象[属性名]

日志:

{ name: '狗', action: '跑', sex: '1' }
1
1

在接口Animal定义时,是没有action sex属性名,通常动态属性的设置可以灵活的添加属性。

在一个接口中只能定义一个动态属性,如果属性有多个类型的话,可以
通过联合类型来定义。比如sex动态属性的值可能是numberstring类型,使用联合类型定义:

interface Animal2 {
    name: string;
    [propName: string]: string | number;

}

let dog2: Animal2 = {
    name : "狗"
}

dog2.sex = 1
dog2.sex = "母"

类的概念在面向对象语言中是不可或缺的,在es6就已经引进了类的概念。

用class声明一个类,constructor定义构造函数,通过new创建类的实例。

class People {
    name: string;

    // 构造函数
    constructor(name: string) {
        this.name = name
    }

    // 方法不用function定义,在es6也是可以省略的
    getName() {
        return this.name
    }

}

// 创建实例
let p = new People("lili")

在es6中类的实例属性的作用域是没有约束的,默认是公开的,如果定义私有属性,通常是使用’_'下划线来区分,外部实例还是可以访问的;在ts提供了和java类似的关键字来声明属性的作用域。

  • 默认是public
  • private 私有属性:只有在当前类下才可以访问,即使是子类也是不能访问的
  • protected 受保护属性:只有在类或子类内部才可以访问,类外部是无法访问的
class Animal {
    // 默认是public
    name: string;
    // 在es6 如果定义私有属性,通常是使用'_'下划线来区分,外部实例还是可以访问的
    // 私有属性,只有当前类才可以访问,即使是子类也是不能访问的
    // 方法也是一样的
    private distanceInMeters: number = 0
    // 属性或参数用readonly修饰:表示只读,不能赋值
    // private readonly distanceInMeters: number = 0
    // 受保护属性
    protected _sex: number

    // 静态属性 : 与实例是无关的,不用实例就可以调用
    // 每个类都可以访问,但内存不是共享的
    static mouth = {size: 5}

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

    // 方法设置默认参数
    move(distanceInMeters: number = 0) {
        this.distanceInMeters = distanceInMeters
        console.log(`${this.name} moved ${distanceInMeters} m`)
    }

    // 在es6中提供了类的setter和getter方法,在java是常见的
    get sex(): number {
        return this._sex
    }

     set sex(sex: number) {
        this._sex = sex
    }
    
    // 静态方法
    static getMouth(): string {
        return Animal.mouth + ""
    }


}

静态属性和方法与类的实例是无关的,直接通过类名来调用,与java是一样的,在内存中是共享的。

继承用extends关键字实现:

class Dog extends Animal {
    // 如果子类不定义构造函数,会默认使用父类的构造函数
    bark() {
        console.log('woof! woof!')

    }

    // 受保护属性: 只有在类或子类内部才可以访问,类外部是无法访问的
    getSex() {
        return  this._sex
    }
}

let dog = new Dog("dog", 1)
dog.move(10)
dog.bark()
// dog._sex // 在类外部访问不了protected的属性
console.log("dog sex: " + dog.getSex())
Animal.mouth = {size: 10}
console.log(Animal.mouth)

// dog.distanceInMeters // 报错,私有属性无法访问

多态是java的三大特性之一,在ts同样也是支持的,多态的三个必要条件:

  • 继承
  • 方法的重写
  • 父类的引用指向子类
class Snake extends Animal {
    constructor(name: string, sex: number) {
        super(name, sex);
    }

    // 重写父类的方法
    move(distanceInMeters: number = 5) {
        super.move(distanceInMeters);
    }
}

class Horse extends Animal {
    constructor(name: string, sex: number) {
        super(name, sex);
    }

    move(distanceInMeters: number = 40) {
        super.move(distanceInMeters);
    }
}

let snake = new Snake("Snake", 1)
// 虽然变量的类型声明是父类Animal,但move输出的是子类实例的信息
// 从而也反映了ts的类也是具有多态性
// 多态的三个必要条件:1、继承;2、重写;3、父类引用指向子类实例对象
// 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
let horse: Animal = new Horse("Horse", 0)
snake.move() // Snake moved 5 m
horse.move() // Horse moved 40 m
console.log("snake sex: " + snake.sex) // 访问getter方法

// 静态属性,与类的实例无关
Snake.mouth = {size: 20}
console.log(Snake.mouth)

抽象类不能直接创建实例,必须通过子类来创建实例

// 抽象类不能直接创建实例
abstract class Animal2 {
    // 抽象方法,子类必须实现该方法
    abstract makeSound(): void;

    move(): void {
        console.log('roaming the earch...');
    }
}

class Duck extends Animal2 {
    // 子类实现抽象方法
    makeSound(): void {
    }

}

// 创建实例
let duck = new Duck()

接口是行为抽象化,没有具体实现,需要通过类来实现细节。

interface ClockInterface {
    currentTime: Date

    setTime(d: Date)
}

class Clock implements ClockInterface {
    currentTime: Date;

    setTime(d: Date) {
        this.currentTime = d
    }

}

泛型

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。在面向对象语言中是十分重要的,优秀的代码都离不开对泛型的理解与使用,能将代码模板化,提高复用性。

比如下面这个例子,在没有使用泛型的情况下:

// id形参为了可以接收任意类型,声明any类型,但返回类型无法确定。
function identity(id: any) {
    return id
}

identity("23")

identity(23)

使用泛型后,在使用期间就知道了具体类型。

// 用泛型就可以解决上面的问题
function identity2<T>(id: T): T {
    return id
}
// 指明泛型T的具体类型是number 
let id = identity2<number>(12)
console.log(id)

泛型也是有类型的,比如identity2

// <T>(id: T) => T 就是identity2函数的类型
let myId: <T>(id: T) => T = identity2
myId<string>("AABB")

泛型在类中的应用:

class Generic<T> {
    value: T;

    // 泛型不能做运算
    // add(x:T,y:T):T{
    //     return x + y
    // }

    // 定义一个泛型函数类型的属性,类型是(x: T, y: T) => T
    add: (x: T, y: T) => T
}

let generic = new Generic<number>()
// 没有add变量赋值,运行会报错
// generic.add(1,2) // 报错: generic.add is not a function

// 赋值后,再调用就不会报错
generic.add = function (x, y) {
    return x + y
}
let ab = generic.add(1, 3)
console.log(ab)


//-- 泛型约束
abstract class Animal {
    abstract getName(): string
}

class Dog extends Animal {
    getName(): string {
        return "Dog";
    }


}

class Bee extends Animal {
    getName(): string {
        return "Bee";
    }

}

class Car {
    getName(): string {
        return "Car";
    }
}

function createAnimal<T extends Animal>(a: new () => T): T {
    // 在java中,泛型是不可以直接在方法中创建实例,因此在编译时会被檫出
    // 在kotlin中 可以通过内联特化和反射创建泛型的实例
    return new a()
}


let d = createAnimal<Dog>(Dog)
console.log(d.getName())
let b = createAnimal(Bee)
console.log(b.getName())

let c = createAnimal(Car) // ???
console.log(c.getName())

类型别名

// 类型别名:给一个类型取一个新名字
type Name = string
type NameResolver = () => string
type  NameOrResolver = Name | NameResolver


// 函数返回值是一个字符串,形参NameOrResolver是一个联合类型,是一个函数类型或字符串
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

参考

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

H.ZWei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值