TypeScript Type Compatibility(类型兼容)

简介: TypeScript中的类型兼容是基于结构归类的。在普通分类的相比之下,结构归类是一种纯粹用于将其成员的类型进行关联的方法。思考下面的代码: interface Named { name: string; } class Person { name: string;...

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 {
}
var x: Empty;
var y: Empty;

x = y;  // ok, y可以和x的结构相匹配

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

interface NotEmpty {
    data: T;
}
var x: NotEmpty;
var y: NotEmpty;

x = y;  // 错误,x和y不兼容

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

案例:

var identity = function(x: T): T { 
    // ...
}

var reverse = function(y: U): U {
    // ...
}

identity = reverse;  // ok,因为(x: any)=>any和(y: any)=>any可以匹配

深层探讨

子类VS赋值

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

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

相关文章
|
19天前
|
设计模式 JavaScript 安全
TypeScript性能优化及代码质量提升的重要性、方法与策略,包括合理使用类型注解、减少类型断言、优化模块导入导出、遵循编码规范、加强代码注释等
本文深入探讨了TypeScript性能优化及代码质量提升的重要性、方法与策略,包括合理使用类型注解、减少类型断言、优化模块导入导出、遵循编码规范、加强代码注释等,旨在帮助开发者在保证代码质量的同时,实现高效的性能优化,提升用户体验和项目稳定性。
40 6
|
18天前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
33 2
|
26天前
|
JavaScript 安全 前端开发
TypeScript类型声明:基础与进阶
通过本文的介绍,我们详细探讨了TypeScript的基础与进阶类型声明。从基本数据类型到复杂的泛型和高级类型,TypeScript提供了丰富的工具来确保代码的类型安全和可维护性。掌握这些类型声明能够帮助开发者编写更加健壮和高效的代码,提高开发效率和代码质量。希望本文能为您在使用TypeScript时提供实用的参考和指导。
32 2
|
1月前
|
JavaScript 开发者
在 Babel 插件中使用 TypeScript 类型
【10月更文挑战第23天】可以在 Babel 插件中更有效地使用 TypeScript 类型,提高插件的开发效率和质量,减少潜在的类型错误。同时,也有助于提升代码的可理解性和可维护性,使插件的功能更易于扩展和升级。
|
2月前
|
JavaScript 前端开发
TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
【10月更文挑战第11天】TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
|
2月前
|
JavaScript 前端开发 安全
TypeScript【基础类型】超简洁教程!再也不用看臭又长的TypeScript文档了!
【10月更文挑战第9天】TypeScript【基础类型】超简洁教程!再也不用看臭又长的TypeScript文档了!
|
1月前
|
JavaScript 前端开发 安全
TypeScript进阶:类型系统与高级类型的应用
【10月更文挑战第25天】TypeScript作为JavaScript的超集,其类型系统是其核心特性之一。本文通过代码示例介绍了TypeScript的基本数据类型、联合类型、交叉类型、泛型和条件类型等高级类型的应用。这些特性不仅提高了代码的可读性和可维护性,还帮助开发者构建更健壮的应用程序。
29 0
|
3月前
|
JavaScript
typeScript进阶(9)_type类型别名
本文介绍了TypeScript中类型别名的概念和用法。类型别名使用`type`关键字定义,可以为现有类型起一个新的名字,使代码更加清晰易懂。文章通过具体示例展示了如何定义类型别名以及如何在函数中使用类型别名。
47 1
typeScript进阶(9)_type类型别名
|
2月前
|
JavaScript 前端开发 安全
深入理解TypeScript:增强JavaScript的类型安全性
【10月更文挑战第8天】深入理解TypeScript:增强JavaScript的类型安全性
57 0
|
2月前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与实用技巧
【10月更文挑战第8天】深入理解TypeScript:类型系统与实用技巧