JavaScript的六種繼承方式(推薦)
繼承是面向?qū)ο缶幊讨杏忠环浅V匾母拍?,JavaScript支持實(shí)現(xiàn)繼承,不支持接口繼承,實(shí)現(xiàn)繼承主要依靠原型鏈來(lái)實(shí)現(xiàn)的。
原型鏈
首先得要明白什么是原型鏈,在一篇文章看懂proto和prototype的關(guān)系及區(qū)別中講得非常詳細(xì)
原型鏈繼承基本思想就是讓一個(gè)原型對(duì)象指向另一個(gè)類型的實(shí)例
function SuperType() { this.property = true } SuperType.prototype.getSuperValue = function () { return this.property } function SubType() { this.subproperty = false } SubType.prototype = new SuperType() SubType.prototype.getSubValue = function () { return this.subproperty } var instance = new SubType() console.log(instance.getSuperValue()) // true
代碼定義了兩個(gè)類型SuperType和SubType,每個(gè)類型分別有一個(gè)屬性和一個(gè)方法,SubType繼承了SuperType,而繼承是通過(guò)創(chuàng)建SuperType的實(shí)例,并將該實(shí)例賦給SubType.prototype實(shí)現(xiàn)的。
實(shí)現(xiàn)的本質(zhì)是重寫原型對(duì)象,代之以一個(gè)新類型的實(shí)例,那么存在SuperType的實(shí)例中的所有屬性和方法,現(xiàn)在也存在于SubType.prototype中了。
我們知道,在創(chuàng)建一個(gè)實(shí)例的時(shí)候,實(shí)例對(duì)象中會(huì)有一個(gè)內(nèi)部指針指向創(chuàng)建它的原型,進(jìn)行關(guān)聯(lián)起來(lái),在這里代碼SubType.prototype = new SuperType(),也會(huì)在SubType.prototype創(chuàng)建一個(gè)內(nèi)部指針,將SubType.prototype與SuperType關(guān)聯(lián)起來(lái)。
所以instance指向SubType的原型,SubType的原型又指向SuperType的原型,繼而在instance在調(diào)用getSuperValue()方法的時(shí)候,會(huì)順著這條鏈一直往上找。
添加方法
在給SubType原型添加方法的時(shí)候,如果,父類上也有同樣的名字,SubType將會(huì)覆蓋這個(gè)方法,達(dá)到重新的目的。 但是這個(gè)方法依然存在于父類中。
記住不能以字面量的形式添加,因?yàn)?,上面說(shuō)過(guò)通過(guò)實(shí)例繼承本質(zhì)上就是重寫,再使用字面量形式,又是一次重寫了,但這次重寫沒(méi)有跟父類有任何關(guān)聯(lián),所以就會(huì)導(dǎo)致原型鏈截?cái)唷?/p>
function SuperType() { this.property = true } SuperType.prototype.getSuperValue = function () { return this.property } function SubType() { this.subproperty = false } SubType.prototype = new SuperType() SubType.prototype = { getSubValue:function () { return this.subproperty } } var instance = new SubType() console.log(instance.getSuperValue()) // error
問(wèn)題
單純的使用原型鏈繼承,主要問(wèn)題來(lái)自包含引用類型值的原型。
function SuperType() { this.colors = ['red', 'blue', 'green'] } function SubType() { } SubType.prototype = new SuperType() var instance1 = new SubType() var instance2 = new SubType() instance1.colors.push('black') console.log(instance1.colors) // ["red", "blue", "green", "black"] console.log(instance2.colors) // ["red", "blue", "green", "black"]
在SuperType構(gòu)造函數(shù)定義了一個(gè)colors屬性,當(dāng)SubType通過(guò)原型鏈繼承后,這個(gè)屬性就會(huì)出現(xiàn)SubType.prototype中,就跟專門創(chuàng)建了SubType.prototype.colors一樣,所以會(huì)導(dǎo)致SubType的所有實(shí)例都會(huì)共享這個(gè)屬性,所以instance1修改colors這個(gè)引用類型值,也會(huì)反映到instance2中。
借用構(gòu)造函數(shù)
此方法為了解決原型中包含引用類型值所帶來(lái)的問(wèn)題。
這種方法的思想就是在子類構(gòu)造函數(shù)的內(nèi)部調(diào)用父類構(gòu)造函數(shù),可以借助apply()和call()方法來(lái)改變對(duì)象的執(zhí)行上下文
function SuperType() { this.colors = ['red', 'blue', 'green'] } function SubType() { // 繼承SuperType SuperType.call(this) } var instance1 = new SubType() var instance2 = new SubType() instance1.colors.push('black') console.log(instance1.colors) // ["red", "blue", "green", "black"] console.log(instance2.colors) // ["red", "blue", "green"]
在新建SubType實(shí)例是調(diào)用了SuperType構(gòu)造函數(shù),這樣以來(lái),就會(huì)在新SubType對(duì)象上執(zhí)行SuperType函數(shù)中定義的所有對(duì)象初始化代碼。
結(jié)果,SubType的每個(gè)實(shí)例就會(huì)具有自己的colors屬性的副本了。
傳遞參數(shù)
借助構(gòu)造函數(shù)還有一個(gè)優(yōu)勢(shì)就是可以傳遞參數(shù)
function SuperType(name) { this.name = name } function SubType() { // 繼承SuperType SuperType.call(this, 'Jiang') this.job = 'student' } var instance = new SubType() console.log(instance.name) // Jiang console.log(instance.job) // student
問(wèn)題
如果僅僅借助構(gòu)造函數(shù),方法都在構(gòu)造函數(shù)中定義,因此函數(shù)無(wú)法達(dá)到復(fù)用
組合繼承(原型鏈+構(gòu)造函數(shù))
組合繼承是將原型鏈繼承和構(gòu)造函數(shù)結(jié)合起來(lái),從而發(fā)揮二者之長(zhǎng)的一種模式。
思路就是使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。
這樣,既通過(guò)在原型上定義方法實(shí)現(xiàn)了函數(shù)復(fù)用,又能夠保證每個(gè)實(shí)例都有它自己的屬性。
function SuperType(name) { this.name = name this.colors = ['red', 'blue', 'green'] } SuperType.prototype.sayName = function () { console.log(this.name) } function SubType(name, job) { // 繼承屬性 SuperType.call(this, name) this.job = job } // 繼承方法 SubType.prototype = new SuperType() SubType.prototype.constructor = SuperType SubType.prototype.sayJob = function() { console.log(this.job) } var instance1 = new SubType('Jiang', 'student') instance1.colors.push('black') console.log(instance1.colors) //["red", "blue", "green", "black"] instance1.sayName() // 'Jiang' instance1.sayJob() // 'student' var instance2 = new SubType('J', 'doctor') console.log(instance2.colors) // //["red", "blue", "green"] instance2.sayName() // 'J' instance2.sayJob() // 'doctor'
這種模式避免了原型鏈和構(gòu)造函數(shù)繼承的缺陷,融合了他們的優(yōu)點(diǎn),是最常用的一種繼承模式。
原型式繼承
借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類型。
function object(o) { function F() {} F.prototype = o return new F() }
在object函數(shù)內(nèi)部,先創(chuàng)建一個(gè)臨時(shí)性的構(gòu)造函數(shù),然后將傳入的對(duì)象作為這個(gè)構(gòu)造函數(shù)的原型,最后返回這個(gè)臨時(shí)類型的一個(gè)新實(shí)例。
本質(zhì)上來(lái)說(shuō),object對(duì)傳入其中的對(duì)象執(zhí)行了一次淺復(fù)制。
var person = { name: 'Jiang', friends: ['Shelby', 'Court'] } var anotherPerson = object(person) console.log(anotherPerson.friends) // ['Shelby', 'Court']
這種模式要去你必須有一個(gè)對(duì)象作為另一個(gè)對(duì)象的基礎(chǔ)。
在這個(gè)例子中,person作為另一個(gè)對(duì)象的基礎(chǔ),把person傳入object中,該函數(shù)就會(huì)返回一個(gè)新的對(duì)象。
這個(gè)新對(duì)象將person作為原型,所以它的原型中就包含一個(gè)基本類型和一個(gè)引用類型。
所以意味著如果還有另外一個(gè)對(duì)象關(guān)聯(lián)了person,anotherPerson修改數(shù)組friends的時(shí)候,也會(huì)體現(xiàn)在這個(gè)對(duì)象中。
Object.create()方法
ES5通過(guò)Object.create()方法規(guī)范了原型式繼承,可以接受兩個(gè)參數(shù),一個(gè)是用作新對(duì)象原型的對(duì)象和一個(gè)可選的為新對(duì)象定義額外屬性的對(duì)象,行為相同,基本用法和上面的object一樣,除了object不能接受第二個(gè)參數(shù)以外。
var person = { name: 'Jiang', friends: ['Shelby', 'Court'] } var anotherPerson = Object.create(person) console.log(anotherPerson.friends) // ['Shelby', 'Court']
寄生式繼承
寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類似,即創(chuàng)建一個(gè)僅用于封裝繼承過(guò)程的函數(shù)。
function createAnother(o) { var clone = Object.create(o) // 創(chuàng)建一個(gè)新對(duì)象 clone.sayHi = function() { // 添加方法 console.log('hi') } return clone // 返回這個(gè)對(duì)象 } var person = { name: 'Jiang' } var anotherPeson = createAnother(person) anotherPeson.sayHi()
基于person返回了一個(gè)新對(duì)象anotherPeson,新對(duì)象不僅擁有了person的屬性和方法,還有自己的sayHi方法。
在主要考慮對(duì)象而不是自定義類型和構(gòu)造函數(shù)的情況下,這是一個(gè)有用的模式。
寄生組合式繼承
在前面說(shuō)的組合模式(原型鏈+構(gòu)造函數(shù))中,繼承的時(shí)候需要調(diào)用兩次父類構(gòu)造函數(shù)。
父類
function SuperType(name) { this.name = name this.colors = ['red', 'blue', 'green'] }
第一次在子類構(gòu)造函數(shù)中
function SubType(name, job) { // 繼承屬性 SuperType.call(this, name) this.job = job }
第二次將子類的原型指向父類的實(shí)例
// 繼承方法 SubType.prototype = new SuperType()
當(dāng)使用var instance = new SubType()
的時(shí)候,會(huì)產(chǎn)生兩組name和color屬性,一組在SubType實(shí)例上,一組在SubType原型上,只不過(guò)實(shí)例上的屏蔽了原型上的。
使用寄生式組合模式,可以規(guī)避這個(gè)問(wèn)題。
這種模式通過(guò)借用構(gòu)造函數(shù)來(lái)繼承屬性,通過(guò)原型鏈的混成形式來(lái)繼承方法。
基本思路:不必為了指定子類型的原型而調(diào)用父類的構(gòu)造函數(shù),我們需要的無(wú)非就是父類原型的一個(gè)副本。
本質(zhì)上就是使用寄生式繼承來(lái)繼承父類的原型,在將結(jié)果指定給子類型的原型。
function inheritPrototype(subType, superType) { var prototype = Object.create(superType.prototype) prototype.constructor = subType subType.prototype = prototype }
該函數(shù)實(shí)現(xiàn)了寄生組合繼承的最簡(jiǎn)單形式。
這個(gè)函數(shù)接受兩個(gè)參數(shù),一個(gè)子類,一個(gè)父類。
第一步創(chuàng)建父類原型的副本,第二步將創(chuàng)建的副本添加constructor屬性,第三部將子類的原型指向這個(gè)副本。
function SuperType(name) { this.name = name this.colors = ['red', 'blue', 'green'] } SuperType.prototype.sayName = function () { console.log(this.name) } function SubType(name, job) { // 繼承屬性 SuperType.call(this, name) this.job = job } // 繼承 inheritPrototype(SubType, SuperType) var instance = new SubType('Jiang', 'student') instance.sayName()
補(bǔ)充:直接使用Object.create來(lái)實(shí)現(xiàn),其實(shí)就是將上面封裝的函數(shù)拆開(kāi),這樣演示可以更容易理解。
function SuperType(name) { this.name = name this.colors = ['red', 'blue', 'green'] } SuperType.prototype.sayName = function () { console.log(this.name) } function SubType(name, job) { // 繼承屬性 SuperType.call(this, name) this.job = job } // 繼承 SubType.prototype = Object.create(SuperType.prototype) // 修復(fù)constructor SubType.prototype.constructor = SubType var instance = new SubType('Jiang', 'student') instance.sayName()
ES6新增了一個(gè)方法,Object.setPrototypeOf,可以直接創(chuàng)建關(guān)聯(lián),而且不用手動(dòng)添加constructor屬性。
// 繼承 Object.setPrototypeOf(SubType.prototype, SuperType.prototype) console.log(SubType.prototype.constructor === SubType) // true
以上所述是小編給大家介紹的JavaScript的六種繼承方式(推薦),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
JavaScript flash復(fù)制庫(kù)類 Zero Clipboard
開(kāi)發(fā)中經(jīng)常會(huì)用到復(fù)制的功能,在 IE 下實(shí)現(xiàn)比較簡(jiǎn)單。但要想做到跨瀏覽器比較困難了。2011-01-01JavaScript數(shù)據(jù)類型和變量_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了JavaScript數(shù)據(jù)類型和變量的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06ajax跨域調(diào)用webservice的實(shí)現(xiàn)代碼
這篇文章主要介紹了 ajax跨域調(diào)用webservice服務(wù)例子和理解,最近ajax訪問(wèn)webservice遇到跨域的問(wèn)題,網(wǎng)上搜索資料,總結(jié)如下2016-05-05解讀new?Object()和Object.create()的區(qū)別
這篇文章主要介紹了解讀new?Object()和Object.create()的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02List the Codec Files on a Computer
List the Codec Files on a Computer...2007-06-06前端不使用i18n該如何優(yōu)雅的實(shí)現(xiàn)多語(yǔ)言
多語(yǔ)言的重要性相信不需要多言,下面這篇文章主要給大家介紹了關(guān)于前端不使用i18n該如何優(yōu)雅的實(shí)現(xiàn)多語(yǔ)言,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01