TypeScript是一种基于结构类型的语言,可根据其成员来描述类型。以结构相同的Person接口和Programmer类为例,如下所示。

  1. interface Person {
  2. name: string;
  3. }
  4. class Programmer {
  5. name: string;
  6. }
  7. let person: Person = new Programmer();

  由于结构类型的关系,因此当变量声明为Person类型时,可通过Programmer类实例化。由此可知,结构类型只关注类型的组成结构,而名称并不重要。

一、函数

  在判断两个函数的兼容性时,需要考虑参数数量、返回值类型等多个方面。

1)参数数量

  假设有两个函数add()和sum(),它们的参数数量不同,后者比前者多一个参数,而函数的返回值类型相同,如下所示。

  1. let add = (x: number) => 0;
  2. let sum = (y: number, z: number) => 0;

  当把add赋给sum时,编译能成功执行;而反之,则会报错,如下所示。

  1. sum = add; //正确
  2. add = sum; //错误

  由此可知,参数少的函数不能赋给参数多的;而反过来时,需要确保每个位置所对应的参数类型保持一致,不过参数名称可以不同。

2)返回值类型

  假设有两个函数add()和sum(),它们没有参数,后者的返回值比前者多一个属性,如下所示。

  1. let add = () => ({ x: 1 });
  2. let sum = () => ({ x: 1, y: 2});

  当把sum赋给add时,编译能成功执行;而反之,则会报错,如下所示。

  1. add = sum; //正确
  2. sum = add; //错误

  由此可知,源函数的返回值类型得是目标函数返回值的子类型。

3)参数类型

  TypeScript中的参数类型需要同时满足协变和逆变,即双向协变。协变比较好理解,是指子类型兼容父类型,而逆变正好与协变相反。在下面的示例中,定义了父类Person和子类Programmer,Programmer类覆盖了Person类中的work()方法,并且其参数类型声明的更加宽泛。

  1. class Person {
  2. work(msg: string | undefined) { }
  3. }
  4. class Programmer extends Person {
  5. work(msg: string) { }
  6. }

  接下来声明两个函数,它们的参数类型分别是Person和Programmer两个类,如下所示,其中person()函数是programmer()函数的子类型。

  1. let person = (x: Person) => 0;
  2. let programmer = (x: Programmer) => 0;

  由于参数类型是双向协变的,因此两个变量之间可相互赋值,如下所示。

  1. person = programmer;
  2. programmer = person;

4)可选参数和剩余参数

  在比较函数的兼容性时,不需要匹配可选参数。以下面的pls()和add()两个函数为例,pls()中的两个参数必传,而add()中的第二个参数是可选的。

  1. let pls = (x: number, y: number) => 0;
  2. let add = (x: number, y?: number) => 0;
  3. pls = add;
  4. add = pls;

  虽然参数不同,但是两个函数仍然是兼容的,并且可以相互赋值。剩余参数相当于无限个可选参数,也不会被匹配。下面示例中的sum()函数只声明了剩余参数,它与pls()和add()两个函数都是兼容的。

  1. let sum = (...args: number[]) => 0;
  2. pls = sum;
  3. sum = pls;
  4.  
  5. add = sum;
  6. sum = add;

5)函数重载

  当比较存在多个重载的函数时,其每个重载都要在目标函数上找到对应的函数签名,以此确保目标函数能在源函数所有可调用的地方调用,如下所示。

  1. interface add {
  2. (x: number, y: string): any;
  3. (x: number, y: number): number;
  4. }
  5. function sum(x: number, y: string): any;
  6. function sum(x: number, y: number): number;
  7. let func: add = sum;

二、枚举

  来自于不同枚举类型的枚举值,被认为是不兼容的,如下所示,当把Direction.Up赋给color变量时,在编译阶段会报错。

  1. enum Color { Red, Green, Blue }
  2. enum Direction { Up, Down, Left, Right }
  3. let color = Color.Red; //正确
  4. color = Direction.Up; //错误

  数字枚举和数字类型相互兼容,如下所示,color变量被赋予了枚举成员,digit变量是一个数字,它们之间可以相互赋值。

  1. let color = Color.Red;
  2. let digit = 1;
  3. color = digit;
  4. digit = color;

  字符串枚举无法兼容字符串类型,如下所示,当把field变量赋给Color的枚举成员时,在编译阶段会报错,但反过来可以正确执行。

  1. enum Color { Red = "RED", Green = "GREEN", Blue = "BLUE" }
  2. let color = Color.Red;
  3. let field = "PURPLE";
  4. color = field; //错误
  5. field = color; //正确

