TypeScript 背后的结构化类型系统原理详解

目录
  • 前言
  • 什么是结构化类型系统?
  • 什么是标称类型系统?
  • 结构化类型系统等价于鸭子类型系统吗?
  • 如何在 TypeScript 中模拟标称类型系统?
    • 交叉类型实现
    • 类实现
  • 总结

前言

你能说清楚类型、类型系统、类型检查这三个的区别吗?在理解TypeScript的结构化类型系统之前,我们首先要搞清楚这三个概念和它们之间的关系

  • 类型:即对变量的访问限制赋值限制。如 TypeScript 中的原始类型、对象类型、函数类型和字面量类型等类型,当一个变量类型确定后,你不能访问这个类型中不存在的属性或方法,也不能将其他不兼容的类型的变量赋给该变量
  • 类型系统:本质上是一组规则,其描述如何为变量、函数等结构分配和实施类型。同时还定义了如何判断类型之间的兼容性,也正是我们今天主要讨论的概念
  • 类型检查:是一种能力,一种确保类型遵循类型系统下的类型兼容性的能力

理解它们对理解我们今天要讨论的TypeScript的结构化类型系统很有帮助

类型系统分为结构化类型系统和标称类型系统,首先我们来看看它们分别都是什么

什么是结构化类型系统?

基于类型结构进行类型兼容性判断

关键体现在两个类型的比较当中,当两个类型比较时,如果是按照属性和方法是否相同来比较的话就称为结构化类型系统,也叫鸭子类型。

比如下面这个例子:

class Dog {
  say() {
    console.log('wang wang wang!')
  }
}
class Cat {
  say() {
    console.log('miao miao miao!')
  }
}
const invokeSay = (dog: Dog) => {
  dog.say()
}
const dog = new Dog()
const cat = new Cat()
invokeSay(dog) // wang wang wang!
invokeSay(cat) // miao miao miao!

虽然invokeSay函数接收的参数类型是Dog,但是由于Cat类型的结构和Dog是一样的(都是只有一个 say 方法),因此会被认为是同一种类型,这就是结构化类型的特点,基于类型结构进行类型兼容性判断

代表语言:C#、Python、Objective-C

什么是标称类型系统?

基于类型名进行兼容性判断

与结构化类型系统相对的叫标称类型系统,它在判断两个类型是否相同时,只看它们的名称是否相同,即便内部结构完全相同也不能认为是同一种类型,比如下面这个例子:

/** @description 人民币 */
type CNY = number
/** @description 美元 */
type USD = number
const CNYCount: CNY = 666
const USDCount: USD = 333
const addCount = (source: CNY, input: CNY) => source + input
addCount(CNYCount, USDCount)

在标称类型系统中,这里对于addCount的调用是错误的,尽管CNY和USD类型都是number类型,但由于它们的名称不同,因此被视为是不同类型

代表语言:C++、Java、Rust

结构化类型系统等价于鸭子类型系统吗?

严格意义上两者并不等同

  • 结构化类型系统基于完全的类型结构来判断类型兼容性
  • 鸭子类型系统基于运行时访问的部分来判断类型兼容性

TypeScript 本身并不是在运行时进行类型检查,因此并不严格等价于鸭子类型系统

如何在 TypeScript 中模拟标称类型系统?

由于 TypeScript 的类型系统是结构化类型系统,所以刚刚那个例子在 TypeScript 中是可以正常运行的:

/** @description 人民币 */
type CNY = number
/** @description 美元 */
type USD = number
const CNYCount: CNY = 666
const USDCount: USD = 333
const addCount = (source: CNY, input: CNY) => source + input
addCount(CNYCount, USDCount)

这里我们的意图应当是让人民币 CNY 和美元 USD 作为两种不同的类型,但是由于 TypeScript 结构化类型系统的特性,两个类型本质上都是number类型,因此会被认为是同一个类型

而如果是标称类型系统就不会有这个问题,如果我们能在 TypeScript 中模拟实现标称类型系统就符合预期了,那么要怎么模拟呢?

