TypeScript
- 属性接口
- 函数类型接口
- 可索引接口
- 类类型接口
- 接口扩展
在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范。在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,它不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。Typescript中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。
属性接口
首先,如果我们想自定义方法来实现对json数据的约束:
function printLabel(labelInfo:{label:string}):void{
console.log("printLabel");
}
// 现在还不满足重用性
function printLabel2(labelInfo:{label:string}):void{
console.log("printLabel2");
}
// printLabel("李四"); // 错误写法
// printLabel({name:"李四"}); // 错误写法
printLabel({label:"李四"}); // 正确写法
这么操作很不方便。如果对象中存储很多数据,这么做约束太不灵活了。并且,现在也不满足重用性,如果其它函数也想用它,就得再次声明。
接口就可以帮我们完成这项工作。
接口就是行为和动作的规范,并能对批量方法传入的参数进行约束。接口的关键字:interface
// 传入对象的约束:属性接口
interface FullName {
// 约束我们一定要传入以下参数,并且注意数据类型
firstName: string
secondName: string
}
function printName(name:FullName):void{
console.log(name.firstName + name.secondName);
}
printName({
firstName: "李",
secondName: "四"
})
Webstorm还很贴心的为我们做了标记:
那么如果我们不按照约束可以吗:
// 传入对象的约束:属性接口
interface FullName {
// 约束我们一定要传入以下参数,并且注意数据类型
firstName: string
secondName: string
}
function printName(name:FullName):void{
console.log(name.firstName + name.secondName);
}
printName({
firstName: "李",
secondName: "四",
age: 20
})
此时我们可以发现,ts会报错说,接口中不存在这样的一个参数:
但是!页面是肯定可以输出age的内容的。看一下js中的内容就明白了。
虽然在ts中,这么传递参数会报错,但其实有一种可以越过检测的方式:
// 传入对象的约束:属性接口
interface FullName {
// 约束我们一定要传入以下参数,并且注意数据类型
firstName: string
secondName: string
}
function printName(name:FullName):void{
console.log(name.firstName + name.secondName);
// 虽然不报错,但是在ts中使用age,依然会报错
console.log(name.age);
}
let obj = {
age: 20,
firstName: "李",
secondName: "四"
}
// 这么传参,虽然age不存在接口中,但是不会报错
printName(obj)
此时,我们可以发现,在传递参数时已经不会报错了,但是,如果我们想使用age,那肯定会报错了,因为在接口中不存在。
同样的,在页面中肯定能输出age的数据。
那如果我们现在想传递这个age参数,一种方法就是在接口中直接定义了。那如果有的函数想用age,有的函数不想用age,该怎么做?
我们也可以使用可选参数:
interface FullName {
firstName: string
secondName: string
age?:number
}
function printName(name:FullName):void{
console.log(name.firstName + name.secondName);
}
function printAge(info:FullName):void{
console.log(info.firstName + info.secondName + info.age);
}
printName({
firstName: "李",
secondName: "四"
})
// 传递参数没有顺序
printAge({
age: 20,
secondName: "四",
firstName: "李"
})
这样无论传参不传参age都不会报错了。
除此以外,还有一种定义参数的方式:
interface FullName {
firstName: string
secondName: string
age?:number
// 参数名是string类型,可以给它一个any类型数据
[propname:string]:any
}
function printName(name:FullName):void{
console.log(name.sex);
}
printName({
firstName: "李",
secondName: "四",
sex: "男"
})
此时,我们就可以传入一个比较随意的参数了。
最后还是要说,虽然我们可以不管ts报错信息,不管接口规范,它在页面中也能展示信息,但既然选择使用ts,那显然这些规范是必须遵守的。
实例:
interface Config {
type: string;
url: string;
data?:string;
dataType: string;
}
// 原生js封装ajax,不支持ie6
function ajax(config:Config){
let xhr = new XMLHttpRequest();
xhr.open(config.type,config.url, true);
xhr.send(config.data);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
console.log('成功');
if(config.dataType == 'json'){
JSON.parse(xhr.responseText);
}else{
console.log(xhr.responseText)
}
}
}
}
ajax({
type: 'get',
url: '', //填写相关api接口
dataType: 'json',
data: 'name=zhangsan'
})
函数类型接口
函数类型接口:对方法传入的参数以及返回值进行约束。
interface FullName {
say():string
}
function printName(name:FullName):void{
console.log(name.say())
}
printName({
say(){
return "我是李四"
}
})
// 匿名函数类型接口
interface encrypt {
(key:string,value:string):string;
}
// 一定要遵守接口约束
let md5:encrypt = function(key:string,value:string):string{
// 模拟操作
return key+value;
}
console.log(md5("name","李四"))
对于属性接口,毕竟可以对批量传参进行约束,看起来还是有用的。那看到函数类型接口,确实会有人觉得很没用,因为函数本身就可以对参数和返回值进行约束。之前已经说过,接口也是一种标准,确实函数本身也能对这些内容进行限制,但是如果接触到了项目就会明白,有一个相关说明和规范是多么重要。并且,如果接口中设计的内容再多一些,那为了满足重用性,使用这种方式也是必然。
(当然,一般情况下,公司项目组会提供相关接口文档api)
现在已经接触到了三种规范:接口、多态、抽象类。
可索引接口
可索引接口:对数组、对象的约束(不常用)。
// 可索引接口:对数组的约束
interface UserArr{
[index:number]:string;
}
let arr:UserArr = ['a','b','c'];
console.log(arr[0]);
// 可索引接口:对对象的约束
interface UserObj{
[index:string]:string;
}
let arr1:UserObj = {name: '李四'};
console.log(arr1.name);
类类型接口
类类型接口:对类的约束,和抽象类很相似。
interface Animal{
name:string;
eat(str:string):void;
}
// 实现接口用关键词implements
class Dog implements Animal{
name:string;
constructor(name:string) {
this.name = name;
}
// 必须实现方法,但是参数和数据类型不必相同
eat():string{
console.log(this.name + '吃肉')
return this.name;
}
}
let d = new Dog('旺财');
console.log(d.eat());
接口扩展
接口扩展:接口可以继承接口。
interface Animal{
eat():void;
}
interface Person extends Animal{
work():void;
}
// 实现接口用关键词implements
class Web implements Person{
name:string;
constructor(name:string) {
this.name = name;
}
// 不仅要实现相应接口中的属性和方法,
// 还要实现接口继承的接口中的属性和方法,
// 否则报错
eat():void{
console.log(this.name + '吃饭');
}
work():void{
console.log(this.name + '工作');
}
}
let my = new Web('李四');
my.eat();
my.work();
类可以在实现接口的同时继承父类。
interface Animal{
eat():void;
}
interface Person extends Animal{
work():void;
}
class Programmer{
name:string;
constructor(name:string) {
this.name = name;
}
coding(code:string){
console.log(this.name + code)
}
}
class Web extends Programmer implements Person{
constructor(name:string) {
super(name)
}
eat():void{
console.log(this.name + '吃饭');
}
work():void{
console.log(this.name + '工作');
}
}
let my = new Web('李四')
my.coding('写ts代码')
到此为止,已经可以看出,接口和抽象类的区别:
1、抽象类需要用abstract关键词,接口需要用interface关键词;
2、抽象类和接口都是其它类的定义规范,不能直接被实例化;
3、类想使用抽象类,需要用extends继承抽象类。类想使用接口,需要用implements实现接口。除此以外,接口也可以使用接口,需要用extends继承接口。
4、抽象方法只能存放在抽象类中,它不包含具体实现并且必须在派生类中实现,而且严格遵守传入参数数量和数据类型。抽象类中也可以有非抽象方法,在继承抽象类的子类中,不需要一定去实现非抽象方法。而接口中的所有属性和方法都必须实现,但参数和数据类型不用严格对照。