背景閱讀
類別 (MDN)
TypeScript 完全支援 ES2015 中引入的 class
關鍵字。
與其他 JavaScript 語言功能一樣,TypeScript 新增類型註解和其他語法,讓您表達類別與其他類型之間的關係。
類別成員
以下是基本類別 - 空類別
tsTry
classPoint {}
這個類別目前沒有什麼用,讓我們開始加入一些成員。
欄位
欄位宣告會在類別中建立一個可寫入的公開屬性
tsTry
classPoint {x : number;y : number;}constpt = newPoint ();pt .x = 0;pt .y = 0;
與其他位置一樣,類型註解是可選的,但如果未指定,會是隱含的 any
。
欄位也可以有初始化項;這些初始化項會在類別實例化時自動執行
tsTry
classPoint {x = 0;y = 0;}constpt = newPoint ();// Prints 0, 0console .log (`${pt .x }, ${pt .y }`);
就像 const
、let
和 var
一樣,類別屬性的初始化項會用來推斷其類型
tsTry
constpt = newPoint ();Type 'string' is not assignable to type 'number'.2322Type 'string' is not assignable to type 'number'.pt .x = "0";
--strictPropertyInitialization
strictPropertyInitialization
設定控制類別欄位是否需要在建構函式中初始化。
tsTry
classBadGreeter {Property 'name' has no initializer and is not definitely assigned in the constructor.2564Property 'name' has no initializer and is not definitely assigned in the constructor.: string; name }
tsTry
classGoodGreeter {name : string;constructor() {this.name = "hello";}}
請注意,欄位需要在建構函式本身中初始化。TypeScript 沒有分析您從建構函式呼叫的方法,以偵測初始化,因為衍生類別可能會覆寫這些方法,而無法初始化成員。
如果您打算透過建構函式以外的方法明確初始化欄位(例如,外部函式庫可能為您填寫部分類別),您可以使用明確指定運算子,!
tsTry
classOKGreeter {// Not initialized, but no errorname !: string;}
readonly
欄位可以加上 readonly
修飾詞。這會防止在建構函式外部指定欄位。
tsTry
classGreeter {readonlyname : string = "world";constructor(otherName ?: string) {if (otherName !==undefined ) {this.name =otherName ;}}err () {this.Cannot assign to 'name' because it is a read-only property.2540Cannot assign to 'name' because it is a read-only property.= "not ok"; name }}constg = newGreeter ();Cannot assign to 'name' because it is a read-only property.2540Cannot assign to 'name' because it is a read-only property.g .= "also not ok"; name
建構函式
背景閱讀
建構函式 (MDN)
類別建構函式與函式非常相似。你可以加入具有型別註解、預設值和重載的參數
tsTry
classPoint {x : number;y : number;// Normal signature with defaultsconstructor(x = 0,y = 0) {this.x =x ;this.y =y ;}}
tsTry
classPoint {// Overloadsconstructor(x : number,y : string);constructor(s : string);constructor(xs : any,y ?: any) {// TBD}}
類別建構函式簽章和函式簽章之間只有幾個不同
- 建構函式無法有型別參數 - 這些屬於外部類別宣告,我們稍後會學習
- 建構函式無法有傳回型別註解 - 類別實例型別永遠是傳回的內容
超級呼叫
就像在 JavaScript 中,如果你有一個基底類別,你需要在使用任何 this.
成員之前在建構函式主體中呼叫 super();
tsTry
classBase {k = 4;}classDerived extendsBase {constructor() {// Prints a wrong value in ES5; throws exception in ES6'super' must be called before accessing 'this' in the constructor of a derived class.17009'super' must be called before accessing 'this' in the constructor of a derived class.console .log (this .k );super();}}
忘記呼叫 super
是 JavaScript 中容易犯的錯誤,但 TypeScript 會在你需要時告訴你。
方法
背景閱讀
方法定義
類別上的函式屬性稱為方法。方法可以使用與函式和建構函式相同的類型註解
tsTry
classPoint {x = 10;y = 10;scale (n : number): void {this.x *=n ;this.y *=n ;}}
除了標準類型註解之外,TypeScript 沒有為方法新增任何其他新功能。
請注意,在方法主體內,仍然必須透過 this.
來存取欄位和其他方法。方法主體中未限定的名稱將永遠參考封裝範圍內的某個項目
tsTry
letx : number = 0;classC {x : string = "hello";m () {// This is trying to modify 'x' from line 1, not the class propertyType 'string' is not assignable to type 'number'.2322Type 'string' is not assignable to type 'number'.= "world"; x }}
取得器 / 設定器
類別也可以有存取器
tsTry
classC {_length = 0;getlength () {return this._length ;}setlength (value ) {this._length =value ;}}
請注意,在 JavaScript 中,沒有額外邏輯的欄位後援取得/設定配對很少有用。如果您不需要在取得/設定作業期間新增額外邏輯,公開欄位是可以的。
TypeScript 對存取器有一些特殊的推論規則
- 如果存在
get
但沒有set
,則屬性會自動為readonly
- 如果未指定設定器參數的類型,則會從取得器的傳回類型推論
- 取得器和設定器必須具有相同的 成員可見性
自 TypeScript 4.3 以來,可以有取得和設定不同類型的存取器。
tsTry
classThing {_size = 0;getsize (): number {return this._size ;}setsize (value : string | number | boolean) {letnum =Number (value );// Don't allow NaN, Infinity, etcif (!Number .isFinite (num )) {this._size = 0;return;}this._size =num ;}}
索引簽章
類別可以宣告索引簽章;這些簽章與其他物件類型的索引簽章相同
tsTry
classMyClass {[s : string]: boolean | ((s : string) => boolean);check (s : string) {return this[s ] as boolean;}}
因為索引簽章類型也需要擷取方法的類型,所以不容易使用這些類型。通常最好將索引資料儲存在其他地方,而不是儲存在類別實例本身。
類別傳承
與其他具有物件導向功能的語言一樣,JavaScript 中的類別可以繼承自基礎類別。
implements
子句
你可以使用 implements
子句檢查類別是否符合特定 interface
。如果類別未正確實作,系統會發出錯誤訊息
tsTry
interfacePingable {ping (): void;}classSonar implementsPingable {ping () {console .log ("ping!");}}classClass 'Ball' incorrectly implements interface 'Pingable'. Property 'ping' is missing in type 'Ball' but required in type 'Pingable'.2420Class 'Ball' incorrectly implements interface 'Pingable'. Property 'ping' is missing in type 'Ball' but required in type 'Pingable'.implements Ball Pingable {pong () {console .log ("pong!");}}
類別也可以實作多個介面,例如 class C implements A, B {
。
注意事項
了解 implements
子句只是檢查類別是否可以視為介面類型非常重要。它不會改變類別或其方法的類型。常見的錯誤來源是假設 implements
子句會改變類別類型 - 它不會!
tsTry
interfaceCheckable {check (name : string): boolean;}classNameChecker implementsCheckable {Parameter 's' implicitly has an 'any' type.7006Parameter 's' implicitly has an 'any' type.check () { s // Notice no error herereturns .toLowerCase () === "ok";}}
在此範例中,我們可能預期 s
的類型會受到 check
的 name: string
參數影響。並非如此 - implements
子句不會改變類別主體的檢查方式或其推論的類型。
類似地,實作具有選用屬性的介面不會建立該屬性
tsTry
interfaceA {x : number;y ?: number;}classC implementsA {x = 0;}constc = newC ();Property 'y' does not exist on type 'C'.2339Property 'y' does not exist on type 'C'.c .= 10; y
extends
子句
背景閱讀
extends 關鍵字 (MDN)
類別可以從基底類別中extend
。衍生類別具有其基底類別的所有屬性和方法,並且也可以定義其他成員。
tsTry
classAnimal {move () {console .log ("Moving along!");}}classDog extendsAnimal {woof (times : number) {for (leti = 0;i <times ;i ++) {console .log ("woof!");}}}constd = newDog ();// Base class methodd .move ();// Derived class methodd .woof (3);
覆寫方法
背景閱讀
super 關鍵字 (MDN)
衍生類別也可以覆寫基底類別的欄位或屬性。您可以使用super.
語法存取基底類別的方法。請注意,由於 JavaScript 類別是一個簡單的查詢物件,因此沒有「super 欄位」的概念。
TypeScript 強制執行衍生類別永遠是其基底類別的子類型。
例如,以下是覆寫方法的合法方式
tsTry
classBase {greet () {console .log ("Hello, world!");}}classDerived extendsBase {greet (name ?: string) {if (name ===undefined ) {super.greet ();} else {console .log (`Hello, ${name .toUpperCase ()}`);}}}constd = newDerived ();d .greet ();d .greet ("reader");
重要的是,衍生類別必須遵循其基底類別的合約。請記住,透過基底類別參考來參考衍生類別實例是非常常見(而且永遠都是合法的)
tsTry
// Alias the derived instance through a base class referenceconstb :Base =d ;// No problemb .greet ();
如果Derived
沒有遵循Base
的合約會怎樣?
tsTry
classBase {greet () {console .log ("Hello, world!");}}classDerived extendsBase {// Make this parameter requiredProperty 'greet' in type 'Derived' is not assignable to the same property in base type 'Base'. Type '(name: string) => void' is not assignable to type '() => void'.2416Property 'greet' in type 'Derived' is not assignable to the same property in base type 'Base'. Type '(name: string) => void' is not assignable to type '() => void'.( greet name : string) {console .log (`Hello, ${name .toUpperCase ()}`);}}
如果我們編譯此程式碼,即使有錯誤,此範例也會崩潰
tsTry
constb :Base = newDerived ();// Crashes because "name" will be undefinedb .greet ();
僅類型欄位宣告
當 target >= ES2022
或 useDefineForClassFields
為 true
,類別欄位會在父類別建構函式完成後初始化,覆寫父類別設定的任何值。當您只想重新宣告繼承欄位的更精確類型時,這可能會造成問題。為了解決這些情況,您可以撰寫 declare
來指示 TypeScript 此欄位宣告不應有執行時期效果。
tsTry
interfaceAnimal {dateOfBirth : any;}interfaceDog extendsAnimal {breed : any;}classAnimalHouse {resident :Animal ;constructor(animal :Animal ) {this.resident =animal ;}}classDogHouse extendsAnimalHouse {// Does not emit JavaScript code,// only ensures the types are correctdeclareresident :Dog ;constructor(dog :Dog ) {super(dog );}}
初始化順序
在某些情況下,JavaScript 類別初始化順序可能會令人驚訝。我們來考慮以下程式碼
tsTry
classBase {name = "base";constructor() {console .log ("My name is " + this.name );}}classDerived extendsBase {name = "derived";}// Prints "base", not "derived"constd = newDerived ();
這裡發生了什麼事?
JavaScript 定義的類別初始化順序為
- 初始化基本類別欄位
- 執行基本類別建構函式
- 初始化衍生類別欄位
- 執行衍生類別建構函式
這表示基本類別建構函式在自己的建構函式期間看到了 name
的自身值,因為衍生類別欄位初始化尚未執行。
繼承內建型別
注意:如果您不打算繼承內建型別,例如
Array
、Error
、Map
等,或者您的編譯目標明確設定為ES6
/ES2015
或更高,您可以跳過本節
在 ES2015 中,傳回物件的建構函式會隱式地將 this
的值替換為任何呼叫 super(...)
的呼叫者。產生的建構函式程式碼必須擷取 super(...)
的任何潛在傳回值,並將其替換為 this
。
因此,繼承 Error
、Array
等可能無法再如預期般運作。這是因為 Error
、Array
等的建構函式會使用 ECMAScript 6 的 new.target
來調整原型鏈;然而,在 ECMAScript 5 中呼叫建構函式時,無法確保 new.target
的值。其他下階編譯器通常預設有相同的限制。
對於以下類型的子類別
tsTry
classMsgError extendsError {constructor(m : string) {super(m );}sayHello () {return "hello " + this.message ;}}
您可能會發現
- 在建構這些子類別傳回的物件上,方法可能是
undefined
,因此呼叫sayHello
會導致錯誤。 instanceof
會在子類別的執行個體及其執行個體之間中斷,因此(new MsgError()) instanceof MsgError
會傳回false
。
建議您在任何 super(...)
呼叫之後,立即手動調整原型。
tsTry
classMsgError extendsError {constructor(m : string) {super(m );// Set the prototype explicitly.Object .setPrototypeOf (this,MsgError .prototype );}sayHello () {return "hello " + this.message ;}}
不過,MsgError
的任何子類別都必須手動設定原型。對於不支援 Object.setPrototypeOf
的執行時期,您可能可以使用 __proto__
。
很不幸地,這些解決方法不適用於 Internet Explorer 10 及更早版本。您可手動將方法從原型複製到執行個體本身 (例如,將 MsgError.prototype
複製到 this
),但原型鏈本身無法修復。
成員可見性
您可以使用 TypeScript 來控制特定方法或屬性是否對類別外的程式碼可見。
public
類別成員的預設可見性為 public
。public
成員可以在任何地方存取
tsTry
classGreeter {publicgreet () {console .log ("hi!");}}constg = newGreeter ();g .greet ();
由於 public
已是預設的可見性修飾詞,因此您不需要在類別成員中撰寫它,但可以出於樣式/可讀性原因選擇這樣做。
protected
protected
成員僅對其宣告所在的類別的子類別可見。
tsTry
classGreeter {publicgreet () {console .log ("Hello, " + this.getName ());}protectedgetName () {return "hi";}}classSpecialGreeter extendsGreeter {publichowdy () {// OK to access protected member hereconsole .log ("Howdy, " + this.getName ());}}constg = newSpecialGreeter ();g .greet (); // OKProperty 'getName' is protected and only accessible within class 'Greeter' and its subclasses.2445Property 'getName' is protected and only accessible within class 'Greeter' and its subclasses.g .(); getName
受保護
成員的公開
衍生類別需要遵循其基底類別合約,但可能會選擇公開具有更多功能的基底類別子類型。這包括將受保護
成員設為公開
tsTry
classBase {protectedm = 10;}classDerived extendsBase {// No modifier, so default is 'public'm = 15;}constd = newDerived ();console .log (d .m ); // OK
請注意,Derived
已經可以自由讀寫m
,因此這不會有意義地改變此情況的「安全性」。這裡要注意的主要事項是,在衍生類別中,如果這種公開並非有意為之,我們需要小心重複受保護
修飾詞。
跨層級受保護
存取
不同的 OOP 語言對於是否可以透過基底類別參考存取受保護
成員有不同的看法
tsTry
classBase {protectedx : number = 1;}classDerived1 extendsBase {protectedx : number = 5;}classDerived2 extendsBase {f1 (other :Derived2 ) {other .x = 10;}f2 (other :Derived1 ) {Property 'x' is protected and only accessible within class 'Derived1' and its subclasses.2445Property 'x' is protected and only accessible within class 'Derived1' and its subclasses.other .= 10; x }}
例如,Java 認為這是合法的。另一方面,C# 和 C++ 則選擇此程式碼應該是違法的。
TypeScript 在這裡與 C# 和 C++ 站同一陣線,因為在 Derived2 中存取 x 應該只允許 Derived2 的子類別,而 Derived1 並非其中之一。此外,如果透過 Derived1 參照存取 x 是非法的(這當然應該是如此!),那麼透過基底類別參照存取它絕不會改善情況。
另請參閱 為何我無法從衍生類別存取受保護的成員?,其中進一步說明 C# 的推理。
private
private
類似於 protected
,但即使從子類別也無法存取成員
tsTry
classBase {privatex = 0;}constb = newBase ();// Can't access from outside the classProperty 'x' is private and only accessible within class 'Base'.2341Property 'x' is private and only accessible within class 'Base'.console .log (b .); x
tsTry
classDerived extendsBase {showX () {// Can't access in subclassesProperty 'x' is private and only accessible within class 'Base'.2341Property 'x' is private and only accessible within class 'Base'.console .log (this.); x }}
由於 private
成員對衍生類別不可見,因此衍生類別無法增加其可見性
tsTry
classBase {privatex = 0;}classClass 'Derived' incorrectly extends base class 'Base'. Property 'x' is private in type 'Base' but not in type 'Derived'.2415Class 'Derived' incorrectly extends base class 'Base'. Property 'x' is private in type 'Base' but not in type 'Derived'.extends Derived Base {x = 1;}
跨執行個體的 private
存取
不同的 OOP 語言對於同一個類別的不同執行個體是否可以存取彼此的 private
成員有不同的看法。Java、C#、C++、Swift 和 PHP 等語言允許這麼做,但 Ruby 則不允許。
TypeScript 允許跨實例存取 private
tsTry
classA {privatex = 10;publicsameAs (other :A ) {// No errorreturnother .x === this.x ;}}
注意事項
與 TypeScript 類型系統的其他面向一樣,private
和 protected
僅在類型檢查期間強制執行。
這表示,JavaScript 執行時期建構,例如 in
或簡單的屬性查詢,仍可存取 private
或 protected
成員
tsTry
classMySafe {privatesecretKey = 12345;}
js
// In a JavaScript file...const s = new MySafe();// Will print 12345console.log(s.secretKey);
private
也允許在類型檢查期間使用方括號表示法存取。這使得 private
宣告的欄位可能更容易讓單元測試等項目存取,缺點是這些欄位是軟私有,不會嚴格強制執行私密性。
tsTry
classMySafe {privatesecretKey = 12345;}consts = newMySafe ();// Not allowed during type checkingProperty 'secretKey' is private and only accessible within class 'MySafe'.2341Property 'secretKey' is private and only accessible within class 'MySafe'.console .log (s .); secretKey // OKconsole .log (s ["secretKey"]);
與 TypeScript 的 private
不同,JavaScript 的 私有欄位 (#
) 在編譯後仍保持私有,且不會提供先前提到的逃生艙口,例如方括號表示法存取,使其成為硬私有。
tsTry
classDog {#barkAmount = 0;personality = "happy";constructor() {}}
tsTry
"use strict";class Dog {#barkAmount = 0;personality = "happy";constructor() { }}
在編譯到 ES2021 或更低版本時,TypeScript 會使用 WeakMap 取代 #
。
tsTry
"use strict";var _Dog_barkAmount;class Dog {constructor() {_Dog_barkAmount.set(this, 0);this.personality = "happy";}}_Dog_barkAmount = new WeakMap();
如果您需要保護類別中的值免於惡意行為者,您應該使用提供硬執行時期私密性的機制,例如封閉、WeakMap 或私有欄位。請注意,這些在執行時期新增的私密性檢查可能會影響效能。
靜態成員
背景閱讀
靜態成員 (MDN)
類別可以有static
成員。這些成員與類別的特定實例無關。它們可以透過類別建構函式物件本身來存取
tsTry
classMyClass {staticx = 0;staticprintX () {console .log (MyClass .x );}}console .log (MyClass .x );MyClass .printX ();
靜態成員也可以使用相同的public
、protected
和private
可見度修飾詞
tsTry
classMyClass {private staticx = 0;}Property 'x' is private and only accessible within class 'MyClass'.2341Property 'x' is private and only accessible within class 'MyClass'.console .log (MyClass .); x
靜態成員也會被繼承
tsTry
classBase {staticgetGreeting () {return "Hello world";}}classDerived extendsBase {myGreeting =Derived .getGreeting ();}
特殊靜態名稱
通常無法安全地覆寫 Function
原型的屬性。由於類別本身是可使用 new
呼叫的函式,因此無法使用某些 static
名稱。函式屬性(例如 name
、length
和 call
)無法定義為 static
成員
tsTry
classS {staticStatic property 'name' conflicts with built-in property 'Function.name' of constructor function 'S'.2699Static property 'name' conflicts with built-in property 'Function.name' of constructor function 'S'.= "S!"; name }
為何沒有靜態類別?
TypeScript(和 JavaScript)沒有像 C# 那樣的 static class
建構函式。
這些建構函式僅存在於這些語言強制所有資料和函式都必須在類別內部的情況下;由於 TypeScript 中不存在這種限制,因此不需要這些建構函式。在 JavaScript/TypeScript 中,只有一個執行個體的類別通常只表示為一個正常的物件。
例如,我們不需要 TypeScript 中的「靜態類別」語法,因為一般物件(甚至是頂層函式)也能執行相同的工作
tsTry
// Unnecessary "static" classclassMyStaticClass {staticdoSomething () {}}// Preferred (alternative 1)functiondoSomething () {}// Preferred (alternative 2)constMyHelperObject = {dosomething () {},};
類別中的static
區塊
靜態區塊允許您撰寫一系列陳述式,其具有自己的範圍,可以存取包含類別中的私有欄位。這表示我們可以使用撰寫陳述式的所有功能撰寫初始化程式碼,不會洩漏變數,而且可以完全存取我們類別的內部。
tsTry
classFoo {static #count = 0;getcount () {returnFoo .#count;}static {try {constlastInstances =loadLastInstances ();Foo .#count +=lastInstances .length ;}catch {}}}
泛型類別
類別(很像介面)可以是泛型的。當使用 new
執行泛型類別的實例化時,其類型參數的推論方式與函式呼叫相同
tsTry
classBox <Type > {contents :Type ;constructor(value :Type ) {this.contents =value ;}}constb = newBox ("hello!");
類別可以使用泛型約束和預設值,方式與介面相同。
靜態成員中的類型參數
這段程式碼是非法的,而且可能不容易理解原因
tsTry
classBox <Type > {staticStatic members cannot reference class type parameters.2302Static members cannot reference class type parameters.defaultValue :; Type }
請記住,類型總是會被完全清除!在執行階段,只有一個 Box.defaultValue
屬性槽。這表示設定 Box<string>.defaultValue
(如果可行)也會變更 Box<number>.defaultValue
- 不好。泛型類別的 static
成員永遠無法參考類別的類型參數。
this
在類別中的執行階段
背景閱讀
this 關鍵字 (MDN)
重要的是要記住,TypeScript 沒有改變 JavaScript 的執行階段行為,而 JavaScript 以具有某些奇特的執行階段行為而聞名。
JavaScript 處理 this
的方式確實不尋常
tsTry
classMyClass {name = "MyClass";getName () {return this.name ;}}constc = newMyClass ();constobj = {name : "obj",getName :c .getName ,};// Prints "obj", not "MyClass"console .log (obj .getName ());
長話短說,預設情況下,函式內 this
的值取決於函式被呼叫的方式。在此範例中,由於函式是透過 obj
參照被呼叫的,因此其 this
的值是 obj
,而不是類別實例。
這很少是你想要發生的情況!TypeScript 提供了一些方法來減輕或防止這種錯誤。
箭頭函式
背景閱讀
箭頭函式 (MDN)
如果您有一個函式會經常以失去其 this
內容的方式被呼叫,那麼使用箭頭函式屬性會比使用方法定義更有意義
tsTry
classMyClass {name = "MyClass";getName = () => {return this.name ;};}constc = newMyClass ();constg =c .getName ;// Prints "MyClass" instead of crashingconsole .log (g ());
這有一些權衡
this
值保證在執行時是正確的,即使是未經 TypeScript 檢查的程式碼- 這將使用更多記憶體,因為每個類別實例都會有以這種方式定義的每個函式的副本
- 您無法在衍生類別中使用
super.getName
,因為原型鏈中沒有條目可以從中擷取基底類別方法
this
參數
在方法或函式定義中,名為 this
的初始參數在 TypeScript 中具有特殊意義。這些參數會在編譯期間被刪除
tsTry
// TypeScript input with 'this' parameterfunctionfn (this :SomeType ,x : number) {/* ... */}
js
// JavaScript outputfunction fn(x) {/* ... */}
TypeScript 檢查呼叫帶有 this
參數的函式是否使用正確的內容。我們可以將 this
參數新增到方法定義中,以靜態強制執行方法正確呼叫,而不是使用箭頭函式
tsTry
classMyClass {name = "MyClass";getName (this :MyClass ) {return this.name ;}}constc = newMyClass ();// OKc .getName ();// Error, would crashconstg =c .getName ;The 'this' context of type 'void' is not assignable to method's 'this' of type 'MyClass'.2684The 'this' context of type 'void' is not assignable to method's 'this' of type 'MyClass'.console .log (g ());
這種方法與箭頭函式方法產生相反的權衡
- JavaScript 呼叫者可能仍然會在不知情的情況下不正確地使用類別方法
- 每個類別定義只會分配一個函式,而不是每個類別實例一個函式
- 基本方法定義仍可透過
super
呼叫。
this
類型
在類別中,稱為 this
的特殊類型會動態地參考目前類別的類型。讓我們看看這有什麼用處
tsTry
classBox {contents : string = "";set (value : string) {this.contents =value ;return this;}}
在此,TypeScript 將 set
的傳回類型推論為 this
,而不是 Box
。現在讓我們建立 Box
的子類別
tsTry
classClearableBox extendsBox {clear () {this.contents = "";}}consta = newClearableBox ();constb =a .set ("hello");
您也可以在參數類型註解中使用 this
tsTry
classBox {content : string = "";sameAs (other : this) {returnother .content === this.content ;}}
這與撰寫 other: Box
不同,如果您有派生類別,其 sameAs
方法現在只會接受該派生類別的其他執行個體
tsTry
classBox {content : string = "";sameAs (other : this) {returnother .content === this.content ;}}classDerivedBox extendsBox {otherContent : string = "?";}constbase = newBox ();constderived = newDerivedBox ();Argument of type 'Box' is not assignable to parameter of type 'DerivedBox'. Property 'otherContent' is missing in type 'Box' but required in type 'DerivedBox'.2345Argument of type 'Box' is not assignable to parameter of type 'DerivedBox'. Property 'otherContent' is missing in type 'Box' but required in type 'DerivedBox'.derived .sameAs (); base
基於 this
的類型守衛
您可以在類別和介面的方法傳回位置中使用 this is Type
。當與類型縮小 (例如 if
陳述式) 混用時,目標物件的類型會縮小到指定的 Type
。
tsTry
classFileSystemObject {isFile (): this isFileRep {return this instanceofFileRep ;}isDirectory (): this isDirectory {return this instanceofDirectory ;}isNetworked (): this isNetworked & this {return this.networked ;}constructor(publicpath : string, privatenetworked : boolean) {}}classFileRep extendsFileSystemObject {constructor(path : string, publiccontent : string) {super(path , false);}}classDirectory extendsFileSystemObject {children :FileSystemObject [];}interfaceNetworked {host : string;}constfso :FileSystemObject = newFileRep ("foo/bar.txt", "foo");if (fso .isFile ()) {fso .content ;} else if (fso .isDirectory ()) {fso .children ;} else if (fso .isNetworked ()) {fso .host ;}
基於 this
的類型守衛的常見用例是允許延遲驗證特定欄位。例如,當驗證 hasValue
為 true 時,此案例會從儲存在 box 內的值中移除 undefined
tsTry
classBox <T > {value ?:T ;hasValue (): this is {value :T } {return this.value !==undefined ;}}constbox = newBox <string>();box .value = "Gameboy";box .value ;if (box .hasValue ()) {box .value ;}
參數屬性
TypeScript 提供特殊語法,用於將建構函數參數轉換為具有相同名稱和值的類別屬性。這些稱為參數屬性,是透過在建構函數參數之前加上可見性修飾詞 public
、private
、protected
或 readonly
來建立的。產生的欄位會取得這些修飾詞
tsTry
classParams {constructor(public readonlyx : number,protectedy : number,privatez : number) {// No body necessary}}consta = newParams (1, 2, 3);console .log (a .x );Property 'z' is private and only accessible within class 'Params'.2341Property 'z' is private and only accessible within class 'Params'.console .log (a .); z
類別表達式
背景閱讀
類別表達式 (MDN)
類別表達式與類別宣告十分類似。唯一的實際差異在於類別表達式不需要名稱,儘管我們可以透過它們最終繫結到的任何識別碼來參照它們
tsTry
constsomeClass = class<Type > {content :Type ;constructor(value :Type ) {this.content =value ;}};constm = newsomeClass ("Hello, world");
建構函數簽章
JavaScript 類別使用 new
算子進行實例化。給定類別本身的類型,InstanceType 實用程式類型會對此操作進行建模。
tsTry
classPoint {createdAt : number;x : number;y : numberconstructor(x : number,y : number) {this.createdAt =Date .now ()this.x =x ;this.y =y ;}}typePointInstance =InstanceType <typeofPoint >functionmoveRight (point :PointInstance ) {point .x += 5;}constpoint = newPoint (3, 4);moveRight (point );point .x ; // => 8
abstract
類別和成員
TypeScript 中的類別、方法和欄位可能是 abstract。
abstract 方法 或 abstract 欄位 是尚未提供實作的欄位。這些成員必須存在於 abstract 類別 中,而 abstract 類別 無法直接實例化。
abstract 類別的角色是作為子類別的基底類別,而子類別會實作所有 abstract 成員。當一個類別沒有任何 abstract 成員時,它被稱為 具體類別。
我們來看一個範例
tsTry
abstract classBase {abstractgetName (): string;printName () {console .log ("Hello, " + this.getName ());}}constCannot create an instance of an abstract class.2511Cannot create an instance of an abstract class.b = newBase ();
我們無法使用 new
實例化 Base
,因為它是 abstract 的。我們需要建立一個衍生類別並實作 abstract 成員
tsTry
classDerived extendsBase {getName () {return "world";}}constd = newDerived ();d .printName ();
請注意,如果我們忘記實作基底類別的 abstract 成員,我們會收到錯誤訊息
tsTry
classNon-abstract class 'Derived' does not implement inherited abstract member 'getName' from class 'Base'.2515Non-abstract class 'Derived' does not implement inherited abstract member 'getName' from class 'Base'.extends Derived Base {// forgot to do anything}
abstract 建構簽章
有時你想要接受一些類別建構函式,它會產生一個類別的實例,而這個類別衍生自一些 abstract 類別。
例如,您可能想要撰寫此程式碼
tsTry
functiongreet (ctor : typeofBase ) {constCannot create an instance of an abstract class.2511Cannot create an instance of an abstract class.instance = newctor ();instance .printName ();}
TypeScript 正確地告訴您,您正在嘗試實例化抽象類別。畢竟,根據 `greet` 的定義,撰寫這個程式碼完全合法,而這最終會建構一個抽象類別
tsTry
// Bad!greet (Base );
相反地,您想要撰寫一個接受具有建構簽章的函式的函式
tsTry
functiongreet (ctor : new () =>Base ) {constinstance = newctor ();instance .printName ();}greet (Derived );Argument of type 'typeof Base' is not assignable to parameter of type 'new () => Base'. Cannot assign an abstract constructor type to a non-abstract constructor type.2345Argument of type 'typeof Base' is not assignable to parameter of type 'new () => Base'. Cannot assign an abstract constructor type to a non-abstract constructor type.greet (); Base
現在,TypeScript 正確地告訴您哪些類別建構函式可以呼叫 - `Derived` 可以,因為它是具體的,但 `Base` 則不行。
類別之間的關係
在多數情況下,TypeScript 中的類別會以結構化的方式進行比較,與其他型別相同。
例如,這兩個類別可以彼此替換,因為它們相同
tsTry
classPoint1 {x = 0;y = 0;}classPoint2 {x = 0;y = 0;}// OKconstp :Point1 = newPoint2 ();
類似地,即使沒有明確的繼承,類別之間也存在子型關係
tsTry
classPerson {name : string;age : number;}classEmployee {name : string;age : number;salary : number;}// OKconstp :Person = newEmployee ();
這聽起來很簡單,但有一些情況看起來比其他情況更奇怪。
空的類別沒有成員。在結構化型別系統中,沒有成員的型別通常是其他任何東西的超型別。因此,如果您撰寫一個空的類別(不要這樣做!),任何東西都可以用來取代它
tsTry
classEmpty {}functionfn (x :Empty ) {// can't do anything with 'x', so I won't}// All OK!fn (window );fn ({});fn (fn );