TS型变与对象类型进阶

 子类型:给定两个类型A和B,假设B是A的子类型,那么在需要A的地方都可以放心使用B。计作 A <: B (A是B的子类型)。

超类型正好与子类型相反。A >: B (A是B的超类型)。

1 TS 类型

可赋值性指在判断需要B类型的地方是否可以使用A类型。当A <: B 时,是满足可赋值性的,不过TS有两处例外的地方:

1)当A是any时,可赋值给B类型。

function testFun(b:boolean) {console.log(b); // 2
}
let a:any = 2;
testFun(a);

TS 这样做是为了方便与JS代码互操作。

2)B 是枚举类型,且至少有一个成员是number类型,而且A是数字。

这是安全性的一大隐患,尽量不要这么操作。

1.1 型变

如果想要保证A可赋值给B对象,那么A对象的每个属性都必须<: B 对象对应的属性。此时,我们说TS对结构(对象和类)的属性类型进行了协变。

type User = {name: string,money?: number
}
type VipUser = {name: string,money: number
}let user: User = {name: "牛",
}
let vipUser:VipUser = {name: "张",money: 1000
}user = vipUser;
vipUser = user; //ERROR Type User is not assignable to type VipUser

不变

只能是T

逆变

可以是 >: T

协变

可以是 <: T

双变

可以是 <: T或 >: T

表 型变的四种方式

在TS中,每个复杂类型的成员都会进行协变,包括函数的返回类型。不过有个例外:函数的参数类型进行逆变。

1.1.1 函数的型变

如果函数A的参数数量小等于函数B的参数数量,且同时满足下述条件,那么函数A是函数B的子类型:

1)函数A的this类型未指定,或者>:函数B的this类型。

2)函数A的各个参数的类型 >: 函数B对应参数。

3)函数A的返回类型<: 函数B的返回类型。

class Person {constructor(public sex: number) {}
}
class Man extends Person{constructor(public name: string,sex: number) {super(sex);}
}type FunA = (this: Person) => void; // 子类型
type FunB = (this: Man) => void; // 父类型let person:Person = new Person(1);
let man:Man = new Man("李",1);let funA:FunA = function () {console.log(this);}
let funB:FunB = function () {console.log(this);}funA.bind(person)();
//funB.bind(person); // ERRORfunA.bind(man)();
funB.bind(man)();funB = funA;
// funA = funB; // ERROR

函数的协变似乎有些特殊,在我们常规认知中,子类型的作用范围窄于父类型。但是在上面例子中,作为子类型的A函数类型,比父类B可绑定的对象类型更广。子类的唯一定义是:在需要父类的地方可以放心使用子类。上面代码在使用B的地方,可以放心使用A,而使用A的地方不能放心使用B。

type FunA = (this:any) => void; // 子类
type FunB = (this:Man) => void; // 父类let funA:FunA = function () {console.log(this);};
let funB:FunB = function () {console.log(this);};funA.bind(man)();
funB.bind(man)();// funB.bind(person)(); // ERROR
funA.bind(person)();let fun1: FunA = funB;
let fun2: FunB = funA;

上面代码展示了一个特殊情况:当this为any类型(或未指定时)。这里符合第一个条件。

type FunA = (person: Person) => void; // 子类
type FunB = (man: Man) => void; // 父类let funA:FunA = function(person:Person) {console.log("funA:" + person.sex);}
let funB:FunB = function (man:Man) {console.log("funB:" + man.name);}function testFunA(fun: (person: Person) => void) {}
function testFunB(fun: (man: Man) => void) {let man = new Man("张",1);fun(man);
}testFunA(funA);
// testFun(funB); // ERROR
testFunB(funA);
testFunB(funB);

1.2 类型扩展

一般来说,TS在推导类型时会放宽要求,故意推导出一个更宽泛的类型。声明变量时如果允许以后修改变量的值,变量的类型将拓展,从字面量扩大到包含该字面量的基类型:

let a = “x”; // string

let b = 1; // number

let c = {x: 1}; // {x: number}

声明为不可变的变量时:

const a = “x”; // “x”

可以通过显示注解类型,防止类型被扩展:

let a: “x” = “x”; // “x”

如果使用let或var 重新为不可变的变量赋值,将自动扩展:

const a = “x”; // “x”

let b = a; // string

const 会禁止类型拓宽,还递归把成员设为readonly:

let x = {a: “t”,b: {x: 12}} as const; // {readonly a: “t”,readonly b: {readonly x:12}}

1.2.1 多余属性检查

TS尝试把新鲜对象字面量类型T赋值给另一个类型U,如果T有U不存在的属性,则报错。

新鲜对象:TS直接从字面量推导出的类型。如果把对象字面量赋值给变量或者对象字面量有断言,则其不是新鲜对象,而是常规对象。

