接口是对值所语句的接口进行检查,被称做“鸭式辨型法”或“结构性子类型化。

举例说明接口是如何工作的

  1. interface obj {
  2. name: string
  3. age: number
  4. }
  5. function teachers(params:obj) {
  6. console.log(params)
  7. }
  8. let teacher = teachers({name:'li',age:30})
  9. obj --> 定义了一个对象,包含 nameage.
  10. teachers -->定义一个函数,形参定义为obj --> {name: string,age: number}
  11. teachers执行时必须传入一个对象{name:'li',age:30} 这种格式得才能被编译器认可,否则报错
  12. 另外,类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以,
  13. 也就是说只要存在规定的属性(如上文的nameage)即可,在增加无用参数也可以,比如:
  14. let teacher = teachers({name:'li',age:30,sex:''})也不会有错。

可选属性

接口中不必存在得属性可以用?来表示

  1. interface obj {
  2. name?: string
  3. age: number
  4. }
  5. 在属性名后添加?就表示他是一个可选属性,传入不传入都可以。

可选属性好处

  • 对可能存在得属性进行预定义
  • 捕获引用不存在的属性时得错误(以上面得obj为例,我传递的属性名为names,则报错)

    只读属性

    只读属性只能在初始化是赋值,而后在修改就会报错!
    1. interface obj {
    2. readonly name: string
    3. readonly age: number
    4. }
    5. let teacher:obj = {name:'1', age:5}
    6. teacher.age =60 //error
    只读数组ReadonlyArray,它与Array相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改.
    1. let a: number[] = [1, 2, 3, 4];
    2. let ro: ReadonlyArray<number> = a;
    3. ro[0] = 12; // error!
    4. ro.push(5); // error!
    5. ro.length = 100; // error!
    6. a = ro; // error!
    ReadonlyArray 保证了数组在初始化后任何属性不可变,也不能赋值给任何属性,如果想要重新赋值可以利用 断言重写如下所示:
    1. a = ro as number[] //ok
    2. a = <number[]>ro //ok

    额外检查属性

    请看下面的例子:
    1. interface SquareConfig {
    2. color?: string; //可选
    3. width?: number;//可选
    4. }
    5. function createSquare(config: SquareConfig): { color: string; area: number } {
    6. return { color: '', area: 6 }
    7. }
    8. let mySquare = createSquare({ colour: "red", width: 100 ,area:''});
    9. //error “{ colour: string; width: number; area: string; }”的参数不能赋给类型“SquareConfig”的参数。
    10. 对象文字只能指定已知的属性,但“colour”中不存在类型“SquareConfig
    上面出现error原因:对象字面量会被特殊对待而且会经过额外的属性检查,当将他们赋值给变量或者作为参数传递的时候,出现对象字面量(SquareConfig)不包含的属性,就会报错。
    需要绕开这些检查,最简便的方法使用类型断言: ```typescript let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
  1. 第二种检查方式,它就是将这个对象赋值给一个另一个变量,因为 `squareOptions`不会经过额外属性检查,所以编译器不会报错
  2. ```typescript
  3. let squareOptions = { colour: "red", width: 100 };
  4. let mySquare = createSquare(squareOptions);

其实并不应该绕开这些检查,大量额外的属性检查错误是真的bug

函数类型

例子:下面是描述函数类型的

  1. //定义函数类型
  2. interface SearchFunc {
  3. (source: string, subString: string): boolean;
  4. }
  5. 创建函数类型变量,并使用
  6. let mySearch: SearchFunc;
  7. mySearch = function(source: string, subString: string) {
  8. let result = source.search(subString);
  9. return result > -1;
  10. }
  11. 对于函数类型检查,函数的参数名不需要与接口里定义的名字相匹配(值类型匹配即可),可以修改如下
  12. let mySearch: SearchFunc;
  13. mySearch = function(src: string, sub: string): boolean {
  14. let result = src.search(sub);
  15. return result > -1;
  16. }

可索引的类型

索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型 如a[10]或者a[“name”]
下面阐述了什么是可索引类型:

  1. interface StringArray {
  2. [index: number]: string;
  3. } //上面是一个索引为 number类型,值为字符串类型的索引
  4. let myArray: StringArray;
  5. myArray = ["Bob", "Fred"];
  6. let myStr: string = myArray[0];
  7. 这个索引签名表示当用number 去索引StringArray时会得到string类型的返回值

