💡 本篇主題與重點字:**super**
昨天提到 JavaScript 是一門基於原型繼承的語言,不同於傳統物件導向語言的類別繼承,透過建構函式和原型鏈來實現繼承。
這種寫法雖然功能完整,但語法較為冗長且容易出錯。ES6 引入了 class
語法糖,讓繼承變得更加直觀,而 super
關鍵字正是這套語法的核心機制之一!
語法糖: 電腦語言中添加的某種語法,這種語法對語言的功能沒有影響,但是更方便程式設計師使用。 —— 維基
當我們在 JavaScript 的 class
語法中使用 super
時,實際上是對父類別(superclass)的一種訪問方式,底層仍然依賴原型鏈與函式綁定機制。讓我們看看上面的程式碼在底層是如何運作的:
Class Animal 的實際轉換
Class Animal
實際上變成了一個 constructor,然後再把 speak
方法加到 Animal.prototype 上。
Class Dog 的實際轉換
Class Dog
也先變成一個 constructor,然後如昨天的步驟建立原型鏈繼承關係,再把新的方法 bark
添加到 Dog.prototype。
這樣的轉換讓我們理解到:
Dog.prototype.__proto__ === Animal.prototype
為真Dog.__proto__ === Animal
(用於靜態方法的繼承)在 ES6 語法中,super(name)
看起來像是直接呼叫父類的建構函式,但實際的工作原理其實誠如昨天所說的 call
。當你在子類的 constructor 中使用 super()
時,JavaScript 引擎會:
this
的綁定設定為正在創建的新實例this
傳入這個過程本質上就是 Animal.call(this, name)
的等價物。
所有的方法調用,因為語法糖畢竟不改變邏輯,所以最終實際上的底層實作都會回到昨天的原理:當在一個方法內使用 super.method()
時,JavaScript 引擎會先找出這個方法的定義位置的原型,然後沿著原型鏈向上尋找該方法並執行(或是直到找不到)。
1. 在 constructor 中呼叫父類別的建構函數
super(name)
呼叫的是 Animal
的建構函式,等同於 Animal.call(this, name)
super()
,否則會出現錯誤,因為 this
還沒被初始化2. 呼叫父類別的實例方法
這樣會得到
Fido makes a noise.
Fido barks.
需要特別注意的是,super.method()
中的 this
仍然指向當前實例,而不是父類別,我們可以回顧前幾天的 this 是誰,因為 this 是動態傳入 super()
底層機制的 .call(this)
,因此指向的對象是子類別。
比較項目 | ES5 傳統原型繼承 | ES6 class 語法 |
---|---|---|
建構函式定義 | 使用 function |
使用 class |
屬性繼承 | Parent.call(this) |
super() |
方法繼承 | Object.create() 設定原型鏈 |
extends 自動建立 |
原型鏈設定 | 手動設定 prototype |
語法糖,自動處理 |
可讀性 | 較複雜,容易出錯 | 理解上會更貼近 OOP |
實質本質 | 基於 prototype chain | 基於 prototype chain |
Dog.prototype.constructor = Dog
這步驟自動完成extends
讓繼承關係一目了然一般來說,現在已經不會看到有 ES5 的寫法了,但為了更好地掌握 JS 本質以避免一些奇怪的 bug,知道這些語法糖底下的基礎邏輯與如何解析運行,還是蠻重要的!!