JavaScript的9種繼承實作方式歸納_javascript技巧
不同於基於類別的程式語言,如 C 和 Java,JavaScript 中的繼承方式是基於原型的。同時由於 JavaScript 是一種非常靈活的語言,因此實現繼承的方式也非常多。
首要的基本概念是關於建構函式和原型鏈的,父物件的建構子稱為Parent,子物件的建構函式稱為Child,對應的父物件和子物件分別為parent和child。
物件中有一個隱藏屬性[[prototype]](注意不是prototype),在 Chrome 中是__proto__,而在某些環境下則不可訪問,它指向的是這個物件的原型。在存取任何一個物件的屬性或方法時,首先會搜尋本物件的所有屬性,如果找不到的話則會根據[[prototype]]沿著原型鏈逐步搜尋其原型物件上的屬性,直到找到為止,否則返回undefined。
1.原型鏈繼承:
原型鍊是 JavaScript 中實作繼承的預設方式,如果要讓子物件繼承父物件的話,最簡單的方式是將子物件建構函式的prototype屬性指向父物件的一個實例:
function Parent() {}
function Child() {}
Child.prototype = new Parent()
這時候,Child的prototype屬性被重寫了,指向了一個新對象,但是這個新對象的constructor屬性卻沒有正確指向Child,JS 引擎並不會自動為我們完成這件工作,這需要我們手動去將Child的原型物件的constructor屬性重新指向Child:
Child.prototype.constructor = Child
以上就是JavaScript 中的預設繼承機制,將需要重用的屬性和方法遷移到原型對像中,而將不可重用的部分設定為對象的自身屬性,但這種繼承方式需要新建一個實例作為原型對象,效率上會低一些。
2.原型繼承(非原型鏈):
為了避免上一個方法需要重複建立原型物件實例的問題,可以直接將子物件建構函式的prototype指向父物件建構函式的prototype,這樣,所有Parent.prototype中的屬性和方法也能被重複使用,同時不需要重複建立原型物件實例:
Child.prototype = Parent.prototype
Child.prototype.constructor = Child
但是我們知道,在JavaScript 中,物件是作為引用類型存在的,這種方法實際上是將Child.prototype和Parent.prototype中保存的指針指向了同一個對象,因此,當我們想要在子對象原型中擴展一些屬性以便之後繼續繼承的話,父物件的原型也會被改寫,因為這裡的原型物件實例總是只有一個,這也是這種繼承方式的缺點。
3.臨時構造者繼承:
為了解決上面的問題,可以藉用一個臨時構造器起到一個中間層的作用,所有子對象原型的操作都是在臨時構造器的實例上完成,不會影響到父對象原型:
var F = function() {}
F.prototype = Parent.prototype
Child.prototype = new F()
Child.prototype.constructor = Child
同時,為了可以在子物件中存取父類別原型中的屬性,可以在子物件建構器上加入一個指向父物件原型的屬性,如uber,這樣,可以在子物件上直接透過child.constructor.uber訪問到父級原型物件。
我們可以將上面的這些工作封裝成一個函數,以後呼叫這個函數就可以方便實現這種繼承方式了:
function extend(Child, Parent) {
var F = function() {}
F.prototype = Parent.prototype
Child.prototype = new F()
Child.prototype.constructor = Child
Child.uber = Parent.prototype
}
然後就可以這樣呼叫:
extend(Dog, Animal)
4.屬性拷貝:
這種繼承方式基本上沒有改變原型鏈的關係,而是直接將父級原型物件中的屬性全部複製到子物件原型中,當然,這裡的複製僅適用於基本資料類型,物件類型只支持引用傳遞。
function extend2(Child, Parent) {
var p = Parent.prototype
var c = Child.prototype
for (var i in p) {
c[i] = p[i]
}
c.uber = p
}
這種方式對部分原型屬性進行了重建,建構物件的時候效率會低一些,但是能夠減少原型鏈的查找。不過我個人覺得這種方式的優點並不明顯。
5.物件間繼承:
除了基於構造器間的繼承方法,還可以拋開構造器直接進行物件間的繼承。即直接進行物件屬性的拷貝,其中包括淺拷貝和深拷貝。
淺拷貝:
接受要繼承的對象,同時建立一個新的空對象,將要繼承對象的屬性拷貝至新對象並傳回這個新對象:
function extendCopy(p) {
var c = {}
for (var i in p) {
c[i] = p[i]
}
c.uber = p
return c
}
拷貝完成之後對於新物件中需要改寫的屬性可以進行手動改寫。
深拷貝:
淺拷貝的問題也顯而易見,它不能拷貝物件類型的屬性而只能傳遞引用,要解決這個問題就要使用深拷貝。深拷貝的重點在於拷貝的遞歸調用,檢測到物件類型的屬性時就會建立對應的物件或數組,並逐一複製其中的基本類型值。
function deepCopy(p, c) {
c = c || {}
for (var i in p) {
if (p.hasOwnProperty(i)) {
if (typeof p[i] === 'object') {
c[i] = Array.isArray(p[i]) ? [] : {}
deepCopy(p[i], c[i])
} else {
c[i] = p[i]
}
}
}
return c
}
其中用到了一個 ES5 的Array.isArray()方法用來判斷參數是否為數組,沒有實作此方法的環境需要自己手動封裝一個 shim。
Array.isArray = function(p) {
return p instanceof Array
}
但是使用instanceof運算子無法判斷來自不同框架的陣列變量,但這種情況比較少。
6.原型繼承:
借助父級對象,透過建構函式建立一個以父級物件為原型的新物件:
function object(o) {
var n
function F() {}
F.prototype = o
n = new F()
n.uber = o
return n
}
這裡,直接將父物件設定為子物件的原型,ES5 中的 Object.create()方法就是這種實作方式。
7.原型繼承與屬性拷貝混合:
原型繼承方法中以傳入的父對象為原型建構子對象,同時還可以在父對象提供的屬性之外額外傳入需要拷貝屬性的對象:
function ojbectPlus(o, stuff) {
var n
function F() {}
F.prototype = o
n = new F()
n.uber = o
for (var i in stuff) {
n[i] = stuff[i]
}
return n
}
8.多重繼承:
這種方式不涉及原型鏈的操作,傳入多個需要拷貝屬性的對象,依序進行屬性的全拷貝:
function multi() {
var n = {}, stuff, i = 0,
len = arguments.length
for (i = 0; i stuff = arguments[i]
for (var key in stuff) {
n[i] = stuff[i]
}
}
return n
}
根據物件傳入的順序依序進行拷貝,也就是說,如果後傳入的物件包含和前面物件相同的屬性,後者將會覆寫前者。
9.構造器借用:
JavaScript中的call()和apply()方法非常好用,其改變方法執行上下文的功能在繼承的實作中也能發揮作用。所謂建構子借用是指在子物件建構器中藉用父物件的建構子對this進行操作:
function Parent() {}
Parent.prototype.name = 'parent'
function Child() {
Parent.apply(this, arguments)
}
var child = new Child()
console.log(child.name)
這種方式的最大優勢就是,在子物件的建構器中,是對子物件的自身屬性進行完全的重建,引用類型的變數也會產生一個新值而不是一個引用,所以對子物件的任何操作都不會影響父對象。
而這種方法的缺點在於,在子物件的建置過程中沒有使用過new操作符,因此子物件不會繼承父級原型物件上的任何屬性,在上面的程式碼中,child的name屬性將會是undefined。
要解決這個問題,可以再次手動將子物件建構器原型設為父物件的實例:
Child.prototype = new Parent()
但這又會帶來另一個問題,即父物件的建構器會被呼叫兩次,一次是在父物件建構器借用過程中,另一次是在繼承原型過程中。
要解決這個問題,就要去掉一次父物件建構器的調用,建構器借用不能省略,那麼只能去掉後一次調用,實現繼承原型的另一方法就是迭代複製:
extend2(Child, Parent)
使用之前實作的extend2()方法即可。

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

