TypeScript中的类型兼容是基于结构归类的。在普通分类的相比之下,结构归类是一种纯粹用于将其成员的类型进行关联的方法。思考下面的代码:

interface Named {
name: string;
} class Person {
name: string;
} var p: Named;
// 正确, 因为这里编译器自动进行结构归类
p = new Person();

如C#、Java这些表面上的类型语言(这里指的“表面上的类型语言”,指C#和Java需要使用“implements”关键字明确指出类实现某个接口才能对应得上其类型),以上的代码便会被当作错误的,因为没有明确指出Person类实现(implements)Named接口。

TypeScript的结构类型系统就是基于JavaScript代码典型的写法设计的。因为JavaScript广泛使用匿名对象如函数表达式和字面量对象,使用结构类型系统代替表面上处理将使JavaScript中这些关系体现的更自然。

TypeScript类型系统允许执行某些在编译阶段无法确定安全性的操作。当一个类型系统有这个属性的时候,我们视之为“不健全的”。TypeScript中允许执行这些操作这一机制是经过仔细考虑的,通过文档我们将解释什么情况下会发生这种事和允许这些操作后所带来的好的一面。

跟着代码出发(老司机,带带我...)

TypeScript结构类型系统的基本规则如:如果x是兼容y的,那么y至少具有和x相同的属性成员。例如:

interface Named {
name: string;
} var x: Named;
// 推断出y的类似是{ name: string; location: string; }
var y = { name: 'Alice', location: 'Seattle' };
x = y;

为了检查y是否能够赋值给x,编译器需要检查x的每个属性,并且在y中找到对应的兼容属性。在这种情况下,y必须有个名为name并且值是字符串的属性。而y满足了这条件,所以能够赋值给x。

同样的规则也适用在检查函数调用参数时:

// 接着上面的代码
function greet(n: Named) {
alert('Hello, ' + n.name);
}
greet(y); // ok

注意,y有一个额外的"location'属性,但这并未产生错误。只有目标类型的成员(这里的目标类型指“Named”)会被检查是否兼容。

比较的过程是递归进行的,检查每个成员及其子成员的类型。

两个函数之间的比较

原始类型和对象类型的比较是相对简单的,但问题是被认为是兼容的函数是怎么样的呢。让我们从一个最基本的例子开始吧,以下两个函数的不同之处仅仅在于他们的参数列表:

var x = (a: number) => 0;
var y = (b: number, s: string) => 0; y = x; // ok
x = y; // 错误

若要检查x是否可以赋值给y,首先看参数列表。y中的每个参数必须在x中都有相应并且类型兼容的参数。主意,参数名可不考虑,只要类型能够对应上。在这个案例中,x的每个参数在y中都有相应的参数,所以是允许赋值的。第二个赋值是错误的,因为y的第二个属性是必须的,但是x没这个属性,所以不被允许赋值。

你可能对为什么在y=x中允许第二个参数而感到疑惑。赋值的过程允许忽略函数额外的参数,这在JavaScript中实际上很常见。例如Array的forEach函数为他的回调函数提供了三个参数:数组元素、索引、包含它的数列。然而,大多用到的也就第一个参数。

var items = [1, 2, 3];

// 这些参数不是强制要求的
items.forEach((item, index, array) => console.log(item)); // 这样也可以
items.forEach((item) => console.log(item));

现在让我们来看看返回值类型是如何处理的,使用两个仅返回值类型不同的函数:

var x = () => ({name: 'Alice'});
var y = () => ({name: 'Alice', location: 'Seattle'}); x = y; // ok
y = x; // 错误,因为x()缺少一个属性

类型机制强制要求源函数的返回值类型是目标函数返回值类型的子类型。

可选参数及剩余参数

当对函数的兼容进行比较时,可选和必须的参数是可以互换的。源类型有额外的可选参数不会造成错误,目标类型的可选参数中不存在对应参数也不会产生错误。
当函数有其余的参数,将会被当作无限的可选参数一样来处理。

从类型机制来看这是不健全的,但从代码运行的角度看,可选参数不是强制要求的,因为它相当于在函数参数的对应位置传入一个"undefined"。