三、类

  类与对象字面量和接口类似,但类包含静态和实例两部分。在比较两个类实例时,仅匹配它们的实例成员,而静态成员和构造函数不影响兼容性,因为它们在比较时会被忽略。

  在下面的示例中,创建了Person和Programmer两个类,虽然Programmer类包含了一个静态属性,并且其构造函数与Person类不同,但是它们之间可以相互兼容。

  1. class Person {
  2. name: string;
  3. constructor(name: string) { }
  4. }
  5. class Programmer {
  6. name: string;
  7. static age: number;
  8. constructor(name: string, age: number) { }
  9. }
  10.  
  11. let person: Person;
  12. let programmer: Programmer;
  13. person = programmer;
  14. programmer = person;

  类的私有成员和受保护成员会影响兼容性,TypeScript要求它们必须来源于同一个类,从而既能保证父类兼容子类,也能避免与其它相同结构的类兼容。

  在下面的示例中,Person和Teacher两个类都包含一个同名的私有属性,Programmer是Person的子类,三个变量的类型对应这三个类。

  1. class Person {
  2. private name: string;
  3. }
  4. class Teacher {
  5. private name: string;
  6. }
  7. class Programmer extends Person { }
  8.  
  9. let person: Person;
  10. let programmer: Programmer;
  11. let teacher: Teacher;

  person和programmer两个变量可相互赋值,因为它们的私有成员来源于同一个类,如下所示。

  1. person = programmer;
  2. programmer = person;

  虽然person和teacher两个变量的结构相同,但是它们的私有成员来源于两个不同的类,因此无法相互赋值,如下所示。

  1. person = teacher; //错误
  2. teacher = person; //错误

四、泛型

  当泛型接口中的类型参数未使用时,不会影响其兼容性,如下所示,x和y两个变量可相互赋值。

  1. interface Person<T> { }
  2. let x: Person<number>;
  3. let y: Person<string>;
  4.  
  5. x = y;
  6. y = x;

  当泛型接口中的类型参数被一个成员使用时,就会影响其兼容性,如下所示,x和y两个变量不可相互赋值。

  1. interface Person<T> {
  2. data: T;
  3. }
  4. let x: Person<number>;
  5. let y: Person<string>;
  6.  
  7. x = y; //错误
  8. y = x; //错误

  当比较未指定参数类型的泛型函数时,在检查兼容性之前会将其替换成any类型,例如下面的两个函数,相当于对“(x: any)=>any”和“(y: any)=>any”进行匹配,因此可相互赋值。

  1. let send = function<T>(x: T): T {
  2. return x;
  3. }
  4. let func = function<U>(y: U): U {
  5. return y;
  6. }
  7.  
  8. send = func;
  9. func = send;

  泛型类的兼容性规则与之前所述一致。

