版权

文章转载自:https://github.com/zhongsp

建议您直接跳转到上面的网址查看最新版本。

介绍

TypeScript的核心原则之一是对值所具有的shape进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

接口初探

下面通过一个简单示例来观察接口是如何工作的:

  1. function printLabel(labelledObj: { label: string }) {
  2. console.log(labelledObj.label);
  3. }
  4.  
  5. var myObj = { size: 10, label: "Size 10 Object" };
  6. printLabel(myObj);

类型检查器会查看printLabel的调用。 printLabel有一个参数,并要求这个对象参数有一个名为label类型为string的属性。 需要注意的是,我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配。

下面我们重写上面的例子,这次使用接口来描述:必须包含一个label属性且类型为string

  1. interface LabelledValue {
  2. label: string;
  3. }
  4.  
  5. function printLabel(labelledObj: LabelledValue) {
  6. console.log(labelledObj.label);
  7. }
  8.  
  9. var myObj = {size: 10, label: "Size 10 Object"};
  10. printLabel(myObj);

LabelledValue接口就好比一个名字,用来描述上面例子里的要求。 它代表了有一个label属性且类型为string的对象。 需要注意的是,我们在这里并不能像在其它语言里一样,说传给printLabel的对象实现了这个接口。我们只会去关注值的外形。 只要传入的对象满足上面提到的必要条件,那么它就是被允许的。

还有一点值得提的是,类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。

可选属性

接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 可选属性在应用“option bags”模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。

下面是应用了“option bags”的例子:

  1. interface SquareConfig {
  2. color?: string;
  3. width?: number;
  4. }
  5.  
  6. function createSquare(config: SquareConfig): {color: string; area: number} {
  7. var newSquare = {color: "white", area: 100};
  8. if (config.color) {
  9. newSquare.color = config.color;
  10. }
  11. if (config.width) {
  12. newSquare.area = config.width * config.width;
  13. }
  14. return newSquare;
  15. }
  16.  
  17. var mySquare = createSquare({color: "black"});

带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?符号。

可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。 比如,我们故意将createSquare里的color属性名拼错,就会得到一个错误提示:

  1. interface SquareConfig {
  2. color?: string;
  3. width?: number;
  4. }
  5.  
  6. function createSquare(config: SquareConfig): {color: string; area: number} {
  7. var newSquare = {color: "white", area: 100};
  8. if (config.color) {
  9. // Error: Property 'collor' does not exist on type 'SquareConfig'
  10. newSquare.color = config.collor; // Type-checker can catch the mistyped name here
  11. }
  12. if (config.width) {
  13. newSquare.area = config.width * config.width;
  14. }
  15. return newSquare;
  16. }
  17.  
  18. var mySquare = createSquare({color: "black"});

函数类型

接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。

为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

  1. interface SearchFunc {
  2. (source: string, subString: string): boolean;
  3. }

这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。

  1. var mySearch: SearchFunc;
  2. mySearch = function(source: string, subString: string) {
  3. var result = source.search(subString);
  4. if (result == -1) {
  5. return false;
  6. }
  7. else {
  8. return true;
  9. }
  10. }

对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。 比如,我们使用下面的代码重写上面的例子:

  1. var mySearch: SearchFunc;
  2. mySearch = function(src: string, sub: string): boolean {
  3. var result = src.search(sub);
  4. if (result == -1) {
  5. return false;
  6. }
  7. else {
  8. return true;
  9. }
  10. }

函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。 如果你不想指定类型,Typescript的类型系统会推断出参数类型,因为函数直接赋值给了SearchFunc类型变量。 函数的返回值类型是通过其返回值推断出来的(此例是falsetrue)。 如果让这个函数返回数字或字符串,类型检查器会警告我们函数的返回值类型与SearchFunc接口中的定义不匹配。

  1. var mySearch: SearchFunc;
  2. mySearch = function(src, sub) {
  3. var result = src.search(sub);
  4. if (result == -1) {
  5. return false;
  6. }
  7. else {
  8. return true;
  9. }
  10. }

数组类型

与使用接口描述函数类型差不多,我们也可以描述数组类型。 数组类型具有一个index类型表示索引的类型,还有一个相应的返回值类型表示通过索引得到的元素的类型。

  1. interface StringArray {
  2. [index: number]: string;
  3. }
  4.  
  5. var myArray: StringArray;
  6. myArray = ["Bob", "Fred"];

支持两种索引类型:string和number。 数组可以同时使用这两种索引类型,但是有一个限制,数字索引返回值的类型必须是字符串索引返回值的类型的子类型。

索引签名能够很好的描述数组和dictionary模式,它们也要求所有属性要与返回值类型相匹配。 因为字符串索引表明obj.propertyobj["property"]两种形式都可以。 下面的例子里,name的类型与字符串索引类型不匹配,所以类型检查器给出一个错误提示:

  1. interface NumberDictionary {
  2. [index: string]: number;
  3. length: number; // 可以,length是number类型
  4. name: string // 错误,`name`的类型不是索引类型的子类型
  5. }