下面的例子是个普遍模式的函数,该函数需要传入个回调函数,并且在调用的时候传入可预知(对于开发者而言)但是未知数量(对于类型机制)的参数。

function invokeLater(args: any[], callback: (...args: any[]) => void):void {
callback.apply(null,args);
} // invokeLater"可能"任何数量的参数
invokeLater([1, 2], (x, y) => console.log(x , y));
invokeLater([3], (x?, y?) => console.log(x , y));
invokeLater([4,5,6,7], (x?, y?) => console.log(x , y));

重载的函数

当一个函数具有重载情况时,源类型的每次重载必须在目标类型上可找到匹配的签名。这确保了目标函数可以在所有源函数可调用的地方调用。当做兼容性检查时,带有特殊签名的函数重载(那些重载时使用字符串)将不会使用他们特殊签名。(详情可见:TypeScript Declaration Merging(声明合并)中的接口合并第二个案例)

枚举

枚举和number相互兼容。不同枚举类型的枚举值之间是不兼容的。例如:

enum Status { Ready, Waiting };
enum Color { Red, Blue, Green }; var status = Status.Ready;
status = Color.Green; // 错误

类的兼容和对象字面量类型还有接口的兼容相似,只是有一个不同:它具有静态类型和实例类型。比较两个类类型的对象时,只比较实例部分的成员。静态部分的成员和构造函数不影响兼容性。

class Animal {
feet: number;
constructor(name: string, numFeet: number) { }
} class Size {
feet: number;
constructor(numFeet: number) { }
} var a: Animal;
var s: Size; a = s; // ok
s = a; // ok

类的私有成员

类中的私有成员会影响其兼容性。当一个类的实例进行兼容性检查时,如果它包含一个私有成员,那么目标类型必须也包含一个来源与同一个类的私有成员(详情可参阅:TypeScript Class(类)中的理解Private(私有))。这也造成了一个类可以被赋值为其父类的实例,但是却不能被赋值成另一个继承其父类的类(虽然他们是同一个类型)。

泛型

因为TypeScript是一个结构类型系统,参数类型只影响将其作为部分成员类型的目标类型(比如有个函数fn(a:string),然后函数中有个变量的某属性值是a,那么a对这个目标类型将产生影响)。

案例:

interface Empty<T> {
}
var x: Empty<number>;
var y: Empty<string>; x = y; // ok, y可以和x的结构相匹配

在上述例子中,x和y是兼容的,因为他们的结构在使用类型参数并没什么不同之处。改变这个例子,给Empty<T>加个成员,看看会是什么情况:

interface NotEmpty<T> {
data: T;
}
var x: NotEmpty<number>;
var y: NotEmpty<string>; x = y; // 错误,x和y不兼容

对于那些没有指定其参数类型的泛型类型,兼容性的检查是通过为所有没指定参数类型的参数使用"any"代替的。然后目标类型检查兼容性,就和非泛型的案例一般。

案例:

var identity = function<T>(x: T): T {
// ...
} var reverse = function<U>(y: U): U {
// ...
} identity = reverse; // ok,因为(x: any)=>any和(y: any)=>any可以匹配

深层探讨

子类VS赋值

至此为止,我们了解了兼容性,它并未在语言规范里定义。在TypeScript中有两种兼容:子类和赋值。它们的不同点在于,赋值扩展了子类型的兼容性并且可对"any"进行取值和赋值、对枚举进行取对应数值。

根据情况的不同,TypeScript语言会在不同的地方使用两种兼容机制中的一种。对于实际运用而言,类型的兼容性是由赋值时的兼容检查或者implements和extends关键字来控制的。更多详情,请参照TypeScript spec (友情提醒,是doc文件下载)

