JavaScript原型鏈及常見的繼承方法
原型鏈
原型鏈的概念
在JavaScript中,每一個(gè)構(gòu)造函數(shù)都有一個(gè)原型,這個(gè)原型中有一個(gè)屬性constructor
會(huì)再次指回這個(gè)構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)所創(chuàng)造的實(shí)例對(duì)象,會(huì)有一個(gè)指針(也就是我們說的隱式原型__proto__
或者是瀏覽器中顯示的[[Prototype]]
)指向這個(gè)構(gòu)造函數(shù)的原型對(duì)象。如果說該構(gòu)造函數(shù)的原型對(duì)象也是由另外一個(gè)構(gòu)造函數(shù)所創(chuàng)造的實(shí)例,那么該構(gòu)造函數(shù)的原型對(duì)象也會(huì)存在一個(gè)指針指向另外一個(gè)構(gòu)造函數(shù)的原型對(duì)象,周而復(fù)始,就形成了一條原型鏈。 最特別的是所有的沒有經(jīng)過再繼承函數(shù)都是由Function
實(shí)例化來的,所有的除了函數(shù)外的對(duì)象都是由Object
實(shí)例化來的,其中Object
也是由Function
實(shí)例化來的,但是Object.prototype.__proto__ === null
是成立的。
再強(qiáng)調(diào)一遍:原型鏈?zhǔn)茄刂鴮?duì)象的隱式原型一層層的去尋找的,找到的是構(gòu)造函數(shù)所創(chuàng)造的實(shí)例。例如下:
這個(gè)就是相當(dāng)于由Student
new 出來的實(shí)例s
,查找自身的 name
屬性,然后沿著原型鏈查找,找到Student
中的prototype
當(dāng)中,然后找到了name
這個(gè)屬性。
而這個(gè)例子,由紅框框起來的代碼(寄生繼承的關(guān)鍵代碼),代替注釋掉的部分,最終s
是找不到name
屬性的,這是因?yàn)榧t框中的代碼,僅僅是將Student
的隱式原型指向了Person
的顯示原型對(duì)象,未能創(chuàng)建任何的實(shí)例,當(dāng)然就不會(huì)存在屬性這個(gè)說法。
原型鏈的問題
原型鏈的問題主要有兩個(gè)方面,第一個(gè)問題是,當(dāng)原型中出現(xiàn)包含引用值(比如數(shù)組)的時(shí)候,所有在這條原型鏈中的實(shí)例會(huì)共享這個(gè)屬性,造成“一發(fā)而動(dòng)全身”的問題。第二個(gè)問題就是子類在實(shí)例化時(shí),不能夠給父類型的構(gòu)造函數(shù)傳參,即
無法在不影響所有對(duì)象實(shí)例的情況下把參數(shù)傳遞進(jìn)父類型的構(gòu)造函數(shù)傳參
幾種常見的繼承方法
盜用構(gòu)造函數(shù)
function SuperType() { this.friends = ['張三','李四'] } function SubType() { SuperType.call(this); } const p1 = new SubType(); p1.friends.push('王武'); const p2 = new SubType(); console.log(p2.friends); // ['張三','李四', '王武']
盜用構(gòu)造函數(shù)實(shí)現(xiàn)繼承在這個(gè)例子中有了充分的體現(xiàn): 首先在子類的構(gòu)造函數(shù)中調(diào)用父類的構(gòu)造函數(shù)。因?yàn)楫吘购瘮?shù)就是特定上下文中執(zhí)行代碼的簡單對(duì)象,所以可以使用call()
方法以創(chuàng)建的對(duì)象為上下文執(zhí)行的構(gòu)造函數(shù)。
盜用構(gòu)造函數(shù)的主要問題,也是創(chuàng)建對(duì)象的幾種方式中構(gòu)造函數(shù)模式自定義類型的問題:必須在構(gòu)造函數(shù)中定義方法,造成內(nèi)存浪費(fèi)。另外,子類也不能訪問父類原型上定義的方法,因此,盜用構(gòu)造函數(shù)也不會(huì)單獨(dú)使用。
組合繼承
組合繼承也稱為偽經(jīng)典繼承,綜合了原型鏈和構(gòu)造函數(shù),將兩者的有點(diǎn)結(jié)合起來。基本的思路就是使用原型鏈繼承原型上的屬性和方法,而通過盜用構(gòu)造函數(shù)繼承實(shí)現(xiàn)實(shí)例的屬性。這樣就可以把方法定義在原型上實(shí)現(xiàn)復(fù)用,又可以讓每個(gè)實(shí)例有自己的屬性。
function SuperType(name) { this.name = name; this.friends = ['張三','李四']; } SuperType.prototype.sayName = function() { console.log(this.name) } // 繼承方法 SubType.prototype = new SuperType(); function SubType(name, age) { SuperType.call(this, name); this.age = age; } const p1 = new SubType('趙六', 12); const p2 = new SubType('趙六2', 22); // 創(chuàng)建的 p1 和 p2 能夠擁有自己的屬性并且引用值屬性也是獨(dú)立的,此外,每一個(gè)實(shí)例能夠公用父類的方法。
組合繼承已經(jīng)接近完美了,但是,我們發(fā)現(xiàn),實(shí)現(xiàn)組合繼承就要調(diào)用兩次父類構(gòu)造函數(shù)。在本質(zhì)上,子類型最終是要包含超類對(duì)象的所有實(shí)例屬性,子類構(gòu)造函數(shù)只要在執(zhí)行時(shí)重寫自己的原型就行了,這就為減少一次調(diào)用父類構(gòu)造函數(shù)提供了思路。
原型式繼承
const person = { name: 'zs', friends: ['ls','ww'] } // 創(chuàng)造出一個(gè)實(shí)例,這個(gè)實(shí)例的隱式原型指向 person const anotherPerosn = Object.create(person); anotherPerosn.name = 'xm' anotherPerosn.friends.push('zl') console.log(anotherPerosn.name) // xm console.log(anotherPerosn.friends) // ['ls','ww', 'zl']; const anotherPerosn2 = Object.create(person); anotherPerosn.name = 'xh' anotherPerosn.friends.push('dd') console.log(anotherPerosn2.name) // xh console.log(anotherPerosn2.friends) // ['ls','ww', 'zl', 'dd'];
對(duì)于原型鏈繼承就不再過多的解釋了。。。。
寄生式繼承
寄生式繼承與原型式繼承比較相似,都會(huì)存在屬性引用值共享的問題。
function createAnotherPerson(original) { const clone = Object.create(original); //通過調(diào)用函數(shù)創(chuàng)建一個(gè)新的對(duì)象 clone.sayHi = function() { // 以某種方式增強(qiáng)這個(gè)對(duì)象 console.log('Hi'); } return clone; }
寄生式繼承,不僅存在著屬性引用值共享的問題而且函數(shù)還不能進(jìn)行復(fù)用。
寄生組合式繼承
// 實(shí)現(xiàn)了寄生式組合繼承的核心邏輯 function inheritPrototype(subFn, parentFn){ subFn.prototype = Object.create(parentFn.prototype); // 創(chuàng)建賦值對(duì)象 Object.defineProperty(subFn.prototype,'constructor', { // 增強(qiáng)對(duì)象 enumerable: false, writable: false, configurable: false, value: subFn, }) } function Person(name, age, address) { this.name = name; this.age = age; this.address = address; } Person.prototype.eating = function() { console.log(this.name + "正在吃飯"); } // 共享方法 function Student(name, age, address, sno) { Person.call(this, name, age, address); // 綁定 this 確保創(chuàng)建出來的對(duì)象是相互獨(dú)立的 this.sno = sno; this.studing = function() { console.log(`${this.name}正在學(xué)習(xí)`) } } function Teacher(name, age, address, tno) { Person.call(this, name, age, address) this.tno = tno; }
寄生+組合式(構(gòu)造函數(shù)+原型鏈)完美的解決了其他繼承出現(xiàn)的問題。
到此這篇關(guān)于JavaScript原型鏈及常見的繼承方法的文章就介紹到這了,更多相關(guān)JS原型鏈內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS實(shí)現(xiàn)TITLE懸停長久顯示效果完整示例
這篇文章主要介紹了JS實(shí)現(xiàn)TITLE懸停長久顯示效果,結(jié)合完整實(shí)例形式分析了JavaScript鼠標(biāo)事件響應(yīng)及頁面元素屬性動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2020-02-02原生JS實(shí)現(xiàn)的多個(gè)彩色小球跟隨鼠標(biāo)移動(dòng)動(dòng)畫效果示例
這篇文章主要介紹了原生JS實(shí)現(xiàn)的多個(gè)彩色小球跟隨鼠標(biāo)移動(dòng)動(dòng)畫效果,涉及javascript事件響應(yīng)、頁面元素屬性動(dòng)態(tài)修改及隨機(jī)數(shù)應(yīng)用等相關(guān)操作技巧,需要的朋友可以參考下2018-02-02簡單實(shí)現(xiàn)Bootstrap標(biāo)簽頁
這篇文章主要教大家簡單實(shí)現(xiàn)Bootstrap標(biāo)簽頁,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12uni-app小程序沉浸式導(dǎo)航實(shí)現(xiàn)的全過程
在跨端項(xiàng)目開發(fā)中,uniapp是個(gè)不錯(cuò)的框架,下面這篇文章主要給大家介紹了關(guān)于uni-app小程序沉浸式導(dǎo)航實(shí)現(xiàn)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10Ajax異步提交表單數(shù)據(jù)的說明及方法實(shí)例
Ajax異步提交表單數(shù)據(jù)的說明及方法實(shí)例,需要的朋友可以參考一下2013-06-06Javascript中找到子元素在父元素內(nèi)相對(duì)位置的代碼
因?yàn)橄胱詣?dòng)定位到子元素,所以一直在找各種找尋元素位置的代碼。 不過總是找不到可以定位子元素相對(duì)位置的代碼2012-07-07JavaScript基于ChatGPT實(shí)現(xiàn)打字機(jī)消息回復(fù)
ChatGPT 是一個(gè)基于深度學(xué)習(xí)的大型語言模型,處理自然語言需要大量的計(jì)算資源和時(shí)間,響應(yīng)速度肯定比普通的讀數(shù)據(jù)庫要慢的多,本文介紹了ChatGPT打字機(jī)消息回復(fù)實(shí)現(xiàn)原理,感興趣的同學(xué)可以跟著小編一起學(xué)習(xí)2023-05-05javascript eval()應(yīng)用實(shí)例 select
javascript eval應(yīng)用小例子。實(shí)例代碼就是控制checkbox的選擇與取消的函數(shù),非常不錯(cuò)。2009-07-07