3.2借用構造函數(shù)
因為原型帶有的引用值問題西剥,開發(fā)人員開始使用借用構造函數(shù)的方法(又被稱為偽造對象或者經(jīng)典繼承)寿弱。它會在子類型構造函數(shù)的內(nèi)部調(diào)用超類型的構造函數(shù)瑞侮。
先來看一個例子:
可以知道哆键,在第一次調(diào)用Child()時,其據(jù)以引用的執(zhí)行環(huán)境是全局環(huán)境卦睹,在第二次通過構造函數(shù)調(diào)用Child()時畦戒,其據(jù)以引用的執(zhí)行環(huán)境是a實例,因此this.constructor指向了Child函數(shù)本身(constructor指向的是生成實例的函數(shù)本身)结序。
此時兢交,我們實際上是在(未來將要)新創(chuàng)建的Child實例的環(huán)境下調(diào)用了Father的構造函數(shù),F(xiàn)ather.call(this)在意義上約等于a.Father()笼痹。
這樣一來配喳,就可以在新的對象上執(zhí)行父函數(shù),通過在子類作用環(huán)境中調(diào)用父類構造函數(shù)定義的friend屬性凳干,和直接在實例中定義的friend屬性一致晴裹,都只存在于Child實例中而不存在于任何原型中,避免了繼承所面臨的引用值問題救赐。
1.傳遞參數(shù)
相對于原型鏈而言涧团,借用構造函數(shù)可以在字類型構造函數(shù)中向超類型(也叫父類型)構造函數(shù)傳遞參數(shù)。
如果為了確保父類寫入屬性時不會覆蓋子類经磅,可以在調(diào)用父類構造函數(shù)之后再為子類設置屬性泌绣。
2.借用構造函數(shù)的問題
構造函數(shù)法無法復用函數(shù)。
但是僅僅借用構造函數(shù)预厌,無法避免構造函數(shù)模式存在的問題:一切都在構造函數(shù)中定義阿迈,函數(shù)復用便無從談起。而且在超超類型的原型中定義方法轧叽,對子類型而言也時不可見的苗沧。子類無法繼承超類的原型中定義的方法,因此這種方式在實際使用中也不經(jīng)常炭晒。
3.3組合繼承(必會)
組合繼承指的是將原型鏈和借用構造函數(shù)兩種方法組合待逞,其思路是:
使用原型鏈實現(xiàn)對原型屬性和方法的繼承。
而通過借用構造函數(shù)來實現(xiàn)對實例屬性的繼承网严。
通過組合繼承的方式识樱,我們可以使得a和b兩個Child的實例分別擁有自己的屬性副本,同時共享一套方法震束。這種方式是js中最常用的繼承模式(必會)
3.4原型式繼承
原型式繼承是基于已有的實例創(chuàng)建新對象的方法怜庸。
在object的內(nèi)部,我們首先創(chuàng)建一個臨時性的構造函數(shù)驴一,然后將傳入的實例作為臨時構造函數(shù)的原型休雌,最后返回這個臨時類型的一個實例灶壶。從本質上講肝断,實際上是對傳入的對象做了一次淺拷貝。
這個方法與之前介紹過的Object.create()方法原理上相同,這個方法接受兩個參數(shù):一個用作新對象原型的對象胸懈,和一個可選的担扑,作為新對象定義額外屬性的對象。在只傳入一個對象時趣钱,create()方法和原型式繼承完全一致涌献。
使用情況:在沒有必要興師動眾的創(chuàng)建構造函數(shù)首有,而只是想創(chuàng)建一個與當前已有對象類似的對象的話燕垃,則可以使用原型式繼承,但是要記住井联,原型式繼承也有引用值共享的問題卜壕。
3.5寄生式繼承
寄生式繼承與寄生構造函數(shù)和工廠模式思想非常類似:創(chuàng)建一個僅用于封裝繼承過程的函數(shù),繼承的過程通過繼承構造函數(shù)實現(xiàn)烙常,并在函數(shù)的內(nèi)部增強對象轴捎,最后再將對象返回,即便繼承的過程是完全通過繼承構造函數(shù)實現(xiàn)的蚕脏。
因此我們可以將寄生式繼承理解為繼承構造函數(shù)方法的擴展侦副。
3.6寄生組合繼承
上文講過,組合繼承是js中最常用的繼承模式驼鞭,但它也有自己的不足秦驯。它的不足在于無論任何情況下,都會調(diào)用兩次超類型(父類型)的構造函數(shù):一次是在創(chuàng)建子類型原型的時候挣棕,一次是在子類型構造函數(shù)內(nèi)部:
雖然子類型最終會包括超類型對象的全部實例屬性汇竭,但我們在子類構造函數(shù)中調(diào)用父類構造函數(shù)(第二次調(diào)用)還是不得不重寫和父類有關的屬性。
按照上面的例子:
第一次調(diào)用父類構造函數(shù)時穴张,Child.prototype獲得兩個屬性friend和parents细燎。他們都將是子類的實例屬性,只不過現(xiàn)在還位于原型中皂甘。
當創(chuàng)建實例時玻驻,我們會在子類構造函數(shù)中再次調(diào)用父類構造函數(shù),這一次又在新實例上創(chuàng)建了實例屬性parents和friend偿枕。
于是璧瞬,新實例上的parents和friend就屏蔽了原型中的同名屬性:我們現(xiàn)在擁有兩組parents和friend屬性:一組在原型上,一組在實例上渐夸。
通過寄生組合繼承的方式嗤锉,可以通過原型鏈的混成形式來繼承方法:即通過借用構造函數(shù)來繼承屬性,通過原型鏈的混成形式來繼承方法墓塌。我們不必為了指定子類型的原型而調(diào)用父類型的構造函數(shù)(消除第一次調(diào)用)瘟忱,我們所需要的只是超類型原型的副本而已奥额。
這種方式的本質是用寄生式繼承來繼承超類型的原型,然后再將結果指定給子類型的原型:
這個方法的高效體現(xiàn)在他只調(diào)用了一次父類構造函數(shù)访诱。因此避免了在子類原型上創(chuàng)建不必要的垫挨,多余的屬性。
寄生組合繼承毫無疑問是引用各類型最理想的繼承范式触菜。
6.4小結
ECMA支持面向對象編程九榔,但是不使用接口,只支持完全繼承涡相。對象可以在代碼執(zhí)行過程中創(chuàng)建和增強哲泊,因此動態(tài)性極強。常用的有如下幾種創(chuàng)建對象的模式:
①.工廠模式(函數(shù)創(chuàng)建對象催蝗,函數(shù)內(nèi)為對象添加屬性和方法攻旦,然后返回對象)
②.構造函數(shù)模式(創(chuàng)建自定義引用,將新建的實例作為函數(shù)的環(huán)境對象)缺點為函數(shù)不共享(多個實例創(chuàng)建多個函數(shù)對象生逸,而不是多個實例共享一個函數(shù)對象)
③.原型模式(用構造函數(shù)的原型來指定應該共享的屬性和方法)牢屋,相比構造函數(shù)模式,多個實例共享同一組方法(來自原型)
除此之外槽袄,js主要通過原型鏈實現(xiàn)繼承烙无,原型鏈的核心是將父類型的實例賦值給子類型的原型。這樣子類型可以訪問父類型的所有屬性和方法遍尺。但是原型鏈的繼承會導致多個實例共用一個原型的引用類型屬性截酷,因此不宜單獨使用。
解決這個問題的方法是借用構造函數(shù)乾戏,我們在子類型構造函數(shù)內(nèi)部調(diào)用父類型構造函數(shù)迂苛,這樣就可以做到每個實例的屬性獨立。我們使用最多的就是這種組合繼承的方式鼓择。這種模式的核心是 用原型鏈繼承共享的屬性和方法三幻,再通過借用構造函數(shù)繼承實例獨有的屬性。此外呐能,我們還有幾種擴展的繼承模式:
①.原型式繼承念搬,在想要根據(jù)現(xiàn)有對象復制一個類似的對象時,可以使用這種方式摆出,其本質是對已有對象的淺拷貝:
②.寄生式繼承朗徊,與原型式繼承相似,僅僅是用一個函數(shù)內(nèi)部封裝了原型式繼承的過程偎漫,并且將對對象的增強也封裝在了函數(shù)內(nèi)部爷恳,最后返回。
③.集寄生式繼承和組合繼承的優(yōu)點于一身象踊,式當前最好的温亲,最有效率的繼承方式棚壁。