类类型

实现接口

与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约。

  1. interface ClockInterface {
  2. currentTime: Date;
  3. }
  4.  
  5. class Clock implements ClockInterface {
  6. currentTime: Date;
  7. constructor(h: number, m: number) { }
  8. }

你也可以在接口中描述一个方法,在类里实现它,如同下面的setTime方法一样:

  1. interface ClockInterface {
  2. currentTime: Date;
  3. setTime(d: Date);
  4. }
  5.  
  6. class Clock implements ClockInterface {
  7. currentTime: Date;
  8. setTime(d: Date) {
  9. this.currentTime = d;
  10. }
  11. constructor(h: number, m: number) { }
  12. }

接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。

类静态部分与实例部分的区别

当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。 你会注意到,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误:

  1. interface ClockConstructor {
  2. new (hour: number, minute: number);
  3. }
  4.  
  5. class Clock implements ClockConstructor {
  6. currentTime: Date;
  7. constructor(h: number, m: number) { }
  8. }

这里因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。

因此,我们应该直接操作类的静态部分。 看下面的例子,我们定义了两个接口,ClockConstructor为构造函数所用和ClockInterface为实例方法所用。 为了方便我们定义一个构造函数createClock,它用传入的类型创建实例。

  1. interface ClockConstructor {
  2. new (hour: number, minute: number): ClockInterface;
  3. }
  4. interface ClockInterface {
  5. tick();
  6. }
  7.  
  8. function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
  9. return new ctor(hour, minute);
  10. }
  11.  
  12. class DigitalClock implements ClockInterface {
  13. constructor(h: number, m: number) { }
  14. tick() {
  15. console.log("beep beep");
  16. }
  17. }
  18. class AnalogClock implements ClockInterface {
  19. constructor(h: number, m: number) { }
  20. tick() {
  21. console.log("tick tock");
  22. }
  23. }
  24.  
  25. var digital = createClock(DigitalClock, 12, 17);
  26. var analog = createClock(AnalogClock, 7, 32);

因为createClock的第一个参数是ClockConstructor类型,在createClock(AnalogClock, 12, 17)里,会检查AnalogClock是否符合构造函数签名。

扩展接口

和类一样,接口也可以相互扩展。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。

  1. interface Shape {
  2. color: string;
  3. }
  4.  
  5. interface Square extends Shape {
  6. sideLength: number;
  7. }
  8.  
  9. var square = <Square>{};
  10. square.color = "blue";
  11. square.sideLength = 10;

一个接口可以继承多个接口,创建出多个接口的合成接口。

  1. interface Shape {
  2. color: string;
  3. }
  4.  
  5. interface PenStroke {
  6. penWidth: number;
  7. }
  8.  
  9. interface Square extends Shape, PenStroke {
  10. sideLength: number;
  11. }
  12.  
  13. var square = <Square>{};
  14. square.color = "blue";
  15. square.sideLength = 10;
  16. square.penWidth = 5.0;

混合类型

先前我们提过,接口能够描述JavaScript里丰富的类型。 因为JavaScript其动态灵活的特点,有时你会希望一个对象可以同时具有上面提到的多种类型。

一个例子就是,一个对象可以同时做为函数和对象使用,并带有额外的属性。

  1. interface Counter {
  2. (start: number): string;
  3. interval: number;
  4. reset(): void;
  5. }
  6.  
  7. function getCounter(): Counter {
  8. var counter = <Counter>function (start: number) { };
  9. counter.interval = 123;
  10. counter.reset = function () { };
  11. return counter;
  12. }
  13.  
  14. var c = getCounter();
  15. c(10);
  16. c.reset();
  17. c.interval = 5.0;

在使用JavaScript第三方库的时候,你可能需要像上面那样去完整地定义类型。

接口继承类

当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。

这是很有用的,当你有一个很深层次的继承,但是只想你的代码只是针对拥有特定属性的子类起作用的时候。子类除了继承自基类外与基类没有任何联系。 例:

  1. class Control {
  2. private state: any;
  3. }
  4.  
  5. interface SelectableControl extends Control {
  6. select(): void;
  7. }
  8.  
  9. class Button extends Control {
  10. select() { }
  11. }
  12. class TextBox extends Control {
  13. select() { }
  14. }
  15. class Image extends Control {
  16. }
  17. class Location {
  18. select() { }
  19. }

在上面的例子里,SelectableControl包含了Control的所有成员,包括私有成员state。 因为state是私有成员,所以只能够是Control的子类们才能实现SelectableControl接口。 因为只有Control的子类才能够拥有一个声明于Control的私有成员state,这对私有成员的兼容性是必需的。

Control类内部,是允许通过SelectableControl的实例来访问私有成员state的。 实际上,SelectableControl就像Control一样,并拥有一个select方法。 ButtonTextBox类是SelectableControl的子类(类为它们都继承自Control并有select方法),但ImageLocation类并不是这样的。

