接口的作用:在面向对象OOP的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。
接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。
OOP : Object Oriented Programming
定义标准。
print的参数是一个对象, 但是必须有label这个属性
ts中自定义方法传入参数,对json进行约束
function print(labelObj:{
label:string }){
console.log( labelObj.label );
}
// 跳过这些检查的方式, 它就是将这个对象赋值给一个另一个变量:
// myObj 不会经过额外属性检查,所以编译器不会报错。
print({
name:'张三'}); //错误的写法
print({
label:'你好'}); //正确的写法
print({
label:"Size test",
name:'sss'
} as {
label:string });
注意下面写法会提示报错:
print({
label:“Size test”,
name:‘laney’
});
将对象字面量作为参数传递的时候, 对象字面量会被特殊对待而且会经过"额外属性检查"。
如果一个对象字面量存在任何"目标类型"不包含的属性时,你会得到一个错误。
如何跳过这种检查
官网上给了三种方式:
第一种使用类型断言:
print({
label:“Size test”,
name:‘laney’
} as { label:string });
第二种添加字符串索引签名:
function print(labelObj:{ label:string ,[propName: string]: any;}){
console.log(labelObj);
}
print({
label:“Size test”,
name:‘laney’
});
它就是将这个对象赋值给一个另一个变量:
myObj 不会经过额外属性检查,所以编译器不会报错。
let myObj={ size:10,label:“Size 10 Object” };
print(myObj);
这是一个简单的函数,在调用print时,会检查传入参数的类型,并且只检查那些必需属性是否存在,并且类型是否匹配。下面利用接口重写上面的例子
//接口
interface labelValue{
label:string;
size:number;
}
//type 别名
type labelObj = {
label?:string;
size:number;
}
function print(labelObj:labelValue){
console.log( 'labelObj.label' );
console.log( labelObj );
}
let myObj2={
size:10,label:"Size 10 Object",name:'Alice' };
print(myObj2);
在参数类型检查的时候,会发现参数遵循的是接口labelValue的规范,然后就回去检查是不是符合接口所描述的规范。
接口就相当于是一个约束,让大家都遵循。
//接口:行为和动作的规范,对批量方法进行约束
//就是传入对象的约束 属性接口
类型断言 Type Assertion
定义: 可以用来手动指定一个值的类型
语法
function getLength(x:number|string):number{
if((<string>x).length) {
return (<string>x).length
} else {
return x.toString().length
}
}
function getLength(x:number|string):number{
if((x as string).length){
return (x as string).length
} else {
return x.toString().length
}
}
类型断言并非是类型转换,断言一个联合类型中不存在的类型会报错!
function wrong(x:number|string):boolean{
return <boolean>x // 报错!
}
interface labelValue{
label:string;
size:number;
}
function print(labelObj:labelValue){
console.log( 'labelObj.label' );
console.log( labelObj );
}
上面1.2 使用接口 的例子,如果这么这么传值会报错
print({
label:‘hello’,
size:30,
name:‘laney’
});
如何绕过额外属性检查,官方给了3种方式
解决方式:
// 第一种使用类型断言
print({
label1:'hello',
size:30
} as labelValue);
// 第二种添加字符串索引签名
/* interface labelValue{
label?:string;
size:number;
[propName: string]: any; //+
} */
//将字面量赋值给另外一个变量
var labK = {
label1:'hello',
size:30
};
print(labK);
interface FullName{
firstName:string; //注意;结束
secondName:string;
// secondName?:string; //可选属性,可传可不传
hello(str:string):string
}
function printName(name:FullName){
// 必须传入对象 firstName secondName
console.log(name.firstName+'--'+name.secondName);
console.log(name)
}
// printName('1213'); //错误
/*传入的参数必须包含 firstName secondName*/
var obj={
firstName:'张',
secondName:'三',
hello(str:string):string{
return obj.firstName+obj.secondName;
}
};
printName(obj)
//如果secondName是可选参数,传参的时候可加以不加
// printName({
// firstName:'firstName'
// })
/*
$.ajax({
type: "GET",
url: "test.json",
data: {username:$("#username").val(), content:$("#content").val()},
dataType: "json"
});
*/
interface Config{
type:string;
url:string;
data?:string;
dataType:string;
}
//原生js封装的ajax
function ajax(config:Config){
var 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('chengong');
if(config.dataType=='json'){
console.log(JSON.parse(xhr.responseText));
}else{
console.log(xhr.responseText)
}
}
}
}
ajax({
type:'get',
data:'name=zhangsan',
url:'http://localhost:3000/test', //api
dataType:'json'
})
ts中函数型接口,非常类似于java、c#中使用lambda表达式传入匿名函数。
因为对象中仅包含一个函数,这个对象的全部意义也仅在于那个可被外部调用的函数,故而称之为函数型接口。
加密的函数类型接口
函数类型的接口:对方法传入的参数以及返回值进行约束 批量约束
interface encrypt{
(key:string,value:string):string;
}
var md5:encrypt=function(key:string,value:string):string{
//模拟操作
return key+value;
}
console.log(md5('name','zhangsan'));
var sha1:encrypt=function(key:string,value:string):string{
//模拟操作
return key+'----'+value;
}
console.log(sha1('name','lisi'));
//对两个数进行运算得到另一个数 函数型接口
interface CalcTwo{
(a:number,b:number):number;
}
/**
* 计算数组被某种算法运算的结果
* @param {number[]} arr 数组
* @param {CalcTwo} calc 用户指定的算法函数
* @param {number} initVal 传入初始值
* @returns {number} 得到最终运算结果
**/
function calcArr(arr:number[],calc:CalcTwo,initVal:number):number{
var result=initVal;
arr.forEach((value)=>{
result = calc(result,value);
});
return result;
}
使用:
var arr:number[]=[1,3,5,7,9];
var add=function(a:number,b:number):number{
return a+b;
};
var multiply=function(a:number,b:number):number{
return a*b;
};
console.log("相加",calcArr(arr, add, 0));//25
console.log("相乘",calcArr(arr, multiply, 1));//945
//或者直接传入一个匿名函数亦可
var s1=calcArr(arr,function(a,b){
return a*b;
},1);
console.log("s1",s1);//945
var s2=calcArr(arr,function (a,b) {
return (a+1)*(b+1);
},1);
console.log("s2",s2);//10170
(不常用)
可索引接口:数组、对象的约束 (不常用)
ts定义数组的方式
/*
var arr:number[]=[2342,235325]
var arr1:Array=[‘111’,‘222’]
*/
interface UserArr{
[index:number]:string
}
var arr:UserArr=['aaa','bbb'];
console.log(arr[0]);
var arr:UserArr=[123,'bbb']; /*错误*/
console.log(arr[0]);
interface UserObj{
[index:string]:string
}
var arr:UserObj={
name:'张三'};
类类型接口用来规范一个类的内容。示例代码如下
用的多,和抽象类有点相似,
类实现接口本质上 即类遵循接口的约束,接口里面写了多少个函数、参数,实现的类里面也要写相同的函数、参数。
interface FullName {
username: string;
}
//Person这个类需要遵循接口FullName的约束
class Person implements FullName {
username: string;
constructor(n: string) {
this.username = n;
}
}
可以在接口中描述一个方法,并在类里具体实现它的功能,如同下面的setName方法一样:
interface FullName {
username: string;
setName(name: string): void;
getName():void;
}
class Person implements FullName {
username: string;
constructor(name: string) {
this.username = name;
}
setName(str: string) {
this.username = str;
}
getName(){
console.log(this.username)
}
}
var p1 = new Person('Alice');
p1.setName('tony');
p1.getName();
下面这个不演示,自行学习
interface Animal{
name:string;
eat(str:string):void;
}
class Dog implements Animal{
name:string;
constructor(name:string){
this.name=name;
}
eat(){
console.log(this.name+'吃粮食')
}
}
var d=new Dog('小黑');
d.eat();
class Cat implements Animal{
name:string;
constructor(name:string){
this.name=name;
}
eat(food:string){
console.log(this.name+'吃'+food);
}
}
var c=new Cat('小花');
c.eat('老鼠');
interface ClockInterface{
currentTime:Date;
getTime(d:Date):any;
}
class Clock implements ClockInterface{
currentTime:Date;
constructor(){
this.currentTime = new Date()
}
getTime(){
console.log("123");
console.log(this.currentTime)
}
}
let clock1=new Clock();
clock1.getTime();
类实现接口本质上也是一样的,即类遵循接口的约束,接口里面写了多少个函数、参数,实现的类里面也要写相同的函数、参数。
接口继承就是说接口可以通过其他接口来扩展自己。
Typescript 允许接口继承多个接口。
继承使用关键字 extends。
interface Animal {
eat(): void;
}
// Person 接口继承了 接口Animal
// 单接口继承,只继承一个接口
interface Person extends Animal {
work(): void;
}
//类实现接口 WebFront类实现接口Person
class WebFront implements Person {
public name: string;
constructor(name: string) {
this.name = name;
}
eat() {
console.log(this.name + "喜欢吃馒头");
}
work() {
console.log(this.name + "写代码");
}
}
var w = new WebFront("小李");
w.eat();
接口的扩展就是给多添加了一些约束。一个接口可以扩展多个接口,当一个接口扩展另一个接口,也继承了该接口的约束。
interface Animal {
eat(): void;
}
interface Person extends Animal {
work(): void;
}
class Programmer {
public name: string;
constructor(name: string) {
this.name = name;
}
coding(code: string) {
console.log(this.name + code);
}
}
//类继承类并实现接口: WebFront继承类Programmer 并实现接口Person
class WebFront extends Programmer implements Person {
constructor(name: string) {
super(name);
}
eat() {
console.log(this.name + "喜欢吃馒头");
}
work() {
console.log(this.name + "写代码");
}
}
var w = new WebFront("小李");
w.eat();
w.coding("写ts代码");
class Point{
x:number;
y:number;
constructor(x:number,y:number){
this.x = x;
this.y = y;
}
log(){
console.log('123456');
}
}
interface Point3d extends Point{
z:number;
}
var point3d:Point3d={
x:1,
y:2,
z:3,
log(){
console.log('7777');
}
}
point3d.log();//7777
官方解释:当接口继承了一个类类型时,它会继承类的成员但不包括其实现。
也就是说,接口继承类值继承了它的约束条件,具体的值并不继承。
总结
接口扩展(继承)接口
interfaceA extends interfaceB
类实现接口
classA implements interfaceA
接口继承类
interfaceB extends classB
类扩展(继承)类
classA extends classB
有人肯分不清上面时候用extends,什么时候用implements。
记住一句话,只要涉及到继承就是extends.
namespace InterfaceAndType {
// 1. Objects / Functions
// 两者都可以用来描述对象或函数的类型,但是语法不同。
// Interface
// interface Point {
// x: number;
// y: number;
// }
// interface SetPoint {
// (x: number, y: number): void;
// }
// Type alias
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
// 2. Other Types 类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组。Interface不行
// primitive 基本类型
type Name = string;
// object
type PartialPointX = {
x: number; };
type PartialPointY = {
y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
// dom
let div = document.createElement('div');
type B = typeof div;
// 3. Extend
// 两者都可以扩展,但是语法又有所不同。此外,请注意接口和类型别名不是互斥的。接口可以扩展类型别名,反之亦然。
// Interface extends interface
interface PointX {
x: number; }
interface PointY extends PointX {
y: number; }
// Type alias extends type alias
type PointA = {
x: number; };
type PointB = PointA & {
y: number; };
// Interface extends type alias
type SizeA = {
x: number; };
interface SizeB extends SizeA {
y: number; }
// Type alias extends interface
interface SizeX {
x: number; }
type SizeY = SizeX & {
y: number; };
// 4. class Implements
// 类可以以相同的方式实现接口或类型别名。但是请注意,类和接口被认为是静态的。因此,它们不能实现/扩展命名联合类型的类型别名。
interface PointV {
x: number;
y: number;
}
class SomePoint implements PointV {
x:number;
y:number;
constructor(x:number,y:number){
this.x = x;
this.y = y;
}
}
type PointV2 = {
x: number;
y: number;
};
class SomePoint2 implements PointV2 {
x:number;
y:number;
constructor(x:number,y:number){
this.x = x;
this.y = y;
}
}
type PointXX = {
x: number; } | {
y: number; };
// FIXME: can not implement a union type
// class SomePoint3 implements PointXX {
// x:number;
// y:number;
// constructor(x:number,y:number){
// this.x = x;
// this.y = y;
// }
// }
}
总结
interface 和 type 很像,很多场景,两者都能使用。但也有细微的差别:
类型:对象、函数两者都适用,但是 type 可以用于基础类型、联合类型、元祖。
同名合并:interface 支持,type 不支持。
计算属性:type 支持, interface 不支持。
总的来说,公共的用 interface 实现,不能用 interface 实现的再用 type 实现。主要是一个项目最好保持一致。