TS系列之接口/類/泛型

接口

接口(Interfaces)是一個很重要的概念,它是對行爲的抽象,而具體如何行動須要由類(classes)去實現(implements)。
命名通常使用大駝峯法。express

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

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

這樣咱們就約束了 tom 的形狀必須和接口 Person 一致。
函數類型app

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而後再去索引對象。ide

// 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)。ui

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接口。this

屬性和方法

使用 class 定義類,使用 constructor 定義構造函數。
經過 new 生成新實例的時候,會自動調用構造函數。code

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關鍵字來實現約束。

相關文章
相關標籤/搜索