type ObjType = {name: string
}let obj1:ObjType = {name: ""
}
let obj2:ObjType = {name: "",type: 1 // 报错
} // 为新鲜对象
let obj3:ObjType = {name: "",type: 1
} as ObjType // true 有类型断言function fun(obj: ObjType) {}
let obj4 = {name: "",type: 1
}
fun(obj4); // true 已被赋值给变量,为常规对象
fun({name: "",type: 1 // 报错
}) // 为新鲜变量

2 对象类型进阶

2.1 类型运算符

我们可以像获取对象属性值一样获取类型的某个属性类型 (必须用方括号,不能用点号):

type ObjType = {name: string,user: {type: number,name: string}
};
type UserType = ObjType["user"]; // {type: number,name:string}

keyof 运算符获取对象所有键的类型,合并为一个字符串字面量类型,以上面的ObjType为例:

type ObjTypeKeysType = keyof ObjType; // "name" | "user"
type UserTypeKeysType = keyof UserType; // "type" | "name"

2.2 映射类型

TS中,我们可以为类型定义索引签名[key:T] : U,该类型的对象可能有更多的键,键的类型为T,值的类型为U。其中T必须为number或string。

而映射类型则在索引签名的基础上,为key做了限制。这让类型更安全:

type IndexType = {[K: string]: string
} // 只限定了key 为 string类型type MappedType = {[K in "key1" | "key2"]: string
} // 限定了属性必须有且只有“key1”和“key2”let indexType: IndexType = {"a": "","b": ""
}; //
let mappedType: MappedType = {"key1": "","key2": ""
}
let mappedType2:MappedType = {"key1": ""
} // 报错 Property key2 is missing in type { key1: string; } but required in type MappedType

2.2.1 Record类型

TS内置的Record类型用于描述有映射关系的对象。是使用映射类型实现的。

type RecordType = Record<"a"|"b",1 | 2 | 3>let r1: RecordType = {"a": 1,"b": 1,
}
let r2: RecordType = {"a": 1,"b": 5
} // 报错 Type 5 is not assignable to type 1 | 2 | 3
let r3: RecordType = {"a": 1
} // 报错 Property b is missing in type { a: 1; } but required in type RecordType

2.3 伴生对象模式

TS的类型和值分别在不同的命名空间。在同一作用域中,我们可以有同名的类型和值。在语义上归属同一个名称的类型和值放在一起,其次,使用方可以一次性导入二者。

type User = {name: string
}
let User = {createUser(name:string):User {console.log("这是值创建的");return {name: name}}
}
let user:User = User.createUser("hmf");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/210092.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用cmake构建Qt6.6的qt quick项目,添加应用程序图标的方法

最近&#xff0c;在学习qt的过程中&#xff0c;遇到了一个难题&#xff0c;不知道如何给应用程序添加图标&#xff0c;按照网上的方法也没有成功&#xff0c;后来终于自己摸索出了一个方法。 1、准备一张图片作为图标&#xff0c;保存到工程目录下面&#xff0c;如logo.ico。 …

Qt 编译fcitx-qt5 插件支持中文输入法

前言 在Linux系统上会遇到Qt开发的程序无法输入中文的情况&#xff0c;原因就是因为输入法框架是采用的fcitx&#xff0c;而不是ibus&#xff0c;Qt默认只支持ibus输入法框架。在Qt/5.15.2/gcc_64/plugins/platforminputcontexts/路径下可以看到&#xff0c;只有libibusplatfo…

引入JavaScript文件的5种方式

在HTML文件中&#xff0c;可以使用以下5种方式引入JavaScript文件&#xff1a; 1.内联方式&#xff08;Inline&#xff09;&#xff1a; 在HTML的<script>标签中直接编写JavaScript代码。 示例&#xff1a; <script>// JavaScript代码 </script>2.外部文件…

Python Selenium3 简单操作进行百度搜索

当前环境&#xff1a;Win10 Python3.7 selenium3.141.0&#xff0c;urllib31.26.2 from selenium import webdriver import timeif __name__ __main__:# Chrome 路径CHROME_PATH rC:\Program Files (x86)\65.0.3312.0\chrome-win32\chrome.exe# ChromeDriver 路径CHROMEDR…

mybatis的快速入门以及spring boot整合mybatis(二)

需要用到的SQL脚本&#xff1a; CREATE TABLE dept (id int unsigned PRIMARY KEY AUTO_INCREMENT COMMENT ID, 主键,name varchar(10) NOT NULL UNIQUE COMMENT 部门名称,create_time datetime DEFAULT NULL COMMENT 创建时间,update_time datetime DEFAULT NULL COMMENT 修改…

极智芯 | 解读国产AI算力 灵汐产品矩阵

欢迎关注我的公众号 [极智视界],获取我的更多经验分享 大家好,我是极智视界,本文分享一下 解读国产AI算力 灵汐产品矩阵。 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码和资源下载,链接:https://t.zsxq.com/0aiNxERDq [系列声明:最近写了十余篇 &…

