2.4組合使用構(gòu)造函數(shù)模式和原型模式
綜合使用構(gòu)造函數(shù)和原型模式可以避免之前二者的問題谨履,而取其長處俱箱。
通常來說陡厘,我們使用構(gòu)造函數(shù)模式定義實(shí)例屬性,而原型模式定義公用的方法和屬性扶歪。這樣做之后理肺,每個(gè)實(shí)例都會有一份只屬于自己的屬性副本,同時(shí)又可以調(diào)用共有方法:
在這里善镰,我們需要的實(shí)例屬性都是在構(gòu)造函數(shù)中定義的妹萨,而實(shí)例共享的屬性和方法則是在原型中定義的。
2.5動態(tài)原型模式
js的原型模式和其他OO語言最大的不同就是構(gòu)造函數(shù)和原型分別獨(dú)立炫欺,這就是動態(tài)原型模式致力于解決的方案乎完。動態(tài)原型模式將所有信息封裝在構(gòu)造函數(shù)中,通過構(gòu)造函數(shù)在必要情況下初始化原型:
在動態(tài)原型模式中,通常會使用instanceof和typeof進(jìn)行類型檢測桥状。并且需要注意帽揪,在使用動態(tài)原型模式時(shí),不能使用對象字面量重寫原型岛宦,原因請參照2020-01-02中有關(guān)重寫原型時(shí)可能會切斷既有實(shí)例和原型之間的聯(lián)系台丛。
2.6寄生構(gòu)造函數(shù)模式
這種使用方式通常用來創(chuàng)建一個(gè)基于既有對象耍缴,而又比既有對象多一些額外方法或?qū)傩缘膶ο罄巍Ee個(gè)例子,我們需要建設(shè)一個(gè)具有額外方法的特殊數(shù)組防嗡,那么就可以使用寄生構(gòu)造函數(shù)模式变汪。
寄生構(gòu)造函數(shù)模式的思想與工廠模式幾乎相同:在構(gòu)造函數(shù)內(nèi)new一個(gè)既有對象,通過對這個(gè)既有對象的實(shí)例增加新的屬性或方法蚁趁,達(dá)到不修改既有對象的構(gòu)造函數(shù)便可以添加新的方法裙盾。
舉個(gè)例子,假如我們需要一個(gè)具有額外方法的特殊數(shù)組:
2.7穩(wěn)妥構(gòu)造函數(shù)模式
穩(wěn)妥對象庐完,指的是沒有公共屬性,其方法也不引用this對象徘熔。通常會被使用在一些安全的和環(huán)境中门躯。定義在其中的屬性通常只能用添加的方法訪問(類似私有屬性的概念)
看一個(gè)例子:
3.繼承
一般的OO語言繼承有兩種方式:接口繼承和實(shí)現(xiàn)繼承酷师。接口繼承只繼承方法簽名讶凉,而實(shí)際繼承則繼承實(shí)際方法。但是因?yàn)樵趈s中函數(shù)沒有簽名山孔,因此js只支持實(shí)現(xiàn)繼承懂讯。繼承的主要原理是依賴原型鏈實(shí)現(xiàn)。
3.1原型鏈
原型鏈?zhǔn)菍?shí)現(xiàn)js中繼承的主要方法台颠。其基本四項(xiàng)是利用原型讓一個(gè)引用類型繼承另外一個(gè)引用類型的屬性和方法褐望。
首先簡單回顧以下構(gòu)造函數(shù),原型對象串前,和實(shí)例之間的關(guān)系:
一個(gè)構(gòu)造函數(shù)有一個(gè)原型對象譬挚,構(gòu)造函數(shù)可以通過prototype訪問原型對象;
一個(gè)原型對象包括一個(gè)指向構(gòu)造函數(shù)的指針constructor酪呻;
實(shí)例包含一個(gè)指向原型對象的指針减宣;
假如我們讓原型對象A等于原型對象B的實(shí)例b,則原型對象A同時(shí)也是原型對象B的實(shí)例b玩荠,因此其內(nèi)就會包括一個(gè)指向原型對象B的指針漆腌。相應(yīng)的,如果原型對象B等于原型對象C的實(shí)例c阶冈,則原型對象B同時(shí)也是原型對象C的實(shí)例c闷尿,因此其內(nèi)部就會包括一個(gè)指向原型對象C的指針女坑。這樣層層遞進(jìn),就構(gòu)成了實(shí)例與原型的鏈條匆骗。
下面我們看一個(gè)實(shí)現(xiàn):
在這個(gè)例子中我們定義了兩個(gè)類型father和child碉就,child繼承了father的方法和屬性。繼承的過程是通過將child的原型對象重寫為Father的實(shí)例完成的瓮钥。本質(zhì)是重寫原型對象筋量,以一個(gè)新類型的實(shí)例代替。
此時(shí)我們也可以在child中再添加其他方法:
通過一張關(guān)系圖可以明晰child和father之間的關(guān)系:
這其中的SubType.prototype既是instance實(shí)例的原型,又是SuperType.prototype的實(shí)例呀酸。因此他擁有一個(gè)實(shí)例才擁有的[[prototype]]的指針,這個(gè)指針指向它的原型(SuperType.prototype),同時(shí)它的實(shí)例instance的[[prototype]]則指向它七咧。
此時(shí)如果調(diào)用instance.subproperty屬性,則調(diào)用了一層搜索(搜索實(shí)例)艾栋。
如果調(diào)用instance.getSubValue()方法,實(shí)際上是我們之前介紹過的二層搜索(搜索實(shí)例的原型)蝗砾。
而調(diào)用instance.getSuperValue()方法,實(shí)際上是第三層搜索(搜索實(shí)例的原型悼粮,而將實(shí)例的原型作為實(shí)例2闲勺,搜索實(shí)例2的原型)扣猫。
因此通過實(shí)現(xiàn)原型鏈菜循,本質(zhì)上是擴(kuò)展了前面我們介紹的原型搜索機(jī)制。
1.1別忘記默認(rèn)的原型
實(shí)際上在上面的原型鏈中還少了一環(huán)申尤,我們知道所有引用類型都默認(rèn)繼承了Object癌幕,這個(gè)繼承也是通過原型鏈實(shí)現(xiàn)的,因此對于SuperType.Prototype,它實(shí)際上也是Object的一個(gè)實(shí)例昧穿。它的[[Prototype]]實(shí)際上指向了Object.prototype:
這也正是所有自定義類型都會繼承toString,valueOf,isPrototypeOf這些方法的根本原因勺远,實(shí)際上,我們在訪問instance.toString()時(shí)时鸵,本質(zhì)上都是通過4次搜索后胶逢,調(diào)用了Object.prototype中的方法。
我們可以看到饰潜,只有底層的SubType.Prototype沒有constructor指向其構(gòu)造函數(shù)初坠,這是因?yàn)镾ubType.Prototype是我們?nèi)斯ぶ貙戇^的,因此它的constructor不指向其構(gòu)造函數(shù)囊拜。
2.確定原型和實(shí)例的關(guān)系
子類繼承父類后,如果使用instanceof測試針對Object冠跷,F(xiàn)ather南誊,child三個(gè)類型,都會返回true蜜托。
同樣同一個(gè)屬性isPrototypeOf()方法抄囚,只要時(shí)原型鏈中出現(xiàn)的原型,都時(shí)該原型鏈所派生的實(shí)例的原型橄务。如果使用isPrototypeOf()測試針對Object幔托,F(xiàn)ather,child三個(gè)類型蜂挪,都會返回true重挑。
3.謹(jǐn)慎定義方法
在這里只要強(qiáng)調(diào)無論如何,給原型添加方法的代碼一定要放在替換原型的語句之后棠涮。換個(gè)更淺顯的說法:給子類加方法一定要在重寫子類之后谬哀。
重寫父類方法的本質(zhì)其實(shí)是在二層搜索時(shí)就找到了getValue(),因此解釋器也就不會再進(jìn)行三層搜索严肪,去Father.prototype中找getValue()方法了史煎。
同樣,需要注意的是篇梭,在給子類添加屬性或方法的時(shí)候,不能使用字面量法添加新屬性和方法恬偷。因?yàn)槲覀冎溃置媪糠ū举|(zhì)上也是在重寫原型對象喉磁。這會導(dǎo)致[[prototype]]指針失靈官脓。
4.原型鏈的問題
原型鏈的問題之一與之前一樣,都來自于包含引用類型值的原型卑笨。我們知道,原型中引用類型的屬性會被所有實(shí)例共享妖滔。這會導(dǎo)致在通過原型來實(shí)現(xiàn)繼承時(shí)桶良,原型實(shí)際上會變成另外一個(gè)類型的實(shí)例,原先的實(shí)例屬性(this.colors)也就順理成章地變成了現(xiàn)在的原型屬性(Subtype.prototype.colors)了陨帆。
因?yàn)镾ubType通過原型鏈繼承了SuperType后承二,SubType.propotype就變成了SuperType的實(shí)例,因此它也擁有了colors屬性亥鸠。這和專門創(chuàng)建了一個(gè)SubType.prototype.colors屬性是一個(gè)道理。因此所有它的實(shí)現(xiàn)就會共享這個(gè)colors屬性神妹,這本質(zhì)上和原型的問題是一致的家妆。
原型的問題之二是在創(chuàng)建字類型的實(shí)例時(shí),無法向父類型的構(gòu)造函數(shù)中傳遞參數(shù)揩徊,類似java中super的用法。如果強(qiáng)行這樣做塑荒,則很有可能印象已經(jīng)實(shí)現(xiàn)的實(shí)例。
基于這兩個(gè)問題彼硫,我們在實(shí)踐中通常很少會單獨(dú)使用原型鏈凌箕。