对比结构化类型系统和标称类型系统的特点,只要我们能够在两个完全兼容的结构化类型中加入一个标识符,那么即便这两个类型的结构是兼容的,但由于这个标识符并不相同,因而会被认为是两个不同的类型

利用这个特点我们就可以在 TypeScript 中模拟标称类型系统

以下有两种方式模拟实现标称类型系统:

  • 通过交叉类型实现 -- 只能在类型层面上实现,无法在运行时逻辑上实现
  • 通过类实现 -- 能兼顾类型层面和运行时逻辑层面

交叉类型实现

我们可以实现一个工具类型Nominal,对传入的泛型参数进行处理,将其和一个特殊的类型进行交叉类型操作,上面提到的标识符就是由这个特殊的类型提供的

nominal.ts

class TagProtector<T extends string> {
  protected __tag__: T
}
export type Nominal<T, U extends string> = T & TagProtector<U>
index.ts
import { Nominal } from './nominal'
/** @description 人民币 */
type CNY = Nominal<number, 'CNY'>
/** @description 美元 */
type USD = Nominal<number, 'USD'>
const CNYCount = 666 as CNY
const USDCount = 333 as USD
const addCount = (source: CNY, input: CNY) => source + input
// 类型“USD”的参数不能赋给类型“CNY”的参数。
//   不能将类型“USD”分配给类型“TagProtector<"CNY">”。
//     属性“__tag__”的类型不兼容。
//       不能将类型“"USD"”分配给类型“"CNY"”。ts(2345)
addCount(CNYCount, USDCount)

由于 TypeScript 实际运行时还是以 JavaScript 的方式运行的,所以类型代码会被抹除,抹除类型后这个代码仍然能够正常执行

这是因为通过这种方式模拟实现标称类型系统只能在类型层面模拟,实际的运行时并不能起到检查的作用,这时候就要用下面的方案 -- 用类模拟实现

类实现

/** @description 人民币 */
class CNY {
  private __tag__!: void
  constructor(public value: number) {}
}
/** @description 美元 */
class USD {
  private __tag__!: void
  constructor(public value: number) {}
}
const CNYCount = new CNY(666)
const USDCount = new USD(666)
const addCount = (source: CNY, input: CNY) => source.value + input.value
// 类型“USD”的参数不能赋给类型“CNY”的参数。
//   类型具有私有属性“__tag__”的单独声明。
addCount(CNYCount, USDCount)

以上两种方式本质都是通过一个非公开的额外属性对类型添加了额外的标识符,从而能够让结构化类型系统将它们判断为不同的类型

总结

相信现在你能够理解什么是结构化类型系统了,正如开头介绍的类型系统的概念中所说,它定义了如何判断类型之间的兼容性

而结构化类型系统对于类型之间兼容性的判断则是基于类型的结构来判断的,只要两个类型的结构上可兼容(如一个类型中的所有属性和方法在另一个类型中都存在),则可以将两个类型视为是兼容的

除此之外,我们还了解了与结构化类型系统对应的标称类型系统,并且了解到如何在TypeScript中模拟实现标称类型系统,让我们对结构化类型系统有更深刻的理解