TypeScript Type Compatibility(类型兼容)的更多相关文章

  1. TypeScript Type Innference(类型推断)

    在这一节,我们将介绍TypeScript中的类型推断.我们将会讨论类型推断需要在何处用到以及如何推断. 基础 在TypeScript中,在几个没有明确指定类型注释的地方将会使用类型推断来提供类型信息. ...

  2. 为vue3.0学点typescript, 解读高级类型

    知识点摘要 本节课主要关键词为: 自动类型推断 / 类型断言 / 类型别名(type) / 映射类型(Pick/Record等...) / 条件类型(extends) / 类型推断(infer) 自动 ...

  3. TypeScript 之 基础类型、高级类型

    基础类型:https://m.runoob.com/manual/gitbook/TypeScript/_book/doc/handbook/Basic%20Types.html 高级类型:https ...

  4. TypeScript完全解读(26课时)_11.TypeScript完全解读-类型推论和兼容性

    11.TypeScript完全解读-类型推论和兼容性 在一些时候省略指令,ts会帮我们推断出省略的类型的地方适合的类型,通过学习ts的类型推论了解ts的推论规则 类型兼容性就是为了适应js灵活的特点, ...

  5. 类型兼容原则(C++)

    类型兼容原则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代. 通过公有继承,派生类得到了基类中除构造函数.析构函数之外的所有成员.这样,公有派生类实际具备了基类的所有功能,凡是基类能解 ...

  6. media query(媒体查询)和media type(媒体类型)

    media type(媒体类型)是css 2中的一个非常有用的属性,通过media type我们可以对不同的设备指定特定的样式,从而实现更丰富的界面.media query(媒体查询)是对media ...

  7. 【Type】类型 ParameterizedType

    Type 接口[重要] Type接口完整的定义: public interface java.lang.reflect.Type { /** * Returns a string describing ...

  8. 【转载】C#使用is关键字检查对象是否与给定类型兼容

    在C#的编程开发过程中,很多时候涉及到数据类型的转换,如果强行转换数据类型,有时候可能会出现程序运行时错误,C#语言中提供了is关键字可以检查对象是否与给定类型兼容,可先判断类型兼容后再进行对象的转换 ...

  9. Type Java类型

    参考:https://blog.csdn.net/a327369238/article/details/52621043 Type —— Java类型 Type是一个空接口,所有类型的公共接口(父接口 ...

随机推荐

  1. Windows下使用Xshell建立反向隧道

    反向隧道是一个进行内网穿透的简单而有用的方法.在Linux下通过OpenSSH和AutoSSH可以很容易地建立稳定的反向隧道.但是在Windows下,还能看到有人特意装个Cygwin来运行这些工具…… ...

  2. Nginx搭建反向代理服务器过程详解

    一.反向代理:Web服务器的“经纪人” 1.1 反向代理初印象 反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从 ...

  3. RESTORE detected an error on page (0:0) in database

    在测试服务器还原生产服务器的一个数据库时遇到了下面错误: System.Data.SqlClient.SqlError: RESTORE detected an error on page (0:0) ...

  4. Java通过几种经典的算法来实现数组排序

    Java实现数组排序 package com.souvc.hibernate.exp; public class MySort { /** * 方法名:main</br> * 详述:Jav ...

  5. 客户端连接RMS服务,报:服务暂时不可用,请确保已连接到此服务器…….

    原因在于客户端office没有安装rms服务模块,或安装的office有缺陷,请重新安装可用的office版本.

  6. python 多线程实例

    #!/usr/bin/env python # -*- coding:utf-8 -*- import Queue import threading class ThreadPool(object): ...

  7. Oracle数据库穿越防火墙访问

    原因 Oracle listener 只起一个中介作用,当客户连接它时,它根据配置寻找到相应的数据库实例进程,然后spawned一个新的数据库连接,这个连接端口由listener传递给客户机,此后客户 ...

  8. javamail中的 javax.mail.AuthenticationFailedException: failed to connect

    java.lang.RuntimeException: javax.mail.AuthenticationFailedException: failed to connect javax.mail.A ...

  9. 迅为最新推出iTOP-6818开发平台无缝支持4418开发板

    iTOP-6818开发板是一款四核ARM 八核开发板与iTOP-4418开发板完全兼容,CPU主频1.4GHz,内存1GB DDR3(2GB可选),存储16GB EMMC,板载千兆以太网,GPS,WI ...

  10. EF with (LocalDb)V11.0

    EF虽说对LocalDb支持的不错,但LocalDb有自身的缺陷(不想sqlite那样数据库文件可以像普通文件一样使用). LocalDb在一个计算机上会对数据库有唯一性约束,要求本机的localdb ...