啥是鸭子类型?
作为一个前端程序员,想必大家都知道javascript是一个弱类型语言,如果需要类型的支持,那就需要借助typescript来实现,但是大家可曾听过这样一个说法?
javascript属于鸭子类型
当我第一次看到这个说法时,我不禁哈哈大笑,鸭子类型是啥?其实这不过是一个比喻而已,鸭子类型的意思是:
如果一个动物看起来像鸭子,游起泳来像鸭子,叫起来也像鸭子,那么它大概率就是鸭子。
结构化类型
TypeScript使用结构化类型(Structural Typing)
来实现javascript中的鸭子类型,结构化类型描述的是两个类型之间的兼容性,我们看一个具体的例子,再下结论。
假设你正在开发一个3D图形应用程序,这个程序最基本的功能就是绘制图形,而绘制图形最基本的数据结构就是点,我们先定义一个2D点。
1 | interface Point2D { |
当然,要绘制3D图形,我们还需要一个3D点。
1 | interface Point3D { |
现在我们可以下结论了,Point3D
与Point2D
是兼容的,因为Point3D
包含了所有Point2D
的属性。
所以结构化类型的定义如下:
如果一个类型B包含了另一个类型A的所有属性,那么这两个类型是兼容的,我们可以将类型B赋值给类型A。
需要注意的是,这种兼容性是单向的,Point3D
可以赋值给Point2D
,但反之不行,因为Point2D
缺少了z
属性。
其实这不难理解,假设我们要绘制一条2D线段,需要两个点来表示这条线段的起点和终点。
1 | function drawLine(start: Point2D, end: Point2D) { |
那么如果我们传入的是Point3D
类型的点,程序依然可以正常工作,因为Point3D
包含了Point2D
的所有属性。多出来的z
属性直接忽略,并不影响结果。
1 | const start: Point3D = { x: 0, y: 0, z: 0 }; |
我们甚至不需要传递一个Point3D
类型的点,任意一个包含x
和y
属性的对象都可以作为参数传递给drawLine
函数。
1 | const start = { x: 0, y: 0 }; |
这就是结构化类型的威力,也是JavaScript的灵活性所在。
名义类型
与结构化类型对应的是名义类型(Nominal Typing)
,比如Java
和C#
这种强类型语言,使用的都是名义类型,名义类型要求类型的名称必须匹配才能兼容。也就是说,只有当两个类型的名称完全相同或者存在继承关系时,它们才被认为是兼容的。
对于Java
或者C#
这样的强类型语言来说,上面drawLine
的例子就不成立了,因为Point2D
和Point3D
是两个不同的类型,即使它们有相同的属性,也不能互相替换。
1 | class Point2D { |
基于这个原因,在强类型语言中如果要实现类型兼容性的话,只能通过继承来实现。
1 | class Point2D { |
上面的例子中,Point3D
继承自Point2D
,这就意味着Point3D
是一个Point2D
类型的对象,可以在需要Point2D
的地方使用。