以上就是TypeScript 背后的结构化类型系统原理详解的详细内容,更多关于TypeScript 结构化类型系统的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解如何发布TypeScript编写的npm包

    目录 前言 项目 初始化项目 构建库 添加测试 发布 测试一下 总结 前言 在这篇文章中,我们将使用TypeScript和Jest从头开始构建和发布一个NPM包. 我们将初始化一个项目,设置TypeScript,用Jest编写测试,并将其发布到NPM. 项目 我们的库称为digx.它允许从嵌套对象中根据路径找出值,类似于lodash中的get函数. 比如说: const source = { my: { nested: [1, 2, 3] } } digx(source, "my.nested[

  • 使用typescript开发angular模块并发布npm包

    本文介绍了使用typescript开发angular模块并发布npm包,分享给大家,具体如下: 创建模块 初始化package.json文件 执行命名 npm init -y 会自动生成package.json文件如下,name默认为文件夹名称 { "name": "MZC-Ng-Api", "version": "1.0.0", "description": "", "mai

  • React TypeScript 应用中便捷使用Redux Toolkit方法详解

    目录 前言 背景 Redux-Toolkit 常规使用 优化方案 优化 useDispatch 和 useSelector 优化修改 redux 状态的步骤 总结 前言 本文介绍的主要内容是 Redux-Toolkit 在 React + TypeScript 大型应用中的实践,主要解决的问题是使用 createSlice 的前提下消费 redux 状态仍旧有点繁琐的问题. 阅读本文需要的前置知识:了解 React .Redux-Toolkit .TypeScript 的使用. 关于 Redux

  • ESLint规范TypeScript代码使用方法

    目录 前导 安装依赖 Rules fix 聊一聊原理 parser plugins extends 总结 前导 ESLint 是一种 JavaScript linter,可以用来规范 JavaScript 或 TypeScript 代码,本文教你怎么在项目中快速把 ESLint 安排上. 怎么写出优雅的代码是一个老生常谈的话题,这其中包含了太多内容可聊,但搞一套标准规范绝对是万里长征第一步.ESLint 致力于如何把规范落地到你的项目中,让你的代码清清爽爽. ESLint 作为一种 JavaSc

  • 教你30秒发布一个TypeScript包到NPM的方法步骤

    文章读译自The 30 second guide to publishing a typescript package to npm,部分内容有修改哈. 这篇文章要求你有一定的 JS .TS 和 NPM 的知识,如果你写过普通的 NPM 包就更好啦~如果没有的话网上也很多教程的,都很简单~ 发布过 npm 包的同学都知道,初始化一个 npm 项目,直接用 npm init -y 就可以了,那如果要用 ts 呢,直接 tsc --init 即可.这两个操作会生成 package.json 和 ts

  • 从零使用TypeScript开发项目打包发布到npm

    前言 typescript作为未来前端开发的主流框架,在前端开发的过程中也会越来越主要,相信这篇文章会对你有很大的帮助! 开发环境搭建 创建ming-npm-package文件夹 我在桌面上创建了一个ming-npm-package的文件夹,然后在编辑器里面打开 初始化项目 npm init 通过npm init 初始化项目来创建用户package.json文件 也可以npm init -y 这个是使用的默认的配置,我个人使用的是npm init 设置配置项 package name: (min

  • TypeScript 背后的结构化类型系统原理详解

    目录 前言 什么是结构化类型系统? 什么是标称类型系统? 结构化类型系统等价于鸭子类型系统吗? 如何在 TypeScript 中模拟标称类型系统? 交叉类型实现 类实现 总结 前言 你能说清楚类型.类型系统.类型检查这三个的区别吗?在理解TypeScript的结构化类型系统之前,我们首先要搞清楚这三个概念和它们之间的关系 类型:即对变量的访问限制与赋值限制.如 TypeScript 中的原始类型.对象类型.函数类型和字面量类型等类型,当一个变量类型确定后,你不能访问这个类型中不存在的属性或方法,

  • MySQL索引背后的数据结构及算法原理详解

    摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等.为了避免混乱,本文将只关注于BTree索引,因为这是平常使用MySQL时主要打交道的索引,至于哈希索引和全文索引本文暂不讨论. 文章主要内容分为三个部分. 第一部分主要从数据结构及算法理论层面讨论MySQL数据库索引的数理基础. 第二部分结合MySQL数据库中My

  • javascript异常处理实现原理详解

    这篇文章主要介绍了javascript异常处理实现原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.什么是例外处理 当 JavaScript程序在运行中发生了诸如数组索引越界.类型不匹配或者语法错误时,JavaScript解释器就会引发例外处理. ECMAScript定义了六种类型的错误,除此之外,我们可以使用Error对象和throw语句来创建并引发自定义的例外处理信息. 通过运用例外处理技术,我们可以实现用结构化的方式来响应错误事

  • Python字典底层实现原理详解

    在Python中,字典是通过散列表或说哈希表实现的.字典也被称为关联数组,还称为哈希数组等.也就是说,字典也是一个数组,但数组的索引是键经过哈希函数处理后得到的散列值.哈希函数的目的是使键均匀地分布在数组中,并且可以在内存中以O(1)的时间复杂度进行寻址,从而实现快速查找和修改.哈希表中哈希函数的设计困难在于将数据均匀分布在哈希表中,从而尽量减少哈希碰撞和冲突.由于不同的键可能具有相同的哈希值,即可能出现冲突,高级的哈希函数能够使冲突数目最小化.Python中并不包含这样高级的哈希函数,几个重要

  • HashMap底层实现原理详解

    一.快速入门 示例:有一定基础的小伙伴们可以选择性的跳过该步骤 HashMap是Java程序员使用频率最高的用于映射键值对(key和value)处理的数据类型.随着JDK版本的跟新,JDK1.8对HashMap底层的实现进行了优化,列入引入红黑树的数据结构和扩容的优化等.本文结合JDK1.7和JDK1.8的区别,深入探讨HashMap的数据结构实现和功能原理. Java为数据结构中的映射定义了一个接口java.uti.Map,此接口主要有四个常用的实现类,分别是HashMap,LinkedHas

  • java synchronized的用法及原理详解

    目录 为什么要用synchronized 使用方式 字节码语义 对象锁(monitor) 锁升级过程 为什么要用synchronized 相信大家对于这个问题一定都有自己的答案,这里我还是要啰嗦一下,我们来看下面这段车站售票的代码: /** * 车站开两个窗口同时售票 */ public class TicketDemo { public static void main(String[] args) { TrainStation station = new TrainStation(); //

  • SpringBoot自动配置原理详解

    目录 阅读收获 一.SpringBoot是什么 二.SpringBoot的特点 三.启动类 3.1 @SpringBootApplication 四.@EnableAutoConfiguration 4.1 @AutoConfigurationPackage 4.2  @Import({AutoConfigurationImportSelector.class}) 五.流程总结图 六.常用的Conditional注解 七.@Import支持导入的三种方式 阅读收获 理解SpringBoot自动配

  • TypeScript声明文件的语法与场景详解

    目录 简介 语法 内容 模块化 模块语法 三斜线指令 reference amd-module 场景 1. 在内部项目中给内部项目写声明文件 2. 给第三方包写声明文件 全局变量的第三方库 修改全局变量的模块的第三方库的声明 修改window ESM和CommonJS UMD 模块插件 总结 简介 声明文件是以.d.ts为后缀的文件,开发者在声明文件中编写类型声明,TypeScript根据声明文件的内容进行类型检查.(注意同目录下最好不要有同名的.ts文件和.d.ts,例如lib.ts和lib.

  • Vue 不定高展开动效原理详解

    目录 使用场景 背景 实现 transition 组件 过渡效果原理 解决 使用场景 在大多数 APP 中,都有问答模块,类似于下面这种(bilibili 为例): 问答模块的静态页面开发并不复杂,也没有特殊的交互.唯一有一点难度应该是回答部分的展开特效. 展开时,需要从上往下将回答部分的 div 慢慢撑开,上面的箭头也要有旋转的特效. 收回时,需要从下往上将回答部分的 div 慢慢缩小,上面的箭头也要有旋转的特效. 对于一般的展开.隐藏特效,只需要在对应元素的 height 上面增加过渡效果即

  • Mysql数据库group by原理详解

    目录 引言 1. 使用group by的简单例子 2. group by 原理分析 2.1 explain 分析 2.2 group by 的简单执行流程 3. where 和 having的区别 3.1 group by + where 的执行流程 3.2 group by + having 的执行 3.3 同时有where.group by .having的执行顺序 3.4 where + having 区别总结 4. 使用 group by 注意的问题 4.1 group by一定要配合聚

随机推荐