转载:《TypeScript 中文入门教程》 3、接口的更多相关文章

  1. 转载:TypeScript 简介与《TypeScript 中文入门教程》

    简介 TypeScript是一种由微软开发的自由和开源的编程语言.它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程.安德斯·海尔斯伯格,C#的首席架构 ...

  2. 转载:《TypeScript 中文入门教程》

    缘由 事情是这样的,我想搜索 TypeScript 中文教程,结果在 https://www.baidu.com , https://cn.bing.com ,上都找不到官方的翻译,也没有一个像样的翻 ...

  3. 【转】TypeScript中文入门教程

    目录 虽然我是转载的,但看在Copy这么多文章也是很幸苦的好吧,我罗列一个目录. 转载:<TypeScript 中文入门教程> 17.注解 (2015-12-03 11:36) 转载:&l ...

  4. 《TypeScript 中文入门教程》

    转载:<TypeScript 中文入门教程> 17.注解 (2015-12-03 11:36) 转载:<TypeScript 中文入门教程> 16.Symbols (2015- ...

  5. 转载:《TypeScript 中文入门教程》 14、输入.d.ts文件

    版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 当使用外部JavaScript库或新的宿主API时,你需要一个声明文件(.d.t ...

  6. 转载:《TypeScript 中文入门教程》 13、类型兼容性

    版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 TypeScript里的类型兼容性基于结构子类型的. 结构类型是只一种只使用其成 ...

  7. 转载:《TypeScript 中文入门教程》 11、声明合并

    版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 TypeScript有一些独特的概念,有的是因为我们需要描述JavaScript ...

  8. 转载:《TypeScript 中文入门教程》 10、混入

    版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 除了传统的面向对象继承方式,还流行一种通过可重用组件创建类的方式,就是联合另一个 ...

  9. 转载:《TypeScript 中文入门教程》 9、泛型

    版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性. 组件不 ...

随机推荐

  1. 辛巴学院-Unity-剑英的c#提高篇(一)主循环

    这是测试版 辛巴学院:正大光明的不务正业. 最近刚刚离开了我服务了三年多的公司,因为一个无数次碰到的老问题,没钱了. 之前不知道做什么好的时候,机缘巧合之下和哒嗒网络的吴总聊了一下,发现了vr gam ...

  2. 修改input框默认黄色背景

    input:-webkit-autofill, textarea:-webkit-autofill, select:-webkit-autofill { -webkit-box-shadow: 0 0 ...

  3. Box Model,边距折叠,内联和块标签,CSSReset

    一.盒子模型(Box Model) 1.1.宽度测试 1.2.溢出测试 1.3.box-sizing属性 1.4.利用CSS画图 二.边距折叠 2.1.概要 2.2.垂直方向外边距合并计算 三.内联与 ...

  4. SSIS 数据类型和类型转换

    在进行ETL开发时,数据类型(Data Type)是最基础的,但也容易被忽略,楼主使用的SQL Server 版本是2012,用此博文记录,常用的SSIS数据类型和TSQL数据类型的映射.SSIS的数 ...

  5. jQuery 2.0.3 源码分析 回溯魔法 end()和pushStack()

    了解了jQuery对DOM进行遍历背后的工作机制,可以在编写代码时有意识地避免一些不必要的重复操作,从而提升代码的性能 从这章开始慢慢插入jQuery内部一系列工具方法的实现 关于jQuery对象的包 ...

  6. 【原创】开源Math.NET基础数学类库使用(15)C#计算矩阵行列式

                   本博客所有文章分类的总目录:[总目录]本博客博文总目录-实时更新  开源Math.NET基础数学类库使用总目录:[目录]开源Math.NET基础数学类库使用总目录 上个月 ...

  7. Web APi入门之Self-Host寄宿及路由原理(二)

    前言 刚开始表面上感觉Web API内容似乎没什么,也就是返回JSON数据,事实上远非我所想,不去研究不知道,其中的水还是比较深,那又如何,一步一个脚印来学习都将迎刃而解. Self-Host 我们知 ...

  8. 把DATATABLE,DS中的内容用HTML的方式显示

    前几天,在搞一个数据显示的时候,因为是不固定的列的,所以需要动态创建列,这里面就运用一下,直接把数据库的Table显示在Html上,有两种方法,但是都有相应的缺点,第一个,如果内容太多,长度不好控制, ...

  9. 编写简单的ramdisk(有请求队列)

    前言 前面用无请求队列实现的ramdisk的驱动程序虽然申请了请求队列,但实际上没用上,因为ramdisk不像实际的磁盘访问速度慢需要缓存,ramdisk之间使用内存空间,所以就没用请求队列了.本文将 ...

  10. 构建自己的PHP框架--定义ORM的接口

    在上一篇博客中,我们抽象出了Controller的基类,实现了页面的渲染和返回JSON字符串的功能. 那作为一个框架,我们现在还缺少什么?是的,大家应该已经注意到了,我们在这之前从来没有连接过数据库, ...