在函數繼承中,使用「基底類別指標」和「衍生類別指標」來理解繼承機制:基底類別指標指向派生類別物件時,執行向上轉型,只存取基底類別成員。派生類別指標指向基底類別物件時,執行向下轉型(不安全),必須謹慎使用。

繼承和多態性會影響類別的耦合度:繼承會增加耦合度,因為衍生類別依賴基底類別。多態性可以降低耦合度,因為物件可以透過虛擬函數和基底類別指標以一致的方式回應訊息。最佳實踐包括謹慎使用繼承、定義公共介面、避免在基底類別中新增資料成員,以及透過依賴注入解耦類別。實戰案例顯示如何使用多態性和依賴注入來降低銀行帳戶應用程式中的耦合度。

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

繼承錯誤調試技巧:確保正確的繼承關係。使用偵錯器逐步執行程式碼,檢查變數值。確保正確使用virtual修飾符。檢查隱藏的繼承帶來的菱形繼承問題。檢查抽象類別中未實現的純虛函數。

C++函式繼承詳解:掌握「is-a」和「has-a」關係什麼是函式繼承?函數繼承是C++中一種將衍生類別中定義的方法與基底類別中定義的方法關聯起來的技術。它允許衍生類別存取和重寫基底類別的方法,從而擴展了基底類別的功能。 「is-a」和「has-a」關係在函數繼承中,「is-a」關係指派生類別是基底類別的子類型,也就是說,衍生類別「繼承」了基底類別的特性和行為。 「has-a」關係指派生類別包含對基底類別物件的參考或指針,也就是說,衍生類別「擁有」了基底類別物件。語法以下是如何實作函數繼承的語法:classDerivedClass:pu

在以下情況下不應使用C++函數繼承:衍生類別需要不同實作時,應建立具有不同實作的新函數。衍生類別不需要函數時,應宣告為一個空類別或使用私有、未實作的基底類別成員函數來停用函數繼承。函數不需要繼承時,應使用其他機制(例如範本)來實作程式碼重用。

什麼是物件導向程式設計?物件導向程式設計(OOP)是一種程式設計範式,它將現實世界中的實體抽象化為類,並使用物件來表示這些實體。類別定義了物件的屬性和行為,而物件則實例化了類別。 OOP的主要優點在於它可以使程式碼更易於理解、維護和重複使用。 OOP的基本概念OOP的主要概念包括類別、物件、屬性和方法。類別是物件的藍圖,它定義了物件的屬性和行為。物件是類別的實例,它具有類別的所有屬性和行為。屬性是物件的特徵,它可以儲存資料。方法是物件的函數,它可以對物件的資料進行操作。 OOP的優點OOP的主要優點包括:可重複使用性:OOP可以讓程式碼更

介面:無實作的契約介面在Java中定義了一組方法簽名,但不提供任何具體實作。它充當一種契約,強制實作該介面的類別實現其指定的方法。介面中的方法是抽象方法,沒有方法體。程式碼範例:publicinterfaceAnimal{voideat();voidsleep();}抽象類別:部分實作的藍圖抽象類別是一種父類,它提供了一個部分實現,可以被它的子類別繼承。與介面不同,抽象類別可以包含具體的實作和抽象方法。抽象方法是用abstract關鍵字聲明的,並且必須被子類別覆蓋。程式碼範例:publicabstractcla
