TypeScript结构化类型初探

啥是鸭子类型?

作为一个前端程序员,想必大家都知道javascript是一个弱类型语言,如果需要类型的支持,那就需要借助typescript来实现,但是大家可曾听过这样一个说法?

javascript属于鸭子类型

当我第一次看到这个说法时,我不禁哈哈大笑,鸭子类型是啥?其实这不过是一个比喻而已,鸭子类型的意思是:

如果一个动物看起来像鸭子,游起泳来像鸭子,叫起来也像鸭子,那么它大概率就是鸭子。

结构化类型

TypeScript使用结构化类型(Structural Typing)来实现javascript中的鸭子类型,结构化类型描述的是两个类型之间的兼容性,我们看一个具体的例子,再下结论。

假设你正在开发一个3D图形应用程序,这个程序最基本的功能就是绘制图形,而绘制图形最基本的数据结构就是点,我们先定义一个2D点。

typescript 复制代码
interface Point2D {
  x: number;
  y: number;
}

当然,要绘制3D图形,我们还需要一个3D点。

typescript 复制代码
interface Point3D {
  x: number;
  y: number;
  z: number;
}

现在我们可以下结论了,Point3DPoint2D是兼容的,因为Point3D包含了所有Point2D的属性。

所以结构化类型的定义如下:

如果一个类型B包含了另一个类型A的所有属性,那么这两个类型是兼容的,我们可以将类型B赋值给类型A。

需要注意的是,这种兼容性是单向的,Point3D可以赋值给Point2D,但反之不行,因为Point2D缺少了z属性。

其实这不难理解,假设我们要绘制一条2D线段,需要两个点来表示这条线段的起点和终点。

typescript 复制代码
function drawLine(start: Point2D, end: Point2D) {
  // 绘制线段的逻辑
}

那么如果我们传入的是Point3D类型的点,程序依然可以正常工作,因为Point3D包含了Point2D的所有属性。多出来的z属性直接忽略,并不影响结果。

typescript 复制代码
const start: Point3D = { x: 0, y: 0, z: 0 };
const end: Point3D = { x: 1, y: 1, z: 1 };
drawLine(start, end); // 依然可以正常绘制线段

我们甚至不需要传递一个Point3D类型的点,任意一个包含xy属性的对象都可以作为参数传递给drawLine函数。

typescript 复制代码
const start = { x: 0, y: 0 };
const end = { x: 1, y: 1 };
drawLine(start, end); // 依然可以正常绘制线段

这就是结构化类型的威力,也是JavaScript的灵活性所在。

名义类型

与结构化类型对应的是名义类型(Nominal Typing),比如JavaC#这种强类型语言,使用的都是名义类型,名义类型要求类型的名称必须匹配才能兼容。也就是说,只有当两个类型的名称完全相同或者存在继承关系时,它们才被认为是兼容的。

对于Java或者C#这样的强类型语言来说,上面drawLine的例子就不成立了,因为Point2DPoint3D是两个不同的类型,即使它们有相同的属性,也不能互相替换。

java 复制代码
class Point2D {
    int x;
    int y;
}

class Point3D {
    int x;
    int y;
    int z;
}

void drawLine(Point2D start, Point2D end) {
    // 绘制线段的逻辑
}

Point3D start = new Point3D(); // 定义起点
Point3D end = new Point3D(); // 定义终点
drawLine(start, end); // 编译错误,Point3D不是Point2D类型

基于这个原因,在强类型语言中如果要实现类型兼容性的话,只能通过继承来实现。

java 复制代码
class Point2D {
    int x;
    int y;
}

class Point3D extends Point2D {
    int z;
}

void drawLine(Point2D start, Point2D end) {
    // 绘制线段的逻辑
}

Point3D start = new Point3D(); // 定义起点
Point3D end = new Point3D(); // 定义终点
drawLine(start, end); // 现在可以正常工作

上面的例子中,Point3D继承自Point2D,这就意味着Point3D是一个Point2D类型的对象,可以在需要Point2D的地方使用。

相关推荐
吴声子夜歌4 分钟前
ES6——Calss详解
javascript·es6·原型模式
❆VE❆6 分钟前
虚拟列表原理与实战运用场景详解
前端·javascript·css·vue.js·html·虚拟列表
灵感__idea8 小时前
Hello 算法:贪心的世界
前端·javascript·算法
killerbasd11 小时前
牧苏苏传 我不装了 4/7
前端·javascript·vue.js
橘子编程12 小时前
JavaScript与TypeScript终极指南
javascript·ubuntu·typescript
叫我一声阿雷吧12 小时前
JS 入门通关手册(45):浏览器渲染原理与重绘重排(性能优化核心,面试必考
javascript·前端面试·前端性能优化·浏览器渲染·浏览器渲染原理,重排重绘·reflow·repaint
大家的林语冰12 小时前
《前端周刊》尤大开源 Vite+ 全家桶,前端工业革命启动;尤大爆料 Void 云服务新产品,Vite 进军全栈开发;ECMA 源码映射规范......
前端·javascript·vue.js
jiayong2313 小时前
第 8 课:开始引入组合式函数
前端·javascript·学习
天若有情67314 小时前
【C++原创开源】formort.h:一行头文件,实现比JS模板字符串更爽的链式拼接+响应式变量
开发语言·javascript·c++·git·github·开源项目·模版字符串
yuki_uix14 小时前
重排、重绘与合成——浏览器渲染性能的底层逻辑
前端·javascript·面试