低多边形建筑3D模型纹理贴图

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 当谈到游戏角色的3D模型风格时&#xff0c;有几种不同的风格&#xf…

基于SSM的鞍山职业技术学院图书借阅管理系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于SSM的鞍山职业技术学院图书借阅管理…

树莓派CSI摄像头在新系统(23年12月)中的不用设置了,没有开关,也没有raspistill

网上都是老信息&#xff0c;用的raspistill命令&#xff0c;至少新系统没有这个东西了&#xff0c;也不会在sudo raspi-config里面也没有摄像头的开关了。 ls /dev/video* 能看到摄像头video0&#xff0c;但是vcgencmd get_camera supported0&#xff0c; detected0&#xff0…

【python】闭包和装饰器

前置知识&#xff1a; 函数的本质就是变量名可以把函数作为参数传递&#xff0c;例如&#xff1a; def func():print("我是func")# 接收的fn是个函数 def handle(fn): # 调用函数fn()handle(func)可以把函数作为返回值返回&#xff0c;例如 def func():def func2(…

CPU的三大调度

计算机系统中的调度可以分为不同层次&#xff0c;包括作业调度、内存调度和进程调度。这三种调度分别负责管理和优化计算机系统中不同层次的资源分配和执行顺序。 高级调度&#xff1a;作业调度&#xff08;Job Scheduling&#xff09;&#xff1a; 作业调度是指对提交到计算…

了解c++11中的新增

一&#xff0c;统一的初始化列表 在引入c11后&#xff0c;我们得出计划都可以用初始化列表进行初始化。 C11 扩大了用大括号括起的列表 ( 初始化列表 ) 的使用范围&#xff0c;使其可用于所有的内置类型和用户自 定义的类型&#xff0c; 使用初始化列表时&#xff0c;可添加等…

Vue学习计划-Vue2--VueCLi(二)vuecli脚手架创建的项目内部主要文件分析

1. 文件分析 1. 补充&#xff1a; 什么叫单文件组件&#xff1f; 一个文件中只有一个组件 vue-cli创建的项目中&#xff0c;.vue的文件都是单文件组件&#xff0c;例如App.vue 2. 进入分析 1. package.json: 项目依赖配置文件&#xff1a; 如图&#xff0c;我们说主要的属性…

性能测试经典面试题(带答案)!

概述一下性能测试流程&#xff1f; 1.分析性能需求。挑选用户使用最频繁的场景来测试。确定性能指标&#xff0c;比如&#xff1a;事务通过率 为100%&#xff0c;TOP99%是5秒&#xff0c;最大并发用户为1000人&#xff0c;CPU和内存的使用率在70%以下2.制定性能测试计划&…

Ubuntu20.04使用cephadm部署ceph集群

文章目录 Requirements环境安装Cephadm部署Ceph单机集群引导&#xff08;bootstrap&#xff09;建立新集群 管理OSD列出可用的OSD设备部署OSD删除OSD 管理主机列出主机信息添加主机到集群从集群中删除主机 部署Ceph集群 Cephadm通过在单个主机上创建一个Ceph单机集群&#xff0…

游戏开发库

整理了38个Python游戏开发库 https://zhuanlan.zhihu.com/p/505095419 https://zhuanlan.zhihu.com/p/262012936 2023 年最佳游戏引擎推荐 https://zhuanlan.zhihu.com/p/624771157 十大开源游戏引擎深入比较之美 https://blog.51cto.com/u_15273495/2915535 panda3d https:…

【EI会议征稿中】第三届网络安全、人工智能与数字经济国际学术会议(CSAIDE 2024)

第三届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2024&#xff09; 2024 3rd International Conference on Cyber Security, Artificial Intelligence and Digital Economy 第二届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2023&…

Verilog基础:寄存器输出的两种风格

相关文章 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 Verilog中的寄存器操作一般指的是那些对时钟沿敏感而且使用非阻塞赋值的操作。例如状态机中的状态转移&#xff0c;实际上就是一种寄存器操作&#xff0c;因为这相…

听GPT 讲Rust源代码--src/tools(10)

File: rust/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs 在Rust源代码中&#xff0c;rust-analyzer是一个Rust语言的IDE插件和代码分析器。其中&#xff0c;generate_is_empty_from_len.rs是rust-analyzer中的一个处理程序&#x…

终于有人把tcp、http、rpc和grpc总结完整了

随着微服务的迅速发展&#xff0c;各大互联网企业也投入到微服务的​使用种。微服务最大的特点是&#xff0c;跨进程、跨服务、跨语言之间的调用&#xff0c;使得我们能够像调用本地类、函数一样。当微服务具备该特点&#xff0c;将我们复杂的业务拆分成不同的服务&#xff0c;…