TS系列(7):知识点汇总
你好,我是沐爸,欢迎点赞、收藏、评论和关注。
一、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 = <string>a
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 = 'hello'if (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: Array<number> // 泛型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 logData<T>(data: T): T {console.log(data)return data
}logData(10) // 不指定泛型,TS可以自动对类型进行推断
logData<number>(100) // 指定泛型
logData<string>('hello') // 指定泛型
泛型数组
// 方式1
function fn1<T>(arr: T[]): T[] {console.log(arr.length)return arr
}// 方式2
function fn2<T>(arr: Array<T>): Array<T> {console.log(arr.length)return arr
}
多个泛型
function logData<T, K>(data1: T, data2: K): T | K {console.log(data1, data2)return Date.now() % 2 ? data1 : data2
}logData(100, 'hello') // 不指定泛型,TS会自动对类型进行推断
logData<number, string>(100, 'hello') // 指定泛型
logData<string, boolean>('hello', false) // 指定泛型
泛型接口
// 示例1
interface PersonInterface<T> {name: string,age: number,extraInfo: T
}let p1: PersonInterface<string>
let p2: PersonInterface<number>p1 = {name: '张三',age: 18,extraInfo: '厉害人物'
}
p2 = {name: '李四',age: 18,extraInfo: 250
}// 示例2
interface Info<T> {like: T
}
let zhangsan: Info<string> = {like: '篮球'
}
let lisi: Info<string[]> = {like: ['篮球']
}// 示例3
interface fn<T> {(a1: T, a2: T): T[]
}
let f1: fn<string> = function(a, b) {return [a, b]
}
let f2: fn<number> = 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 logPerson<T extends PersonInterface>(info: T): void {console.log(`我叫${info.name},今年${info.age}岁了`)
}logPerson({ name: '张三', age: 18 })// 示例2
function fn<T 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 Person<T> {constructor(public name: string,public age: number,public extraInfo: T) { }speak() {console.log(`我叫${this.name},今年${this.age}岁了`)console.log(this.extraInfo)}
}const p1 = new Person<string>('小明', 10, '很聪明')type JobInfo = {title: stringcompany: string
}const p2 = new Person<JobInfo>('张三', 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 getInfoValue<T 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.js'const 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
})
好了,分享结束,谢谢点赞,下期再见。
相关文章:
TS系列(7):知识点汇总
你好,我是沐爸,欢迎点赞、收藏、评论和关注。 一、TS是什么? TypeScript 由微软开发,是基于 JavaScript 的一个扩展语言。TypeScript 包含 JavaScript 的所有内容,是 JavaScript 的超集。TypeScript 增加了静态类型检…...
Unity 查看Inspectors组件时严重掉帧
遇到一个问题,就是运行一个脚本的时候,只要我查看它的Inspectors,就会严重掉帧。 原本是30fps,只要查看这个组件,就掉到5fps。 这还真有点像波粒二象性,一观察就会掉帧,不观察就正常。 using…...
golang学习笔记23-面向对象(五):多态与断言【重要】
本节也是GO核心部分,很重要。 注:由于导包语句已经在19讲(笔记19:面向对象的引入)展示过了,所以这里就不展示了。 一、多态(Polymorphism) 变量(实例)具有多…...
RabbitMQ基础知识
1.1 什么是MQ? 消息队列(Message Queue),是基础数据结构中 “先进先出” 的一种数据结构。 一般用来解决应用解耦、异步消息、流量削峰等问题,实现高性能、高可用、可伸缩和最终一致性架构。 RabbitMQ可以理解为一个邮箱&#x…...
基于Python大数据的音乐推荐及数据分析可视化系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏:Java精选实战项目…...
安达发|太阳能设备行业APS计划排程软件能解决哪些问题
在当今快速发展的太阳能设备行业中,高级计划与排程(APS)软件成为了企业优化生产流程、提高生产效率和满足市场需求的关键工具。APS软件通过集成先进的算法和数据分析技术,为企业提供了一个全面的生产计划和排程解决方案。本文将探…...
CaChe的基本原理
目录 一、Cache的定义与结构 二、Cache的工作原理 三、Cache的映射与替换策略 四、Cache的写操作处理 Cache,即高速缓冲存储器,是计算机系统中位于CPU与主存之间的一种高速存储设备。它的主要作用是提高CPU对存储器的访问速度,从而优化系…...
数据结构-栈(理解版)
一、栈的定义 相信大家对于栈或多或少有一些了解,可能大多数人会告诉你栈是一种先进后出的数据结构。这其实说了跟没说一样(❁◡❁)!当然(last in,first out)是栈最有特色的性质。 这里可以给大家一些比较好理解的例…...
NAND Flash虚拟层初始化
在整个NAND Flash初始化过程中,其主要过程由NAND_Init()函数来完成的,因此以下以NAND_Init()函数作为入口,对NAND Flash虚拟层初始化进行全面分析: NAND_Init()NAND_PhyInit()FMT_Init()FMT_FormatNand()LML_Init() NAND_Init()函数首先调用NAND_PhyInit()函数…...
zabbix7.0监控linux主机案例详解
前言 服务端配置 链接: rocky9.2部署zabbix服务端的详细过程 环境 主机ip应用zabbix-server192.168.10.11zabbix本体zabbix-client192.168.10.12zabbix-agent zabbix-server(服务端已配置) 具体实现过程 zabbix-client配置 安装zabbix-agent 添加扩展包 dnf -y instal…...
2024重生之回溯数据结构与算法系列学习(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
欢迎各位彦祖与热巴畅游本人专栏与博客 你的三连是我最大的动力 以下图片仅代表专栏特色 专栏跑道一 ➡️ MYSQL REDIS Advance operation 专栏跑道二➡️ 24 Network Security -LJS 专栏跑道三 ➡️HCIP;H3C-SE;CCIP——LJS[华为、华三、思科高级网络]…...
django drf 过滤器
排序 代码: from rest_framework.generics import ListAPIView from rest_framework.filters import OrderingFilterclass TestListAPIView(ListAPIView):queryset models.Course.objects.filter(is_deleteFalse).all()serializer_class serializers.TestModelS…...
蓝桥杯—STM32G431RBT6(RTC时钟获取时间和日期)
一、RTC是什么,有什么用? 在 STM32 中,RTC(Real-Time Clock,实时时钟)主要有以下作用: 时间保持:即使在系统断电情况下,也能持续记录时间。(需要纽扣电池供电…...
DriveVLM 论文学习
论文链接:https://arxiv.org/abs/2402.12289 解决了什么问题? 自动驾驶对交通行业有着革命性的作用,实现 FSD 的一个主要障碍就是场景理解。场景理解涉及在复杂且不可预测的环境中进行导航,这些环境可能包括恶劣的天气条件、复杂…...
Unity3D 客户端多开
Unity3D 实现客户端多开 客户端多开 最近在做好友聊天系统,为了方便测试,需要再开一个客户端。 简单的方法,就是直接拷贝一个新的项目,但是需要很多时间和占用空间。 查阅了网络资料,发现有一种软链接,…...
使用代理IP数据采集都需要注意那些?
“在当今大数据时代,数据采集成为了企业决策和个人研究的重要依据。然而频繁访问目标网站往往会引发IP被封锁的风险,这时使用代理IP就显得尤为重要。但代理IP的使用并非毫无风险,以下是使用代理IP进行数据采集时需要注意的几个关键事项。” 一…...
城市大脑:智慧城市的神经中枢——典型实践与经验启示
随着信息技术的飞速发展,智慧城市已成为全球城市转型升级的重要方向。“城市大脑”作为智慧城市的核心引擎,正以其强大的数据处理能力、智能决策支持和跨领域协同优势,引领着城市管理与服务的深刻变革。本文将深入探讨几个具有代表性的“城市…...
嵌入式中CW32多功能测试笔实现
前言 起心动念 在日常的硬件调试工作中,我们最常使用的仪器仪表可能就是万用表了,虽然万用表号称“万用”,但大部分时候,我们需要使用到的功能无非是电压测量和通断测量。 作为调试的“得力干将”,万用表有时候也会存在存在一些缺点和局限性,比如:体积较大不便于携带…...
Python 时间占位符:毫秒的使用
Python 时间占位符:毫秒的使用 在 Python 中,处理时间和日期是一个非常常见的任务。在进行时间格式化时,使用占位符来表示特定的时间单位是非常重要的。特别是毫秒(ms),它在许多应用中扮演着关键角色&…...
深度学习:(七)梯度下降法在神经网络中的应用
梯度下降法在神经网络中的应用 事先规定: 用 n n n 表示个数(维度): n [ 0 ] n x n^{[0]}n_x n[0]nx ,表示单个训练样本 x x x 的元素个数; n [ 1 ] n^{[1]} n[1] 表示隐藏层 1 1 1 的单元(节点&am…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
webpack面试题
面试题:webpack介绍和简单使用 一、webpack(模块化打包工具)1. webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖文件,使用loaders来处理它们&#x…...