TypeScript躬行记(5)——类型兼容性的更多相关文章

  1. TypeScript躬行记(3)——类

    类是对对象的抽象,描述了对象的特征和行为,而对象就是类的实例.ES6引入了类的概念(相关内容可参考ES类和ES6类的继承两节),TypeScript在此基础上,不仅根据ES7等规范完善了类的语法,还添 ...

  2. TypeScript躬行记(6)——高级类型

    本节将对TypeScript中类型的高级特性做详细讲解,包括交叉类型.类型别名.类型保护等. 一.交叉类型 交叉类型(Intersection Type)是将多个类型通过“&”符号合并成一个新 ...

  3. TypeScript躬行记(8)——装饰器

    装饰器(Decorator)可声明在类及其成员(例如属性.方法等)之上,为它们提供一种标注,用于分离复杂逻辑或附加额外逻辑,其语法形式为@expression.expression是一个会在运行时被调 ...

  4. TypeScript躬行记(1)——数据类型

    TypeScript不仅支持JavaScript所包含的数据类型,还额外扩展了许多实用的数据类型,例如枚举.空值.任意值等. 一.JavaScript的数据类型 JavaScript的数据类型包括6种 ...

  5. TypeScript躬行记(2)——接口

    在传统的面向对象语言中,接口(Interface)好比协议,它会列出一系列的规则(即对行为进行抽象),再由类来实现这些规则.而TypeScript中的接口更加灵活,除了包含常规的作用之外,它还能扩展其 ...

  6. TypeScript躬行记(4)——泛型

    泛型是程序设计语言中的一种风格或范式,相当于类型模板,允许在声明类.接口或函数等成员时忽略类型,而在未来使用时再指定类型,其主要目的是为它们提供有意义的约束,提升代码的可重用性. 一.泛型参数 当一个 ...

  7. TypeScript躬行记(7)——命名空间

    TypeScript中的命名空间可将那些具有内在联系的接口.类或对象等代码组织在一起,既能隔离作用域,也能避免命名冲突,并且使得代码结构清晰,更易追踪.在命名空间内部,所有实体部分默认都是私有的,需要 ...

  8. ES6躬行记(1)——let和const

    古语云:“纸上得来终觉浅,绝知此事要躬行”.的确,不管看了多少本书,如果自己不实践,那么就很难领会其中的精髓.自己研读过许多ES6相关的书籍和资料,平时工作中也会用到,但在用到时经常需要上搜索引擎中查 ...

  9. ES6躬行记 笔记

    ES6躬行记(18)--迭代器 要实现以下接口## next() ,return,throw 可以用for-of保证迭代对象的正确性 例如 var str = "向

随机推荐

  1. 微信小程序(一)--微信小程序的介绍

    一.微信小程序简介 小程序是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或者搜一下即可打开应用.也体现了“用完即走”的理念,用户不用关心是否安装太多应用的问题.应用将无 ...

  2. 《剑指offer》面试题7 用两个栈实现队列 Java版

    书中方法:队列是先进先出的,栈是先进后出的,试想把一串数压入A栈,接着一个个出栈并压入B栈,便会完成"头在下"到"头在上"的转变.B栈内还有元素时,直接出栈表示 ...

  3. Linux忘记密码怎么办

    重启 Linux 系统主机并出现引导界面时,按下键盘上的 e 键进入内核编辑界面 在 linux16 参数这行的最后面追加"rd.break"参数,然后按下 Ctrl + X 组合 ...

  4. python学习第二十七天函数的return返回值

    python函数返回值用的return ,函数遇到return 结束函数运行过程,终止程序,不论后面还有多少个输出,都终止本次函数,所有一定要慎重用return 1,函数return用法 def go ...

  5. 关联查询总结,left join 和 inner join 区别和优化

    left join 是做左外关联,主表内容都会显示:符合关联条件的附表内容才会显示出来. inner join 是内关联,没有主表附表的概念:两个表中,同时符合关联条件的数据才会显示出来. left ...

  6. 微信、qq网页二次分享

    二次分享是指,在APP或者浏览器分享到微信或者qq,然后从微信或者qq再分享到别的平台.如果不处理,再次分享出去的图片或者标题就不会显示,对用户非常不友好. 一.微信二次分享 官方接入文档:https ...

  7. k3 cloud成本调整单引入单据后,再做出库成本核算。成本调整单列表已审核的单据消失,非已审核的单据还在,这是出库成本核算设置参数的问题吗?

    存货核算时,会将“期末余额调整”类型的的调整单删除后,再重新产生:因此引入后不要再做出库核算,或者引入其它类型的单据.

  8. vue 中使用 watch 出现了如下的报错

    vue 中使用 watch 出现了如下的报错 报错: Method "watch" has type "object" in the component def ...

  9. jar 启动脚本

    前段时间用springboot做项目后,每次重新发布都好麻烦, 所以写了个脚本来配合jenkins 发布: #!/bin/bash APP_NAME=application.jar function ...

  10. webpack的理解、总结

    weabpck的基础应用 https://blog.zhangjd.me/2016/06/19/webpack-your-bags/ https://juejin.im/post/5cc26dfef2 ...