TypeScript型別系統基本規則

fredricen發表於2020-11-10

開始

TypeScript結構化型別系統的基本規則是,如果x要相容y,那麼y至少具有與x相同的屬性。比如:

interface Named {
    name: string;
}

let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: 'Alice', location: 'Seattle' };
x = y;

這裡要檢查y是否能賦值給x,編譯器檢查x中的每個屬性,看是否能在y中也找到對應屬性。 在這個例子中,y必須包含名字是name的string型別成員。y滿足條件,因此賦值正確。

檢查函式引數時使用相同的規則:

function greet(n: Named) {
    console.log('Hello, ' + n.name);
}
greet(y); // OK

注意,y有個額外的location屬性,但這不會引發錯誤。 只有目標型別(這裡是Named)的成員會被一一檢查是否相容。

這個比較過程是遞迴進行的,檢查每個成員及子成員。

列舉

列舉型別與數字型別相容,並且數字型別與列舉型別相容。不同列舉型別之間是不相容的。比如,

enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };

let status = Status.Ready;
status = Color.Green;  // Error

類與物件字面量和介面差不多,但有一點不同:類有靜態部分和例項部分的型別。 比較兩個類型別的物件時,只有例項的成員會被比較。 靜態成員和建構函式不在比較的範圍內。

class Animal {
    feet: number;
    constructor(name: string, numFeet: number) { }
}

class Size {
    feet: number;
    constructor(numFeet: number) { }
}

let a: Animal;
let s: Size;

a = s;  // OK
s = a;  // OK

類的私有成員和受保護成員

類的私有成員和受保護成員會影響相容性。 當檢查類例項的相容時,如果目標型別包含一個私有成員,那麼源型別必須包含來自同一個類的這個私有成員。 同樣地,這條規則也適用於包含受保護成員例項的型別檢查。 這允許子類賦值給父類,但是不能賦值給其它有同樣型別的類。

泛型

因為TypeScript是結構性的型別系統,型別引數隻影響使用其做為型別一部分的結果型別。比如,

interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;

x = y;  // OK, because y matches structure of x

上面程式碼裡,x和y是相容的,因為它們的結構使用型別引數時並沒有什麼不同。 把這個例子改變一下,增加一個成員,就能看出是如何工作的了:

interface NotEmpty<T> {
    data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;

x = y;  // Error, because x and y are not compatible

在這裡,泛型型別在使用時就好比不是一個泛型型別。

對於沒指定泛型型別的泛型引數時,會把所有泛型引數當成any比較。 然後用結果型別進行比較,就像上面第一個例子。

比如,

let identity = function<T>(x: T): T {
    // ...
}

let reverse = function<U>(y: U): U {
    // ...
}

identity = reverse;  // OK, because (x: any) => any matches (y: any) => any

參考資料:型別相容性

相關文章