TS系列之介面/類/泛型

weixin_33840661發表於2018-04-01

介面

介面(Interfaces)是一個很重要的概念,它是對行為的抽象,而具體如何行動需要由類(classes)去實現(implements)。
命名一般使用大駝峰法。

interface Person {
    name: string;
    age: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25
};

這樣我們就約束了 tom 的形狀必須和介面 Person 一致。
函式型別

interface SearchFunc {
  (source: string, subString: string): boolean;
}
//對於函式型別的型別檢查來說,函式的引數名不需要與介面裡定義的名字相匹配。 
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
  let result = src.search(sub);
  return result > -1;
}

屬性

可選屬性

interface Person {
    name: string;
    age?: number;
}

let tom: Person = {
    name: 'Tom'
};

可索引屬性

共有支援兩種索引簽名:字串和數字。 可以同時使用兩種型別的索引,但是數字索引的返回值必須是字串索引返回值型別的子型別。 這是因為當使用 number來索引時,JavaScript會將它轉換成string然後再去索引物件。

// string
interface Person {
    name: string;
    age?: number;
    [propName: string]: any;       //[propName: string] 定義了任意屬性取 string 型別的值。
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};
// number
interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];
一旦定義了任意屬性,那麼確定屬性和可選屬性都必須是它的子屬性

只讀屬性

interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}
//報錯
let tom: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

tom.id = 9527;
//報錯
let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

tom.id = 89757;
只讀的約束存在於第一次給物件賦值的時候,而不是第一次給只讀屬性賦值的時候

繼承

介面繼承介面

和類一樣,介面也可以相互繼承。 這讓我們能夠從一個介面裡複製成員到另一個介面裡,可以更靈活地將介面分割到可重用的模組裡。

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
一個介面可以繼承多個介面,建立出多個介面的合成介面。

介面繼承類

當介面繼承了一個類型別時,它會繼承類的成員但不包括其實現。 就好像介面宣告瞭所有類中存在的成員,但並沒有提供具體實現一樣。 介面同樣會繼承到類的private和protected成員。 這意味著當你建立了一個介面繼承了一個擁有私有或受保護的成員的類時,這個介面型別只能被這個類或其子類所實現(implement)。

class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    select() { }
}

// 錯誤:“Image”型別缺少“state”屬性。
class Image implements SelectableControl {
    select() { }
}

在上面的例子裡,SelectableControl包含了Control的所有成員,包括私有成員state。 因為 state是私有成員,所以只能夠是Control的子類們才能實現SelectableControl介面。

屬性和方法

使用 class 定義類,使用 constructor 定義建構函式。
通過 new 生成新例項的時候,會自動呼叫建構函式。

class Animal {
    constructor(name) {
        this.name = name;
    }
    sayHi() {
        return `My name is ${this.name}`;
    }
    eat() {
        return 'happy';
    }
}

let a = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack

繼承

使用 extends 關鍵字實現繼承,子類中使用 super 關鍵字來呼叫父類的建構函式和方法。

class Cat extends Animal {
    constructor(name) {
        super(name);      // 呼叫父類的 constructor(name)
        console.log(this.name);
    }
    // 方法重寫
    sayHi() {             
        return 'Meow, ' + super.sayHi();    // 呼叫父類的 sayHi()
    }
}

let c = new Cat('Tom');   // Tom
c.sayHi();                // Meow, My name is Tom
c.eat();                  //'happy'
父類包含了一個建構函式,它必須呼叫 super(),它會執行子類的建構函式。 而且,在建構函式裡訪問 this的屬性之前,我們一定要呼叫 super()。 這個是TypeScript強制執行的一條重要規則。

存取器

使用 getter 和 setter 可以改變屬性的賦值和讀取行為

class Animal {
    constructor(name) {
        this.name = name;
    }
    get name() {
        return 'Jack';
    }
    set name(value) {
        console.log('setter: ' + value);
    }
}

let a = new Animal('Kitty'); // setter: Kitty
a.name = 'Tom'; // setter: Tom
console.log(a.name); // Jack

類的型別

let a: Animal = new Animal('Jack');

修飾符

static

靜態屬性

class Animal {
    static num = 42;

    constructor() {
        // ...
    }
}
console.log(Animal.num); // 42

靜態方法
使用 static 修飾符修飾的方法稱為靜態方法,它們不需要例項化,而是直接通過類來呼叫

class Animal {
    static isAnimal(a) {
        return a instanceof Animal;
    }
}

let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function

public

public 修飾的屬性或方法是公有的,可以在任何地方被訪問到,預設所有的屬性和方法都是 public 的