在TypeScript支持两种索引签名:字符串和数字索引,可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型, 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用”100”(一个string)去索引,因此两者需要保持一致。
下面例子说明了上面的观点

  1. class Animal {
  2. name: string;
  3. }
  4. class Dog extends Animal {
  5. breed: string;
  6. }
  7. // 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!
  8. interface NotOkay {
  9. [x: number]: Animal;
  10. [x: string]: Dog;
  11. } // 因为 NotOkay[0] == NotOkay['0']
  12. //这个代表就表示数字索引的值类型必须是字符串索引的子类型,Object 时String的父类型
  13. interface NumberDictionary {
  14. [index: string]: Object;
  15. [index: number]: string;
  16. }

只读索引,防止给索引赋值

  1. interface ReadonlyStringArray {
  2. readonly [index: number]: string;
  3. }

类类型

实现接口,明确强制一个类去符合某种契约

  1. 声明类,并且implements 实现
  2. interface ClockInterface {
  3. currentTime: Date;
  4. setTime(d: Date);
  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. //声明静态类
  2. interface ClockConstructor {
  3. new(hour: number, minute: number): ClockInterface;
  4. }
  5. //声明要实现的函数
  6. interface ClockInterface {
  7. tick(): any;
  8. }
  9. // DigitalClock 实现这个接口 ClockInterface
  10. class DigitalClock implements ClockInterface {
  11. constructor(h: number, m: number) { }
  12. tick() {
  13. console.log("beep beep");
  14. }
  15. }
  16. //声明函数,返回 ClockInterface 类型
  17. function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
  18. return new ctor(hour, minute);
  19. }
  20. let digital = createClock(DigitalClock, 12, 17);

继承接口

和类一样能够继承

  1. interface Shape {
  2. color: string;
  3. }
  4. interface Square extends Shape {
  5. sideLength: number;
  6. }
  7. let square = <Square>{};
  8. square.color = "blue";
  9. square.sideLength = 10;
  10. square.s = 10; //error 类型不存在则报错

可以继承多个接口

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

混合类型

有时你会希望一个对象可以同时具有上面提到的多种类型。
下面的例子就是,一个对象可以同时做为函数和对象使用,并带有额外的属性

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

接口继承类

当接口继承了一个类类型时,他会继承类的成员,但不包含实现,就像接口声明了类成员但是没有提供实现具体一样,接口同样汇集成这个类的private和protected成员,这就意味着当你创建了一个接口继承了一个拥有私有或者收到保护的成员类时,这个接口类型只能呗这个类和其子类所实现

  1. //定义一个类
  2. class Control {
  3. private state: any;
  4. }
  5. 接口继承了这个类
  6. interface SelectableControl extends Control {
  7. select(): void;
  8. }
  9. //Button 要 implements SelectableControl时必须要先继承 Control
  10. 才对,因为SelectableControl继承Contro时同时继承了私有变量state,这个类必须被这个类可子类所实现
  11. class Button extends Control implements SelectableControl {
  12. select() { }
  13. }
  14. class TextBox extends Control {
  15. // select() { }
  16. }
  17. // // 错误:“Image”类型缺少“state”属性。
  18. class Images implements SelectableControl {
  19. select() { }
  20. }

知识总结:

  1. 1.接口只会检查相应的属性存在并且类型也是对的就可以(额外的参数不会检查),并不会检查参数顺序
  2. 2.可选属性的好处:对可能存在得属性进行预定义,捕获引用不存在的属性时得错误
  3. 3.只读属性只能在创建时改变,随后值不可改变,如果想要改变可通过类型断言重写
  4. 4.额外的属性示例。当不确定参数时可使用
  5. interface SquareConfig {
  6. color?: string;
  7. width?: number;
  8. [propName: string]: any;
  9. }
  10. 5.使用接口描述函数时,只需要保持参数名类型和返回值类型一致即可,参数名称可不对应。如果不标注,系统会自动类型推导。
  11. 如下面的例子:
  12. interface SearchFunc {
  13. (source: string, subString: string): boolean;
  14. }
  15. let mySearch: SearchFunc = function(source: string, subString: string):boolean {
  16. let result = source.search(subString);
  17. return result > -1;
  18. }
  19. 6.可索引签名分为两种,字符串和数字,两者可同时使用,但是数字索引的返回值必须是字符串索引返回值类型的子类型,因为当用number去索引时js会把他转化为string去索引,所以需要保持一致。利用只读索引可以防止给索引赋值。
  20. 7. 当用接口定义一个类时,可以用 implements 来实现,接口也可以继承,也可以继承多个,并且还具有混合类类型,(当时想一个对象既可以作为函数又可以作为对象使用时),
  21. 8.接口继承类时同样会继承类的privateprotected成员,当你创建了一个接口继承了一个拥有私有或者受保护的成员的类时,这个接口类型只能被这个类或者其子类所实现(implement