新手指南: JavaScript 中的 super 關鍵字
更新日期: 2024 年 4 月 10 日
閱讀要求
建議閱讀本文前,務必先閱讀以下 3 篇「物件」的基本概念,才會更清楚理解內文。
物件導向的核心,在於利用類別(class)和物件(object)來模擬現實世界中的實體和過程。
在這種範式下,類別定義了物件的結構(屬性)和行為(方法),而繼承則允許一個類別(子類別)獲得另一個類別(父類別)的屬性和方法。
這不僅有助於減少代碼冗餘,提高代碼重用率,還促進了多態性的實現,使得相同的函數或方法可以用在不同的對象上,表現出不同的行為。
舉例來說,你可以創造一個「動物」類別,在這個類別裡,你定義了動物的一些特性(比如名字和年齡)和行為(比如叫和跑)。
然後,如果你想要創造一個特定的貓,比如你家的寵物貓,你就可以基於「貓」這個類別來創建一個新的實例,並給它賦予一個名字和年齡。繼承讓這一切變得更有趣,它允許你創建一個新類別(比如「橘貓」)。
這個新類別會自動擁有「動物」類別的所有特性和行為,然後你還可以給它加上一些額外的特性或改變一些行為。
這樣做可以讓你重用和修改現有的代碼,而不是從頭開始寫,讓相同的功能在不同類型的狗身上可以有不同的表現。
super 關鍵字的基本用法
在類別繼承的上下文中,super 關鍵字扮演著至關重要的角色。它被用於子類別中,作為一種方式來調用父類別的建構式函數和方法。
這不僅確保了子類別可以正確地初始化繼承自父類別的屬性,還允許子類別在保留父類別行為的同時擴展或修改這些行為。
以同樣的例子說明,當你創建一個新的類別,比如「橘貓」,並想要它繼承「動物」類別的時候,使用 super 就像是在說:「嘿,我要從動物類別繼承一些東西。」
它幫助「橘貓」獲得了「動物」類別的所有特性和行為。
不僅如此,super 還允許「橘貓」在保持「動物」類別原有行為的同時,添加或改變一些東西(比如,增加一個發出貓叫的行為)。
這就是 super 這個關鍵字如此重要的原因,它讓類別之間的關係更加靈活,讓你可以很容易地擴展和自定義功能。
在建構式函數中使用 super
當你創建一個類別並從另一個類別繼承時,子類別的建構式函數(Constructor)必須調用 super(),這會執行父類別的建構式函數。
class Animal {
constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name, age) {
super(name); // 調用父類別的 constructor
this.age = age;
}
}
const myCat = new Cat('Whiskers', 5);
console.log(myCat.name); // 輸出: Whiskers
console.log(myCat.age); // 輸出: 5
在上面的例子中,當使用 super(name) 調用父類別(Animal)的建構式函數時,當創建一個 Cat 的實例並傳遞一個名字時,這個名字會被用作 name 屬性的值。
從而確保,這個屬性在 Cat 實例被創建時就已經準備好了,可以被後續的代碼使用。
接著,範例中提到之後,我們添加了 Cat 特有的 age 屬性。
這表示除了從父類別繼承的屬性外,Cat 類別還定義了自己特有的屬性。這個 age 屬性只存在於 Cat 的實例中,不影響父類別 Animal 或其他可能的子類別。
在方法中使用 super
super 也可以用於子類別中的方法,以調用父類別中被重寫(Overriding)的方法。
它允許子類別提供一個父類別已有方法的新實現版本。當子類別擁有與父類別同名的方法時,這個子類別的方法就「重寫」了父類別的那個方法。
做的目的是讓子類別能夠根據自己的需要,改變或擴展從父類別繼承來的行為。
雖然子類別重寫了父類別的方法,但有時候我們仍然需要從子類別中訪問那個被重寫的父類別方法的原始版本,這時就可以使用 super 關鍵字來實現。
class Animal {
speak() {
console.log("The animal makes a sound.");
}
}
class Cat extends Animal {
speak() {
super.speak(); // 調用父類別的 speak 方法
console.log("The cat meows.");
}
}
const myCat = new Cat();
myCat.speak();
// 輸出:
// The animal makes a sound.
// The cat meows.
在這個例子中,Cat 類別的 speak 方法首先通過 super.speak() 調用了 Animal 類別的 speak 方法(這是被重寫的方法的原始版本),然後添加了自己的行為(貓叫聲)。
這顯示了重寫不僅允許子類別定制或擴展父類別的行為,而且通過 super,子類別還能夠利用那些被重寫的原始方法。
在靜態方法中使用 super
在 JavaScript 中,super 關鍵字的用途不僅限於實例方法和建構式函數,它同樣適用於靜態方法。
這一功能允許子類別的靜態方法,調用其父類別的靜態方法。
補充:靜態方法(Static Methods)
「靜態方法」指的是那些被定義在類別上,而不是類別實例上的方法。
這意味著你可以直接通過類別來調用這些方法,而不需要創建類別的一個實例。
在 JavaScript 中,靜態方法是通過在方法名前加上 static 關鍵字來定義的。一旦定義,你就可以直接使用類別名來調用這些靜態方法。
假設我們有一個 MathUtils 類別,它提供了一些基本的數學工具方法,這些方法與具體的數學實例無關,因此可以定義為靜態方法:
class MathUtils {
static sum(a, b) {
return a + b;
}
static subtract(a, b) {
return a - b;
}
}
// 調用靜態方法
console.log(MathUtils.sum(5, 3)); // 輸出:8
console.log(MathUtils.subtract(10, 6)); // 輸出:4
在上面的例子中,sum 和 subtract 方法都是靜態的,意味著我們不需要創建 MathUtils 類別的一個實例來使用這些方法。
直接使用 MathUtils.sum(5, 3) 和 MathUtils.subtract(10, 6) 就可以調用它們。
想對靜態方法有更深入完整的介紹,建議看一下這篇文章:JavaScript Static Methods
假設有一個父類別 Animal 和一個子類別 Cat,它們都有一個靜態方法 identify。
我們希望在 Cat 的 identify 方法中首先調用 Animal 的 identify 方法,然後添加一些關於 Cat 的額外資訊:
class Animal {
static identify() {
console.log("I am an animal.");
}
}
class Cat extends Animal {
static identify() {
super.identify(); // 調用父類別的靜態方法
console.log("I am a cat.");
}
}
Cat.identify();
// 輸出:
// I am an animal.
// I am a cat.
在這個例子中,Cat.identify() 靜態方法首先通過 super.identify() 調用 Animal 類別的 identify 靜態方法,再接著輸出其特有的資訊。
extend 與 super 關鍵字的關係
雖然 extend 和 super 經常一起使用,來確保子類別能夠正確繼承並初始化父類別的屬性。
但在某些情況下,子類別如果沒有自己的建構式函數,或者不需要在建構式函數中做特殊處理,就不必調用 super。
需要同時使用 extend 和 super
當你的子類別需要自己的建構式函數時,你通常會在其中使用 super 來調用父類別的建構式函數。這確保了從父類別繼承的所有屬性都會被正確初始化:
class Animal {
constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name, lovesFish) {
super(name); // 這裡需要調用 super
this.lovesFish = lovesFish;
}
}
使用 extend 但不調用 super
如果你的子類別不需要自己的建構式函數,那麼你就不需要調用 super,因為 JavaScript 會自動呼叫父類別的建構式函數:
class Animal {
constructor() {
this.species = "Animal";
}
}
class Cat extends Animal {
// 沒有定義建構式函數,也沒有調用 super
purr() {
console.log("Purr...");
}
}
const myCat = new Cat();
console.log(myCat.species); // "Animal",證明父類別的建構式函數被自動調用
在這個例子中,即使 Cat
類別沒有自己的建構式函數且沒有使用 super
,Animal
的建構式函數仍然會被自動調用來初始化 Cat
實例。
super 的細節和限制
只能在衍生類別中使用
super 只能在子類別(也就是使用 extend 關鍵字從另一個類別繼承的類別)的建構式函數和方法中使用。
在一個沒有繼承任何類別的類別中使用 super,或在類別的靜態方法以外的任何地方使用 super,都會導致語法錯誤。
錯誤示例 1:在非衍生類別中使用 super
class MyUtility {
constructor() {
super(); // 錯誤使用!MyUtility 沒有繼承任何類別。
}
}
錯誤示例 2:在靜態方法以外的地方使用 super
class Animal {
speak() {
console.log("The animal makes a sound.");
}
}
class Cat extends Animal {
static jump() {
super.speak(); // 錯誤使用!在靜態方法中不能調用非靜態父類方法。
}
}
建構式函數中的使用規則
在子類別的建構式函數中,super 必須被調用之前,不能使用 this 關鍵字。
這是因為在 JavaScript 中,子類別的實例在父類別的建構式函數被執行並完成初始化之前,是不被認為完全構建的。
如果在調用 super 之前嘗試訪問 this,JavaScript 會拋出一個參考錯誤。
class Animal {
constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name, age) {
this.age = age; // 錯誤!試圖在調用 super 之前使用 this。
super(name); // 正確的做法是首先調用 super。
}
}
const myCat = new Cat('Whiskers', 3);
無法直接訪問父類別的屬性
雖然 super 可以用來調用父類別的方法,但它並不提供一種直接訪問父類別屬性的方式。
這意味著,如果你需要在子類別中讀取或修改繼承自父類別的屬性,你應該通過子類別實例來做,而不是試圖使用 super 來實現。
class Animal {
constructor() {
this.type = 'Animal';
}
}
class Cat extends Animal {
constructor() {
super();
console.log(super.type); // 錯誤!這是不允許的操作,無法這樣直接訪問父類別的屬性。
}
}
const myCat = new Cat();
調用被覆蓋的方法時的 this 指向
當在子類別的方法中使用 super 調用父類別被覆蓋的方法時,方法中的 this 會指向當前的子類別實例。
這保證了即使是從父類別繼承的方法,也能夠訪問子類別中定義的屬性和方法。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + " makes a noise.");
}
}
class Cat extends Animal {
constructor(name) {
super(name);
this.trick = 'jump';
}
speak() {
super.speak(); // 正確調用父類別的方法
console.log("And can " + this.trick); // 此處期望 this 指向 Cat 的實例,並正確訪問到 trick 屬性。
}
}
const myCat = new Cat("Whiskers");
myCat.speak();
// 預期輸出:
// Whiskers makes a noise.
// And can jump
結論
super 在靜態方法中的使用提供了一種強大的機制,以在保持父類別靜態方法的基礎上進行擴展或修改。這使得類別之間的代碼重用變得更加容易,同時也保持了代碼的清晰和組織性。
無論是在實例方法、建構式函數,還是靜態方法中,正確地使用 super 都是深入理解和有效利用 JavaScript 類別繼承機制的關鍵。