class Animal {
    public name;
    public constructor(name) {
        this.name = name;
    }
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom

protected

protected 修飾的屬性或方法是受保護的,它和 private 類似,區別是它在子類中也是允許被訪問的

class Animal {
    protected name;
    public constructor(name) {
        this.name = name;
    }
}

class Cat extends Animal {
    constructor(name) {
        super(name);
        console.log(this.name);
    }
}

private

private 修飾的屬性或方法是私有的,不能在宣告它的類的外部訪問

class Animal {
    private name;
    public constructor(name) {
        this.name = name;
    }
}

let a = new Animal('Jack');
console.log(a.name);          // Jack
a.name = 'Tom';               //Error 

使用 private 修飾的屬性或方法,在子類中也是不允許訪問的

class Animal {
    private name;
    public constructor(name) {
        this.name = name;
    }
}

class Cat extends Animal {
    constructor(name) {
        super(name);
        console.log(this.name);
    }
}         
//Error

readonly

使用 readonly關鍵字將屬性設定為只讀的。 只讀屬性必須在宣告時或建構函式裡被初始化。

class Octopus {
    readonly name: string;
    readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 錯誤! name 是隻讀的.

引數屬性

引數屬性可以方便地讓我們在一個地方定義並初始化一個成員。

class Animal {
    constructor(private name: string) { }
    move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

抽象類

abstract 用於定義抽象類和其中的抽象方法。

  • 抽象類是不允許被例項化的
  • 抽象類中的抽象方法必須被子類實現
abstract class Animal {
    public name;
    public constructor(name) {
        this.name = name;
    }
    public abstract sayHi();
}

let a = new Animal('Jack');
//Error

抽象類中的抽象方法不包含具體實現並且必須在派生類中實現。 抽象方法的語法與介面方法相似。

abstract class Animal {
    public name;
    public constructor(name) {
        this.name = name;
    }
    public abstract sayHi();
}

class Cat extends Animal {
    public sayHi() {
        console.log(`Meow, My name is ${this.name}`);
    }
}

let cat = new Cat('Tom');

裝飾器

裝飾器是一種特殊型別的宣告,它能夠被附加到類宣告,方法, 訪問符,屬性或引數上。 裝飾器使用 @expression這種形式,expression求值後必須為一個函式,它會在執行時被呼叫,被裝飾的宣告資訊做為引數傳入。
多個裝飾器可以同時應用到一個宣告上,就像下面的示例:

書寫在同一行上:
@f @g x

書寫在多行上:
@f
@g
x

在TypeScript裡,當多個裝飾器應用在一個宣告上時會進行如下步驟的操作:

  1. 由上至下依次對裝飾器表示式求值。
  2. 求值的結果會被當作函式,由下至上依次呼叫。

裝飾器求值

類中不同宣告上的裝飾器將按以下規定的順序應用:

  1. 引數裝飾器,然後依次是方法裝飾器,訪問符裝飾器,或屬性裝飾器應用到每個例項成員。
  2. 引數裝飾器,然後依次是方法裝飾器,訪問符裝飾器,或屬性裝飾器應用到每個靜態成員。
  3. 引數裝飾器應用到建構函式。
  4. 類裝飾器應用到類。

類實現介面

實現(implements)是物件導向中的一個重要概念。一般來講,一個類只能繼承自另一個類,有時候不同類之間可以有一些共有的特性,這時候就可以把特性提取成介面(interfaces),用 implements 關鍵字來實現。這個特性大大提高了物件導向的靈活性。

舉例來說,門是一個類,防盜門是門的子類。如果防盜門有一個報警器的功能,我們可以簡單的給防盜門新增一個報警方法。這時候如果有另一個類,車,也有報警器的功能,就可以考慮把報警器提取出來,作為一個介面,防盜門和車都去實現它:

interface Alarm {
    alert();
}

class Door {
}

class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}

class Car implements Alarm {
    alert() {
        console.log('Car alert');
    }
}

一個類可以實現多個介面

interface Alarm {
    alert();
}

interface Light {
    lightOn();
    lightOff();
}

class Car implements Alarm, Light {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

泛型

泛型(Generics)是指在定義函式、介面或類的時候,不預先指定具體的型別,而在使用的時候再指定型別的一種特性。

//泛型函式的型別與非泛型函式的型別沒什麼不同,只是有一個型別引數在最前面,像函式宣告一樣
function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray<string>(3, 'x'); // ['x', 'x', 'x']

//泛型引數的預設型別
function createArray<T = string>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

泛型介面

interface CreateArrayFunc {
    <T>(length: number, value: T): Array<T>;
}

let createArray: CreateArrayFunc;
createArray = function<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

//也可以把泛型引數提前到介面名上
interface CreateArrayFunc<T> {
    (length: number, value: T): Array<T>;
}

let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

泛型類

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型約束

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}
//對泛型進行約束,只允許這個函式傳入那些包含 length 屬性的變數

我們定義一個介面來描述約束條件。 建立一個包含 .length屬性的介面,使用這個介面和extends關鍵字來實現約束。

相關文章