不同於基於類別的程式語言,如 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()方法即可。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務


熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

Dreamweaver CS6
視覺化網頁開發工具

Atom編輯器mac版下載
最受歡迎的的開源編輯器

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

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

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中