国内优秀网站推荐,口碑宣传,网络架构方法,建设企业网站有哪些你好#xff0c;我是沐爸#xff0c;欢迎点赞、收藏、评论和关注。
一、TS是什么#xff1f;
TypeScript 由微软开发#xff0c;是基于 JavaScript 的一个扩展语言。TypeScript 包含 JavaScript 的所有内容#xff0c;是 JavaScript 的超集。TypeScript 增加了静态类型检…你好我是沐爸欢迎点赞、收藏、评论和关注。
一、TS是什么
TypeScript 由微软开发是基于 JavaScript 的一个扩展语言。TypeScript 包含 JavaScript 的所有内容是 JavaScript 的超集。TypeScript 增加了静态类型检查、接口、泛型等很多现代开发特性因此更适合大型项目的开发。TypeScript 需要编译为 JavaScript然后交给浏览器或其他 JavaScript 运行环境执行。TypeScript 支持任意浏览器任意环境任意系统并且是开源的。
二、为什么需要TS
今非昔比的 JavaScript了解
JavaScript 当年诞生时的定位是浏览器脚本语言用于在网页中嵌入一些简单的逻辑而且代码量很少。随着时间的推移JavaScript 变得越来越流行如今的 JavaScript 已经全栈编程了。现如今的 JavaScript 应用场景比当年丰富的多代码量也比当年大很多随便一个 JavaScript 项目的代码量可以轻松达到几万行甚至几十万行然而 JavaScript 当年 出生简陋没考虑到如今的应用场景和代码量逐渐的就出现了很多困扰。
JavaScript 中的困扰
1.不清不楚的数据类型
let welcome hello
welcome() // TypeError: welcome is not a function
2.有漏洞的逻辑
const str Date.now() % 2 ? 奇数 : 偶数if (str ! 奇数) {console.log(hello)
} else if (str 偶数) {console.log(world) // 这里永远不会执行但不会报错
}
3.访问不存在的属性
const obj {width: 20,height: 10
}const area obj.width * obj.heigth // 这里并不会报错console.log(area) // NaN
4.低级的拼写错误
const message hello,world
message.toUperCase() // 拼写错误但不会提示
【静态类型检查】
在代码运行前进行检查发现代码的错误或不合理之处减少运行时异常的出现几率此种检查叫【静态类型检查】TypeScript 核心就是【静态类型检查】简言之就是把运行时的错误前置。同样的功能TypeScript 的代码量要大于 JavaScript但由于 TypeScript 的代码结构更加清晰在后期代码的维护中 TypeScript 却远胜于 JavaScript。
三、编译TS
浏览器不能直接运行 TypeScript 代码需要编译为 JavaScript 再交给浏览器解析器执行。
1.命令行编译
要把 .ts 文件编译为 .js 文件需要配置 TypeScript 的编译环境步骤如下
第一步创建一个 demo.ts 文件例如
const person {name: 小明,age: 6
}
console.log(我叫${person.name}我今年${person.age}岁了)
第二步全局安装 TypeScript
npm i typescript -g# 查看版本号
tsc -v
第三步使用命令编译 .ts 文件
tsc demo.ts# 依次编译多个.ts文件
tsc demo.ts demo2.ts demo3.ts
执行编译命令后会生成一个 demo.js 文件
var person {name: 小明,age: 6
};
console.log(\u6211\u53EB.concat(person.name, \uFF0C\u6211\u4ECA\u5E74).concat(person.age, \u5C81\u4E86));如果 tsc 命令不能被识别管理员权限打开 PowerShell 输入set-ExecutionPolicy RemoteSigned 按回车。
2.自动化编译
第一步创建 TypeScript 编译控制文件
tsc --init
工程中会生成一个 tsconfig.json 配置文件其中包含很多编译时的配置。观察发现默认编译的 JS 版本是 ES7我们可以手动调整为其他版本。
第二步监视目录中的 .ts 文件变化
tsc --watch
第三步小优化当编译出错时不生成 .js 文件默认情况下出错也会生成 .js 文件
tsc --noEmitOnError --watch
备注也可以修改 tsconfig.json 中的 noEmitOnError 配置
3.不编译运行
如果在 Node 环境可以直接运行.ts 文件吗答案是可以的。不过需要全局安装 ts-node然后就可以直接用 node 运行 .ts文件了。
npm i -g ts-nodets-node demo.ts
注意
相同文件夹下的不同 .ts 文件不能有同名变量则会提示重复声明变量
let age: number
// Cannot redeclare block-scoped variable age
其实问题出在了变量命名空间如果不把文件当作模块使用的话 TypeScript 会认为所有文件里的代码都是在同一个作用域里的所以即使在不同文件也不能声明同名变量。 七、常用类型
1.any
any的含义是任意类型一旦将变量类型限制为 any那就意味着放弃了对该变量的类型检查。
// 明确的表示a的类型是 any 【显式的any】
let a: any
// 以下对a的赋值均无警告
a 100
a hello
a false// 没有明确的表示b的类型是any但TS主动推断出来b是any 【隐式地any】
let b
// 以下对b的赋值均无警告
b 100
b hello
b false
注意点any类型的变量可以赋值给任意类型的变量
let c: any
c 9let x: string
x c // 无警告
2.unknown
unknown的含义是未知类型。
1.unknown可以理解为一个类型安全的any适用于不确定数据的具体类型。
// 设置a的类型为unknown
let a: unknown// 以下对a的赋值均正常
a 100
a hello
a false// 设置x的数据类型为string
let x: string
x a // Type unknown is not assignable to type string
2.unknown会强制开发者在使用之前进行类型检查从而提供更强的类型安全性。
// 设置a的类型为unknown
let a: unknown
a hello// 第一种方式加类型判断
if (typeof a string) {x a
}// 第二种方式加断言
x a as string// 第三种方式加断言
x stringa
3.读取any类型数据的任何属性都不会报错而unknown正好与之相反。
let str1: string
str1 hello
str1.toUpperCase() // 无警告let str2: any
str2 hello
str2.toUpperCase() // 无警告let str3: unknown
str3 hello
str3.toUpperCase() // str3 is of type unknown// 使用断言强制指定str3的类型为string
(str3 as string).toUpperCase() // 无警告 3.never
never的含义是任何值都不是简而言之就是不能有值undefined、null、、0都不行
1.几乎不用never去直接限制变量因为没有意义例如
// 指定a的类型为never那就意味着a以后不能存在任何的数据了
let a: never// 以下对a的赋值都会有警告
a 1
a true
a undefined
a null
2.never一般是TypeScript主动推断出来的例如
// 指定a的类型为string
let a: string
a helloif (typeof a string) {console.log(a.toUpperCase())
} else {console.log(a) // TS会推断出此处的a是never因为没有任何一个值符合此处的逻辑
}
3.never也可用于限制函数的返回值
// 限制throwError函数不需要有任何返回值任何值都不行包括undefined、null
function throwError(str: string): never {throw new Error(程序异常 str)
}
4.void
1.void通常用于函数返回值声明含义【函数返回值为空调用者不应该依赖其返回值进行任何操作】
function logMessage(msg: string): void {console.log(msg)
}
logMessage(hello)
注意编码者没有编写return去指定函数的返回值所以logMessage函数是没有显式返回值的但会有一个隐式返回值就是undefined即虽然函数返回类型为void但也是可以接受undefined的简单记undefined是void可以接受的一种空。
2.以下写法均符合规范
function logMessage(msg: string): void {console.log(msg)
}function logMessage(msg: string): void {console.log(msg)return
}function logMessage(msg: string): void {console.log(msg)return undefined
}
3.那限制函数返回值时是不是undefined和void就没有区别呢----有区别。因为还有这句话【返回值类型为void的函数调用者不应该依赖其返回值进行任何操作】对比下面两段代码
function logMessage(msg: string): void {console.log(msg)
}let result logMessage(hello)if (result) { // An expression of type void cannot be tested for truthinessconsole.log(logMessage有返回值)
}
function logMessage(msg: string): undefined {console.log(msg)
}let result logMessage(hello)if (result) { // 这里无警告console.log(logMessage有返回值)
}
理解 void 与 undefined
void是一个广泛的概念用来表达空而undefined则是这种空的具体实现之一。因此可以说undefined是void能接受的空状态的一种具体形式。换句话说void包含undefined但void表达的语义超越了单纯的undefined它是一种意图上的约定而不仅仅是特定值的限制。
总结若函数返回类型void那么
从语法上讲函数可以返回undefined至于显式返回还是显式返回都无所谓从语义上讲函数调用这不应关系函数的返回值也不应该依赖返回值进行任何操作即使返回了undefined值。
5.object
关于object与Object直接说结论实际开发中用的较少因为范围太大了。
object小写
object的含义是所有非原始类型可存储对象、函数、数组等由于限制的范围比较宽泛在实际开发中使用的相对较少。
let a: object // a的值可以是任何非原始类型包括对象、函数、数组等。// 以下代码是将非原始类型赋值给a所以均符合要求
a {}
a {name: 张三}
a [1, 2, 3]
a function() {}
a new String(hello)
class Person {}
a new Person()// 以下代码是将原始类型赋值给a会警告
a 1
a true
a hello
a null
a undefined
Object大写
官方描述所有可以调用Object方法的类型。简单记忆除了undefined和null的任何值。由于限制的返回实在太大了所以实际开发中使用频率极低。
let a: Object // a的值必须是Object的实例对象出去undefined和null的任何值// 以下代码均无警告因为赋值给a的值都是Object的实例对象
a {}
a {name: 张三}
a [1, 2, 3]
a function() {}
a new String(hello)
class Person {}
a new Person()
a 1
a true
a hello// 以下代码均有警告
a null // Type null is not assignable to type Object
a undefined // Type undefined is not assignable to type Object
声明对象类型
1.实际开发中限制一般对象通常使用以下形式
// 限制person对象必须有name属性age为可选属性
let person1: { name: string, age?: number }// 含义同上也能用分号做分隔
let person2: { name: string; age?: number }// 含义同上也能用换行做分隔
let person3: {name: stringage?: number
}// 如下赋值均可以
person1 { name: 张三, age: 18}
person2 { name: 李四 }
person3 { name: 王五 }// 如下赋值不合法
person3 { name: 王五, gender: 男 } // Object literal may only specify known properties, and gender does not exist in type { name: string; age?: number | undefined; }
2.索引签名允许定义对象可以具有任意数量的属性这些属性的键和类型是可变的常用于描述类型不确定的属性具有动态属性的对象。
// 限制person对象必须具有name属性可选age属性是数字同时可以具有任意数量、任意类型的属性
let person {name: stringage?: number[key: string]: any // 索引签名完全可以不用key这个单词换成其他的也可以
}// 赋值合法
person {name: 张三,age: 20,gender: 男
}
声明函数类型
let count: (a: number, b: number) numbercount function (x, y) {return x y
}count (x, y) {return x y
}
备注
TypeScript 中的 在函数类型声明时表示函数类型描述其参数类型和返回类型。JavaScript 中的 是一种定义函数的语法是具体的函数实现。函数类型声明还可以使用接口、自定义类型等方式下文中会详细讲解。
声明数组类型
let arr1: number[]
let arr2: Arraynumber // 泛型arr1 [1, 2, 3]
arr2 [4, 5, 6]
6.tuple
元组tuple是一种特殊的数组类型存储一组固定数量和固定类型的元素。元组用于精确描述一组值的类型?表示可选元素。 // 第一个元素必须是 string 类型第二个元素必须是 number 类型
let arr1: [string, number]// 第一个元素必须是 number 类型第二个元素时可选的如果存在必须是 boolean 类型
let arr2: [number, boolean?]// 第一个元素必须是 number 类型后面的元素可以是任意数量的 string 类型
let arr3: [number, ...string[]]// 可以赋值
arr1 [hello, 123]
arr2 [100, false]
arr2 [200]
arr3 [100, a, b]
arr3 [100]// 不可以赋值
arr1 [hello, 123, false]
7.enum
枚举enum可以定义一组命名常量它能增强代码的可读性也让代码更好维护。
如下代码的功能是根据调用walk时传入的不同参数执行不同的逻辑存在的问题是调用walk时传参时没有任何提示开发者很容易写错字符串内容并且用于判断逻辑的up、down、left、right是连续且相关的一组值那此时就特别适合使用枚举enum。
function walk(str: string) {if (str up) {console.log(向上走)} else if (str down) {console.log(向下走)} else if (str left) {console.log(向左走)} else if (str right) {console.log(向右走)} else {console.log(未知方向)}
}walk(up)
walk(down)
walk(left)
walk(right)
1.数字枚举
数字枚举是一种最常见的枚举类型其成员的值会自动递增且数字枚举还具备反响映射的特点在下面代码的打印中不难发现可以通过值来获取对应的枚举成员名称。
// 定义一个描述【上下左右】方向的枚举Direction
enum Direction {Up,Down,Left,Right
}console.log(Direction)
// {
// 0: Up,
// 1: Down,
// 2: Left,
// 3: Right,
// Up: 0,
// Down: 1,
// Left: 2,
// Right: 3
// }// 反向映射
console.log(Direction.Up) // 0
console.log(Direction[0]) // Up// 此代码报错枚举中的属性是只读的
Direction.Up shang // Cannot assign to Up because it is a read-only property
也可以指定枚举成员的初始值其后的成员值会自动自增。
enum Direction {Up 6,Down,Left,Right
}console.log(Direction.Up) // 6
console.log(Direction.Down) // 7
使用数字枚举完成刚才walk函数中的逻辑此时我们发现代码更加直观易读而且类型安全同时也易于维护。
enum Direction {Up,Down,Left,Right
}function walk(n: Direction) {if (n Direction.Up) {console.log(向上走)} else if (n Direction.Down) {console.log(向下走)} else if (n Direction.Left) {console.log(向左走)} else if (n Direction.Right) {console.log(向右走)} else {console.log(未知方向)}
}walk(Direction.Up) // 向上走
walk(Direction.Down) // 向下走 3.常量枚举
官方描述常量枚举是一种特殊枚举类型它使用 const关键字定义在编译时会被内联避免生成一些额外的代码。
使用普通枚举的 TypeScript 代码如下
enum Direction {Up,Down,Left,Right
}let x Direction.Up
编译后生成的 JavaScript 代码量较大
use strict;var Direction;
(function (Direction) {Direction[Direction[Up] 0] Up;Direction[Direction[Down] 1] Down;Direction[Direction[Left] 2] Left;Direction[Direction[Right] 3] Right;
})(Direction || (Direction {}));
var x Direction.Up;
使用常量枚举的 TypeScript 代码如下
const enum Direction {Up,Down,Left,Right
}let x Direction.Up
编译后生成的 JavaScript 代码量较小
use strict;var x 0 /* Direction.Up */;
8.type
type可以为任意类型创建别名让代码更简洁、可读性更强同时能更方便地进行复用和扩展。
1.基本用法
类型别名使用type关键字定义type后跟类型名称例如下面代码中 num 是类型别名。
type num numberlet price: num
price 10
2.联合类型
联合类型是一种高级类型它表示一个值可以是几种不同类型之一。
type Status number | string
type Gender 男 | 女function printStatus(status: Status) {console.log(status)
}function logGender(str: Gender) {console.log(str)
}printStatus(404)
printStatus(200)
printStatus(501)logGender(男)
logGender(女)
3.交叉类型
交叉类型允许将多个类型合并为一个类型。合并后的类型将拥有所有被合并类型的成员。交叉类型通常用于对象类型。
// 面积
type Area {height: number // 高width: number // 宽
}// 地址
type Address {num: number // 楼号cell: number // 单元号room: string // 房间号
}type House Area Addressconst house: House {height: 180,width: 75,num: 6,cell: 3,room: 702
}
9.一个特殊情况
代码段1正常
在函数定义时限制函数返回值为void那么函数的返回值就必须是空。
function demo(): void {// 返回 undefined 合法return undefined// 以下返回均不合法return 100return falsereturn nullreturn []
}
代码段2特殊
使用类型声明限制函数返回值为void时TypeScript 并不会严格要求函数返回空。
type LogFunc () voidconst f1: LogFunc () {return 100 // 允许返回非空值
}const f2: LogFunc () 200 // 允许返回非空值const f3: LogFunc function() {return 300 // 允许返回非空值
}
为什么会这样
是为了确保如下代码成立我们知道Array.prototype.push的返回一个数字而Array.prototype.forEach方法期望其回调的返回类型是void
const arr [1, 2, 3]
const arr2 [0]arr.forEach((el) arr2.push(el))
官方文档的说明TypeScript: Documentation - More on Functions
10.复习类相关知识
本小结复习类相关知识如果有相关基础可以跳过。
class Person {// 属性声明name: stringage: number// 构造器constructor(name: string, age: number) {this.name namethis.age age}// 方法speak() {console.log(我叫${this.name},今年${this.age}岁)}
}// Person 实例
const p1 new Person(张三, 20)// Student 继承 Person
class Student extends Person {grade: string// 若 Student 类不要额外属性Student的构造器可以省略constructor(name:string, age: number, grade: string) {super(name, age)this.grade grade}// 重写父类继承的方法override speak() {console.log(我是学生我叫${this.name},今年${this.age}岁我在读${this.grade}年级)}// 子类自己的方法study() {console.log(${this.name}正在努力学习中...)}
} 11.属性修饰符 修饰符 含义 具体规则 public 公开的 可以被类内部、子类、类外部访问 protected 受保护的 可以被类内部、子类访问 private 私有的 可以被类内部访问 readonly 只读属性 属性无法修改
public修饰符
class Person {// name写了public修饰符age没写修饰符但默认是public修饰符public name: stringage: numberconstructor(name: string, age: number) {this.name namethis.age age}speak() {// 类的【内部】可以访问public修饰的name和ageconsole.log(我叫${this.name},今年${this.age}岁)}
}const p1 new Person(张三, 20)
// 类的【外部】可以访问public修饰的属性
console.log(p1.name)class Student extends Person {constructor(name:string, age: number, grade: string) {super(name, age)}study() {// 子类可以访问父类中public修饰的name/ageconsole.log(${this.age}岁的${this.name}正在努力学习中...)}
}
属性的简写形式
简写前
class Person {public name: stringpublic age: numberconstructor(name: string, age: number) {this.name namethis.age age}
}
简写后
class Person {constructor(public name: string, public age: number) {}
}
protected
class Person {// name和age是受保护属性不能在类外部访问但在【类】和【子类】中访问constructor(protected name: string,protected age: nuber) {}// getDetails是受保护方法不能在类外部访问但可以在【类】和【子类】中访问protected getDetails(): string {// 类中能访问受保护的name和age属性return 我叫${this.name},今年${this.age}岁}// introduce 是公开方法类、子类和类外部都能访问introduce() {// 类中能访问受保护的 getDetails 方法console.log(this.getDetails())}
}const p1 new Person(张三, 20)
// 可以在类外部访问introduce
p1.introduce()// 以下代码均报错
// p1.getDetails()
// p1.name
// p1.ageclass Student extends Person {study() {console.log(this.getDetails())console.log(${this.name}正在努力学习)}
}const s2 new Student(小明, 8)
s1.study()
privated
class Person {constructor(public name: string,public age: number,private IDCard: string) { }private getPrivateInfo() {return 身份证号码为${this.IDCard}}getInfo() {return 我叫${this.name}, 今年刚满${this.age}岁}getFullInfo() {return this.getInfo() , this.getPrivateInfo()}
}const p1 new Person(小明, 18, 423516200012135569)
p1.name
p1.age
p1.IDCard // Property IDCard is private and only accessible within class Person
console.log(p1.getInfo())
console.log(p1.getFullInfo())
p1.getPrivateInfo() // Property getPrivateInfo is private and only accessible within class Person
readonly 修饰符
class Car {constructor(public readonly vin: string, // 车辆识别码只读属性public readonly year: number, // 出厂年份只读属性public color: string,public sound: string) { }// 打印车辆信息displayInfo() {console.log(识别码${this.vin},出厂年份${this.year},颜色${this.color},音响${this.sound})}
}const car new Car(hdyejdukeikduejuhf, 2018, 黑色, Bose音响)
car.displayInfo()// 修改vin和year都会报错
car.vin hdyejudkisessedkuhi
car.year 2000
12.抽象类
概述抽象类是一种无法被实例化的类专门用来定义类的结构和行为类中可以写抽象方法也可以写具体实现。抽象类主要用来为其派生类提供一个基础结构要求其派生类必须实现其中的抽象方法。简记抽象类不能实例化其意义是可以被继承抽象类里可以有普通方法也可以有抽象方法。
通过以下场景理解抽象类
我们定义一个抽象类package表示所有包裹的基本结构任何包裹都具有重量属性weight包裹都需要计算运费。但不同类型的包裹如标准速度、特快专递都有不同的运费计算方式因此用于计算运费的calculate方法是一个抽象方法必须由具体的子类来实现。
abstract class Package {// 构造方法constructor(public weight: number) {}// 抽象方法abstract calculate(): number// 具体方法printPackage() {console.log(包裹重量为${this.weight}kg运费为${this.calculate()}元)}
}
class StandardPackage extends Package {constructor(weight: number,public unitPrice: number) { super(weight) }calculate(): number {return this.weight * this.unitPrice}
}
StandardPackage类继承了Package实现了calculate方法
class ExpressPackage extends Package {constructor(weight: number,public unitPrice: number,public additional: number) { super(weight) }calculate(): number {if (this.weight 10) {return 10 * this.unitPrice (this.weight - 10) * this.additional} else {return this.weight * this.unitPrice}}
}const e1 new ExpressPackage(13, 8, 2)
e1.printPackage()
总结何时使用抽象类
定义通用接口为一组相关的类定义通用的行为方法或属性时。提供基础实现在抽象类中提供某些方法或为其提供基础实现这样派生类就可以继承这些实现确保关键实现强制派生类实现一些关键行为。共享代码和逻辑当多个类需要共享部分代码时抽象类可以避免代码重复。
13.interface(接口)
interface是一种定义结构的方式主要作用是为类、对象、函数等规定一种契约这样可以确保代码的一致性和类型安全但要注意interface只能定义格式不能包含任何实现
定义类结构
// PersonInterface接口用于限制Person类的格式
interface PersonInterface {name: stringage: numberspeak(n: number): void
}// 定义一个类 Person实现 PersonInterface 接口
class Person implements PersonInterface {constructor(public name: string,public age: number) { }// 实现接口中的 speak 方法注意实现speak时参数个数可以少于接口中的规定但不能多。speak(n: number): void {for (let i 0; i n; i) {// 打印出包含名字和年龄的问候语句console.log(你好我叫${this.name},我的年龄是${this.age})}}
}// 创建一个 Person 类的实例 p1传入名字 tom 和年龄 18
const p1 new Person(tom, 18)
p1.speak(3)
定义对象结构
interface User {name: stringreadonly gender: string // 只读属性age?: number // 可选属性run: (n: number) void
}const user: User {name: 张三,gender: 男,age: 18,run(n) {console.log(奔跑了${n}米)}
}
定义函数结构
interface CountInterface {(a: number, b: number): number
}const count: CountInterface (x, y) {return x y
}
接口之间的继承
一个interface继承另一个interface从而实现代码的复用。
interface PersonInterface {name: stringage: number
}interface StudentInterface extends PersonInterface {grade: string
}const stu: StudentInterface {name: zhangsan,age: 25,grade: 高三
}
接口可合并
interface PersonInterface {name: stringage: number
}interface PersonInterface {gender: string
}const p: PersonInterface {name: zhangsan,age: 18,gender: 男
}
总结何时使用接口
定义对象的格式描述数据模型、API响应格式、配置对象...是开发中用的最多的场景。类的契约规定一个类需要实现哪些属性和方法。自动合并一般用于扩展第三方库的类型这种特性在大型项目中可能会用到。
八、泛型
泛型允许我们在定义函数、类或接口时使用类型参数来表示未指定的类型这些参数在具体使用时才被指定具体的类型泛型能让同一段代码适用于多种类型同时仍然保持类型的安全性。
举例来说如下代码中T就是泛型不一定叫T设置泛型后即可在函数中使用T来表示该类型
泛型函数
function logDataT(data: T): T {console.log(data)return data
}logData(10) // 不指定泛型TS可以自动对类型进行推断
logDatanumber(100) // 指定泛型
logDatastring(hello) // 指定泛型
泛型数组
// 方式1
function fn1T(arr: T[]): T[] {console.log(arr.length)return arr
}// 方式2
function fn2T(arr: ArrayT): ArrayT {console.log(arr.length)return arr
}
多个泛型
function logDataT, K(data1: T, data2: K): T | K {console.log(data1, data2)return Date.now() % 2 ? data1 : data2
}logData(100, hello) // 不指定泛型TS会自动对类型进行推断
logDatanumber, string(100, hello) // 指定泛型
logDatastring, boolean(hello, false) // 指定泛型
泛型接口
// 示例1
interface PersonInterfaceT {name: string,age: number,extraInfo: T
}let p1: PersonInterfacestring
let p2: PersonInterfacenumberp1 {name: 张三,age: 18,extraInfo: 厉害人物
}
p2 {name: 李四,age: 18,extraInfo: 250
}// 示例2
interface InfoT {like: T
}
let zhangsan: Infostring {like: 篮球
}
let lisi: Infostring[] {like: [篮球]
}// 示例3
interface fnT {(a1: T, a2: T): T[]
}
let f1: fnstring function(a, b) {return [a, b]
}
let f2: fnnumber function(a, b) {return [a, b]
}
console.log(f1(1, 2)) // [1, 2]
console.log(f2(1, 2)) // [1, 2]
泛型约束指定泛型类型
// 示例1
interface PersonInterface {name: string,age: number
}function logPersonT extends PersonInterface(info: T): void {console.log(我叫${info.name}今年${info.age}岁了)
}logPerson({ name: 张三, age: 18 })// 示例2
function fnT extends string|any[](a: T):number {return a.length
}
fn(hello) // 正常
fn([1, 2, 3]) // 正常
fn(123) // Argument of type number is not assignable to parameter of type string | any[]
泛型类
class PersonT {constructor(public name: string,public age: number,public extraInfo: T) { }speak() {console.log(我叫${this.name}今年${this.age}岁了)console.log(this.extraInfo)}
}const p1 new Personstring(小明, 10, 很聪明)type JobInfo {title: stringcompany: string
}const p2 new PersonJobInfo(张三, 30, { title: 总经理, company: 华夏科技公司 })
keyof
interface Info {name: string,age: number
}
let zhangsan: Info {name: zhangsan,age: 10
}
getInfoValue(zhangsan, name)// 示例1
function getInfoValue(info: Info, key: string): void {// 这里会报错因为传入的 key 可能不是 Info 的属性console.log(info[key])
}// 示例2
function getInfoValue(info: Info, key: keyof Info): void {console.log(info[key])
}// 示例3
function getInfoValueT extends keyof Info(info: Info, key: T): Info[T] {return info[key]
}
九、类型声明文件
类型声明文件是 TypeScript 中的一种特殊文件通常以.d.ts作为扩展名。它的主要作用是为现有的 JavaScript 代码提供类型信息使得 TypeScript 能够在使用这些 JavaScript 库或模块时进行类型检查和提示。
demo.js
export function add(a, b) {return a b
}export function mul(a, b) {return a * b
}
demo.d.ts
declare function add(a: number, b: number): number
declare function mul(a: number, b: number): numberexport { add, mul }
example.ts
import { add, mul } from ./demo.jsconst x add(2, 3) // x 类型为 number
const y mul(4, 5) // y 类型为 numberconsole.log(x, y)
十、函数
为函数定义类型
为函数参数和返回值指定类型。
function add(x: number, y: number): number {return x y
}let myAdd function(x: number, y: number): number {return x y;
}
推断类型
如果函数省略返回值类型TS 会根据语句自动推断出返回值类型。
function add(x: number, y: number) {return x y
}const result add(1, 2)console.log(result.length) // Property length does not exist on type number
书写完整函数类型
let myAdd: (x: number, y: number) number function(x: number, y: number): number { return x y
}
可选参数和默认参数
默认情况下函数调用时传入的参数类型和个数必须和函数定义时一致。TS 可通过?实现可选参数功能未传递参数时变量的值为undefined。
function fullName(firstName: string, lastName?: string) {if (lastName) {return firstName lastName} else {return firstName}
}let result1 fullName(Bob) // 正常
let result2 fullName(Bob, Adams) // 正常
let result3 fullName(Bob, Adams, Sr.) // Expected 1-2 arguments, but got 3
在 TS 中我们也可以为参数指定默认值在没有传递参数或参数的值为undefined时生效。
function fullName(firstName: string, lastName Smith) {return firstName lastName
}let result1 fullName(Bob) // Bob Smith
let result2 fullName(Bob, undefined) // Bob Smith
let result4 fullName(Bob, Adams) // Bob Adams
函数的默认参数不必放在参数的末尾如果要让默认值生效需明确传入undefined。
function fullName(firstName Bob, lastName: string) {return firstName lastName
}let result1 fullName(Smith) // Expected 2 arguments, but got 1
let result2 fullName(undefined, Smith) // 正常
let result4 fullName(John, Smith) // 正常
剩余参数
如果你不知道传递了多少个参数可以使用剩余参数。
示例1
function fullName(firstName: string, ...restOfName: string[]) {return firstName restOfName.join( )
}const result fullName(Joseph, Samuel, Lucas, MacKinzie)console.log(result) // Joseph Samuel Lucas MacKinzie
示例2
function multiply(n: number, ...m: number[]) {return m.map((x) n * x)
}const result multiply(10, 1, 2, 3, 4)console.log(result) // [10, 20, 30, 40]
参数解构
TS 中一样可以使用解构赋值但需要为解构的参数指定类型否则提示含有隐式 any。
function sum({ a, b, c }: { a: number, b: number, c: number }) {console.log(a b c)
}sum({a: 10,b: 3,c: 9
})
使用接口和类型别名改写
type ABC {a: number,b: number,c: number
}function sum({ a, b, c }: ABC) {console.log(a b c)
}sum({a: 10,b: 3,c: 9
})
interface ABC {a: number,b: number,c: number
}function sum({ a, b, c }: ABC) {console.log(a b c)
}sum({a: 10,b: 3,c: 9
})
参数为接口或类型别名
函数的参数除了可直接定义类型也可以是接口和类型别名。
接口
interface Person {firstName: stringlastName: string
}function greet(person: Person) {console.log(Hello, person.firstName person.lastName)
}greet({firstName: Bob,lastName: Smith
})
类型别名
type Person {firstName: stringlastName: string
}function greet(person: Person) {console.log(Hello, person.firstName person.lastName)
}greet({firstName: Bob,lastName: Smith
})
返回 void 类型
函数声明和函数表达式在返回 void类型时会有所不同
// 函数声明
function fn1(): void {return 100 // Type number is not assignable to type void
}// 函数表达式
type voidFn () void// 箭头函数
const fn2: voidFn () 100// 普通函数
const fn3: voidFn function() {return 100
}console.log(fn2()) // 100
console.log(fn3()) // 100
注意函数返回void类型时不能根据返回值判断进行其他操作
function fn1(): void {return
}type voidFn () voidconst fn2: voidFn () 100const fn3: voidFn function() {return 100
}// 以下操作都会报错An expression of type void cannot be tested for truthiness.
if (fn1()) {console.log(fn1)
}
if (fn2()) {console.log(fn2)
}
if (fn3()) {console.log(fn3)
}
重载
在 TS 中函数重载是一种允许为同一个函数名定义多个不同函数签名的特性。实现重载需要两个步骤
声明多个函数签名实现函数体
示例1
// 函数签名1
function makeDate(timestamp: number): Date
// 函数签名2
function makeDate(m: number, d: number, y: number): Date
// 实现函数体
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {if (d ! undefined y ! undefined) {return new Date(y, mOrTimestamp, d)} else {return new Date(mOrTimestamp)}
}const d1 makeDate(1727620102980) // 正常
const d2 makeDate(9, 29, 2024) // 正常
const d3 makeDate(1727620102980, 9) // 只存在1个或3个参数的情况不存在2个参数的情况
示例2
function fn(x: boolean): void
function fn(x: string): void
function fn(x: boolean | string) {console.log(x)
}fn(false)
fn(hello)
fn(100) // 参数不合法
示例3
function fn(x: string): string
function fn(x: boolean): boolean
function fn(x: string | boolean): string | boolean {return x
}fn(false)
fn(hello)
fn(100) // 参数不合法
示例4
function len(s: string): number
function len(arr: any[]): number
function len(x: any) {return x.length
}len(hello) // 正常
len([1, 2, 3]) // 正常
len(Math.random() 0.5 ? hello : [1, 2, 3]) // 报错因为这里返回的是联合类型而len返回的是数字类型
示例4的优化
function len(x: any[] | string) {return x.length
}len(hello) // 正常
len([1, 2, 3]) // 正常
len(Math.random() 0.5 ? hello : [1, 2, 3]) // 正常
提示在可能的情况下总是倾向于使用联合类型的参数而不是重载参数。
示例6重载中的this
interface User {admin: boolean
}interface DB {filterUsers(filter: (this: User) boolean): User[]
}const db: DB {filterUsers: (filter: (this: User) boolean) {let user1: User {admin: true}let user2: User {admin: false}return [user1, user2]}
}const admins db.filterUsers(function (this: User) {return this.admin
})console.log(admins) // [ { admin: true }, { admin: false } ]// 箭头函数报错 An arrow function cannot have a this parameter
const admins db.filterUsers((this: User) {return this.admin
}) 好了分享结束谢谢点赞下期再见。