文章目录
- 什么是TS
- 前期准备
- 安装TS
- TS配置文件
- 基础类型
- 原始类型
- object类型
- 数组类型
- 元组类型
- 枚举
- 函数类型
- 可选参数和默认参数
- 剩余参数
- any任意类型
- 高级类型
- 交叉类型
- 联合类型
- 接口
- 类
- 泛型
- 类型别名
- 参考
什么是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 +" "+ lastNamefunction 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 numberconsole.log(a + 100)}sum("abc")
sum(22)
高级类型
交叉类型
交叉类型是指两个对象的属性并集合成一个新类型,前提是其中一个对象的属性或方法在另外一个对象都具有,也就是该类型具有所有类型的特性。
class Student {name: stringage: number
}class Teacher {name: stringage: number_occupation: stringconstructor(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 = 25private readonly occupation: stringconstructor(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
动态属性的值可能是number
或string
类型,使用联合类型定义:
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 {// 默认是publicname: 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 = namethis._sex = sex}// 方法设置默认参数move(distanceInMeters: number = 0) {this.distanceInMeters = distanceInMetersconsole.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: DatesetTime(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) => Tadd: (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();}
}
参考
- https://segmentfault.com/a/1190000040220033
- https://www.tslang.cn/docs/home.html
- https://ts.xcatliu.com/introduction/what-is-typescript.html
- https://juejin.cn/post/7106079206160891918