JavaScript 内核第 4 / 5 章
泛型与类型收窄
用约束、断言、重载和索引签名表达更精确的类型关系。
泛型
🥖 提示
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用一个变量代替,而在使用的时候再指定类型的一种特性 泛型的思想就是把类型抽象成一个变量,在定义函数或者类的时候,传入类型的形参,使用的时候传入或根据参数推导具体的类型

🌟 提示
泛型的类型限制是通过接口的继承实现的
// 首先定义一个具有长度的类型 接口
interface ILength {
length: number;
}
// 函数传入的类型形参是继承自ILength的类型,则必需实现了length属性,否则会报错
// 泛型约束
function getLength<T extends ILength(arg: T): number {
return arg.length;
// 而且传入的参数类型必须是ILength的子类 ,所以肯定具有length属性
}
// 所以可见,使用泛型具有通用的约束参数类型的作用
// 传入的参数类型也可以是多个或者有默认值的
function getLength2<T extends ILength, K = string>(arg1: T, arg2: K): number {
return arg1.length + arg2.length;
}
// 调用函数可以不直接写具体的传入类型,函数能够自动推导出类型
getLength2("abc", "def");
泛型约束
- 简单约束
interface ILength {
length: number;
}
// 函数传入的类型形参是继承自ILength的类型,则必需实现了length属性,否则会报错
function getLength<T extends ILength(arg: T): number {
return arg.length;
// 而且传入的参数类型必须是ILength的子类 ,所以肯定具有length属性
}
- keyof 用来约束类型必须是对象中的一个key
interface IPerson {
name:string
age: number
height:string
}
type PersonKeys = keyof IPerson // 'name' | 'age' | 'height'
//约束K必须是IPerson当中的一个key
function getLength<T extends IPerson,K extends keyof IPerson >(arg: T,arg2:K): number {
return arg.age;
}
- 映射类型 - 使用对象索引签名实现对一个接口的复制,并且对其修改
interface IPerson {
readonly name: string;
age: number;
height: string;
}
//可以使用+ - 来增加或者去除原来类型中的修饰符
// 使用的索引签名 和 类型提取实现类型复制
//需用type
type MapIPerson<T> = {
- readonly [prop in keyof T]-?: T[prop]
}
type NewPerson = MapIPerson<IPerson>
//即可复制IPerson的属性,需用type
ts知识扩展
类型断言
- as 定义为某一个类型,如果需要强制转换,需要先转换成unknown 再转换成所需类型
- ! 非空断言 值不为空
//1. as关键词
// 用来告诉ts变量的实际类型
//类型断言只能把类型断言成更加(从不太具体的类型断言成)具体,或者(从比较的具体的类型断言成)更加不太具体的类型
class Person{}
class Student extends Person{
studying() {}
}
function sayHello(p:Person) {
(p as Student).studying()
// 把p断言为子类,就可以调用子类的方法
}
let stu=new Student();
sayHello(stu)
//2. 非空断言 !
function sayHello(name?: string) {
//非空断言,保证非空,调用方法
console.log( name!.length);
}
// 3.扩展
// 1. 可选链 ?. 用来判断是否有值
// 2. !! 转换成布尔值
// 3. ?? 用来判断是否有值,有值就返回,没有值就返回后面的值,比三目运算符更加方便
类型别名
type myType = number | string | boolean;
// 使用type来定义类型别名
type myObjType = {
name: string;
age: number;
height?: number;
};
// 对象别名也可以是对象
类型缩小
❗ 提示
缩小类型的范围,从而可以使用特定类型的方法
- typeof / instanceof / in
- === !== 平等缩小
//亦即在(联合类型)变量使用的时候,多一层判断,确认最终类型
type IDtype = string |number
function getData(id:IDtype){
if(typeof id === 'string'){
//此时id就是string类型
console.log(id.length)
}
}
// in的判断方式
type Dog = {
bark(){}
}
type Cat = {
Miao(){ }
}
function sound(p:Cat|Dog){
// 通过in来判断
if('bark' in p){
p.bark()
}else{
p.Miao()
}
}
const pet:Dog= {
bark(){
console.log('汪汪汪')
}
}
sound(pet)
函数的重载
// 定义相同名称,参数不同的几个函数签名,只有一个实现函数,ts会根据参数的类型 来判断调用哪个函数
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
return a + b;
}
console.log(add(10, 20));
console.log(add("10", "20"));
//实际
//能通过联合类型实现的优先使用联合类型,不能实现的才使用函数重载,比较麻烦
鸭子类型
❗ 提示
一个对象看起来像鸭子,叫声像鸭子,行为像鸭子,那么可以认为它是一个鸭子
ts中的类型检测使用的鸭子类型
❤️ 提示
鸭子类型是一种动态类型的概念,它是指在运行时,一个对象的有效语义是由它当前方法和属性的集合决定的,而不是由它所继承的特定类或接口的集合决定的。这意味着,只要一个对象具有与所需方法和属性相同的方法和属性,它就可以被视为是所需的类型。这种类型检查方式通常用于动态语言中,如JavaScript和Python
对象和接口中的属性都可以使用的修饰符
?.可选Readonly只读
对象接口索引签名
相当于一个动态属性
type Person = {
[index: number]: string //允许通过索引获取值
lenght: number
}
interface Person {
[index: number]: string; //允许通过索引获取值
lenght: number;
[key:string]:string|number //[]中的类型只能是string 或者number ,不能是其他,联合也不行
};
//1. 可以使用2个索引签名的时候, [key:string]返回的类型必要是[index: number]返回的类型的父类型
//因为number类型最终会转换成string
//2. 其他属性( lenght)也必须符合[key:string]返回的类型
//其实就是一条:其他属性( 包含[index: number])也必须符合[key:string]返回的类型的子类型
字面量赋值
严格对象字面量赋值
在使用字面量对接口或者类型赋值时,直接赋值会严格校验字面量的属性必须和接口保持一致,如下例子
interface IPerson {
name: string;
age: number;
height: number;
}
const info = {
name: "why",
age: 18,
height: 1.88,
address: "广州市"
}
const p2 : IPerson ={
name: "why",
age: 18,
height: 1.88,
address: "广州市"
}
//这样会报错
const p: IPerson = info
// freshness擦除 类型检测时
//这样不会报错
// console.log(info)
// console.log(p)
function printInfo(person: IPerson) {
console.log(person);
}
// 代码会报错
printInfo({
name: "why",
age: 18,
height: 1.88,
address: "广州市"
})
const info = {
name: "why",
age: 18,
height: 1.88,
address: "广州市",
};
// 使用对象的引用,会在类型检测的时候有擦除,所以不会报错
printInfo(info);
ts的知识扩展
- 模块化 支持各种模块化 最推荐的模块化方式是esm
❗ 提示
- js规范声明任何没有export的js文件都会认为是一个脚本,而非一个模块
- 在一个脚本文件中,变量和类型会被声明在共享的全局作用域,将多个输入文件合并成一个输出文件
- 如果希望它被作为一个模块处理,需要添加
export {}
这会把文件当成单独的模块处理,有独立的作用域,不会污染作用域
- 类型的导入导出
//方法一
import type { IPerson } from './type'
//方法二
import { type IPerson } from './type'
//type.ts
export interface IPerson {
name:string
}
✍️ 提示
使用type关键词的好处,使用babel、esbuild等编译时,方便识别安全移除,提升效率 不使用也可以
- 类型查找 类型声明
🎉 提示
.d.ts文件,主要用于类型声明,不写代码逻辑
- 内部类型声明 安装typescript后
- 外部类型声明 自定义代码类型声明
- 第三方库类型声明
//声明变量
declare const age:number
//声明模块
declare module 'lodash '{
//模块内部需要导出
export function join(...args:any[]):any
}
//声明静态资源
declare module '*.jpg'