转载:《TypeScript 中文入门教程》 3、接口
版权
文章转载自:https://github.com/zhongsp
建议您直接跳转到上面的网址查看最新版本。
介绍
TypeScript的核心原则之一是对值所具有的shape进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
接口初探
下面通过一个简单示例来观察接口是如何工作的:
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
} var myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
类型检查器会查看printLabel
的调用。 printLabel
有一个参数,并要求这个对象参数有一个名为label
类型为string
的属性。 需要注意的是,我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配。
下面我们重写上面的例子,这次使用接口来描述:必须包含一个label
属性且类型为string
:
interface LabelledValue {
label: string;
} function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
} var myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
LabelledValue
接口就好比一个名字,用来描述上面例子里的要求。 它代表了有一个label
属性且类型为string
的对象。 需要注意的是,我们在这里并不能像在其它语言里一样,说传给printLabel
的对象实现了这个接口。我们只会去关注值的外形。 只要传入的对象满足上面提到的必要条件,那么它就是被允许的。
还有一点值得提的是,类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。
可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 可选属性在应用“option bags”模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。
下面是应用了“option bags”的例子:
interface SquareConfig {
color?: string;
width?: number;
} function createSquare(config: SquareConfig): {color: string; area: number} {
var newSquare = {color: "white", area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
} var mySquare = createSquare({color: "black"});
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?
符号。
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。 比如,我们故意将createSquare
里的color
属性名拼错,就会得到一个错误提示:
interface SquareConfig {
color?: string;
width?: number;
} function createSquare(config: SquareConfig): {color: string; area: number} {
var newSquare = {color: "white", area: 100};
if (config.color) {
// Error: Property 'collor' does not exist on type 'SquareConfig'
newSquare.color = config.collor; // Type-checker can catch the mistyped name here
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
} var mySquare = createSquare({color: "black"});
函数类型
接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
interface SearchFunc {
(source: string, subString: string): boolean;
}
这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。
var mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
var result = source.search(subString);
if (result == -1) {
return false;
}
else {
return true;
}
}
对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。 比如,我们使用下面的代码重写上面的例子:
var mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
var result = src.search(sub);
if (result == -1) {
return false;
}
else {
return true;
}
}
函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。 如果你不想指定类型,Typescript的类型系统会推断出参数类型,因为函数直接赋值给了SearchFunc
类型变量。 函数的返回值类型是通过其返回值推断出来的(此例是false
和true
)。 如果让这个函数返回数字或字符串,类型检查器会警告我们函数的返回值类型与SearchFunc
接口中的定义不匹配。
var mySearch: SearchFunc;
mySearch = function(src, sub) {
var result = src.search(sub);
if (result == -1) {
return false;
}
else {
return true;
}
}
数组类型
与使用接口描述函数类型差不多,我们也可以描述数组类型。 数组类型具有一个index
类型表示索引的类型,还有一个相应的返回值类型表示通过索引得到的元素的类型。
interface StringArray {
[index: number]: string;
} var myArray: StringArray;
myArray = ["Bob", "Fred"];
支持两种索引类型:string和number。 数组可以同时使用这两种索引类型,但是有一个限制,数字索引返回值的类型必须是字符串索引返回值的类型的子类型。
索引签名能够很好的描述数组和dictionary
模式,它们也要求所有属性要与返回值类型相匹配。 因为字符串索引表明obj.property
和obj["property"]
两种形式都可以。 下面的例子里,name
的类型与字符串索引类型不匹配,所以类型检查器给出一个错误提示:
interface NumberDictionary {
[index: string]: number;
length: number; // 可以,length是number类型
name: string // 错误,`name`的类型不是索引类型的子类型
}
类类型
实现接口
与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约。
interface ClockInterface {
currentTime: Date;
} class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) { }
}
你也可以在接口中描述一个方法,在类里实现它,如同下面的setTime
方法一样:
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
} class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。
类静态部分与实例部分的区别
当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。 你会注意到,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误:
interface ClockConstructor {
new (hour: number, minute: number);
} class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number) { }
}
这里因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。
因此,我们应该直接操作类的静态部分。 看下面的例子,我们定义了两个接口,ClockConstructor
为构造函数所用和ClockInterface
为实例方法所用。 为了方便我们定义一个构造函数createClock
,它用传入的类型创建实例。
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick();
} function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute);
} class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
} var digital = createClock(DigitalClock, 12, 17);
var analog = createClock(AnalogClock, 7, 32);
因为createClock
的第一个参数是ClockConstructor
类型,在createClock(AnalogClock, 12, 17)
里,会检查AnalogClock
是否符合构造函数签名。
扩展接口
和类一样,接口也可以相互扩展。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
interface Shape {
color: string;
} interface Square extends Shape {
sideLength: number;
} var square = <Square>{};
square.color = "blue";
square.sideLength = 10;
一个接口可以继承多个接口,创建出多个接口的合成接口。
interface Shape {
color: string;
} interface PenStroke {
penWidth: number;
} interface Square extends Shape, PenStroke {
sideLength: number;
} var square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
混合类型
先前我们提过,接口能够描述JavaScript里丰富的类型。 因为JavaScript其动态灵活的特点,有时你会希望一个对象可以同时具有上面提到的多种类型。
一个例子就是,一个对象可以同时做为函数和对象使用,并带有额外的属性。
interface Counter {
(start: number): string;
interval: number;
reset(): void;
} function getCounter(): Counter {
var counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
} var c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
在使用JavaScript第三方库的时候,你可能需要像上面那样去完整地定义类型。
接口继承类
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
这是很有用的,当你有一个很深层次的继承,但是只想你的代码只是针对拥有特定属性的子类起作用的时候。子类除了继承自基类外与基类没有任何联系。 例:
class Control {
private state: any;
} interface SelectableControl extends Control {
select(): void;
} class Button extends Control {
select() { }
}
class TextBox extends Control {
select() { }
}
class Image extends Control {
}
class Location {
select() { }
}
在上面的例子里,SelectableControl
包含了Control
的所有成员,包括私有成员state
。 因为state
是私有成员,所以只能够是Control
的子类们才能实现SelectableControl
接口。 因为只有Control
的子类才能够拥有一个声明于Control
的私有成员state
,这对私有成员的兼容性是必需的。
在Control
类内部,是允许通过SelectableControl
的实例来访问私有成员state
的。 实际上,SelectableControl
就像Control
一样,并拥有一个select
方法。 Button
和TextBox
类是SelectableControl
的子类(类为它们都继承自Control
并有select
方法),但Image
和Location
类并不是这样的。
转载:《TypeScript 中文入门教程》 3、接口的更多相关文章
- 转载:TypeScript 简介与《TypeScript 中文入门教程》
简介 TypeScript是一种由微软开发的自由和开源的编程语言.它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程.安德斯·海尔斯伯格,C#的首席架构 ...
- 转载:《TypeScript 中文入门教程》
缘由 事情是这样的,我想搜索 TypeScript 中文教程,结果在 https://www.baidu.com , https://cn.bing.com ,上都找不到官方的翻译,也没有一个像样的翻 ...
- 【转】TypeScript中文入门教程
目录 虽然我是转载的,但看在Copy这么多文章也是很幸苦的好吧,我罗列一个目录. 转载:<TypeScript 中文入门教程> 17.注解 (2015-12-03 11:36) 转载:&l ...
- 《TypeScript 中文入门教程》
转载:<TypeScript 中文入门教程> 17.注解 (2015-12-03 11:36) 转载:<TypeScript 中文入门教程> 16.Symbols (2015- ...
- 转载:《TypeScript 中文入门教程》 14、输入.d.ts文件
版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 当使用外部JavaScript库或新的宿主API时,你需要一个声明文件(.d.t ...
- 转载:《TypeScript 中文入门教程》 13、类型兼容性
版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 TypeScript里的类型兼容性基于结构子类型的. 结构类型是只一种只使用其成 ...
- 转载:《TypeScript 中文入门教程》 11、声明合并
版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 TypeScript有一些独特的概念,有的是因为我们需要描述JavaScript ...
- 转载:《TypeScript 中文入门教程》 10、混入
版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 除了传统的面向对象继承方式,还流行一种通过可重用组件创建类的方式,就是联合另一个 ...
- 转载:《TypeScript 中文入门教程》 9、泛型
版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性. 组件不 ...
随机推荐
- 跟vczh看实例学编译原理——二:实现Tinymoe的词法分析
文章中引用的代码均来自https://github.com/vczh/tinymoe. 实现Tinymoe的第一步自然是一个词法分析器.词法分析其所作的事情很简单,就是把一份代码分割成若干个tok ...
- WCF Security基本概念(转载)
WCF Security 主要包括 "Transfer Security"."Access Control"."Auditing" 几个部分 ...
- 部分安卓手机微信浏览器中使用XMLHttpRequest 2上传图片显示字节数为0的解决办法
前端JS中使用XMLHttpRequest 2上传图片到服务器,PC端和大部分手机上都正常,但在少部分安卓手机上上传失败,服务器上查看图片,显示字节数为0.下面是上传图片的核心代码: HTML < ...
- SpringMVC执行流程
Spring mvc是一个机遇DispacterServlet的MVC框架,每一个请求都是通过DispacterServlet派发的,具体流程如下: 1.web容器加载后,用户向服务器发送请求,前端控 ...
- 应用Web.Config配置网站
1.配置数据库连接 在ASP.NET中配置数据库连接的两种方式: appSettings和connectionStrings 命名空间: using System.Configuration; 1)a ...
- Performance Monitor2:Peformance Counter
Performance Counter 是量化系统状态或活动的一个数值,Windows Performance Monitor在一定时间间隔内(默认的取样间隔是15s)获取Performance Co ...
- Utility1:Overview
Utility 是利用,使用的意思,utilization是指使用效率,利用率的意思. SQL Sever 内置 Utility Feature,便于集中监控Server关键资源(CPU和Disk)的 ...
- jQuery 2.0.3 源码分析Sizzle引擎 - 超级匹配
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 通过Expr.find[ type ]我们找出选择器最右边的最终seed种子合集 通过Sizzle.compile函数编译器 ...
- socket编程
一.socket地址的数据类型及相关函数 socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4.IPv6.UNIX Domain Socket.然而各种网络协议的地址格式并 ...
- Android之TabActivity的使用
TabActivity实现多页显示效果 由于手机屏幕有限,所以我们要尽量充分利用屏幕资源.在我们的应用程序中通常有多个Activity,而且会经常切换显示,这样我们就可以用TabActivity来显示 ...