在上篇文章中,由于篇幅的原因只是針對(duì)構(gòu)造函數(shù)的構(gòu)造過程和原型鏈的存取進(jìn)行深入的講解翻诉,有點(diǎn)偏原理性的講解还绘,并沒有對(duì)
___proto___
、prototype
和constructor
這些屬性之間的互相關(guān)系以及實(shí)際上的應(yīng)用分析清楚膘侮。所以本文的目的就是為了加深對(duì)原型繼承的理解套蒂,并能夠?qū)⑵鋺?yīng)用在實(shí)際上撼港。
prototype
//創(chuàng)建一個(gè)構(gòu)造函數(shù)。
function Fruit(){};
//輸出其原型對(duì)象:
console.log(Fruit.prototype);
//如果手動(dòng)設(shè)置其prototype屬性的話营曼,那么將改變其原型對(duì)象
Fruit.prototype = {
getName : function(){
return this.name;
},
name : 'fruit',
};
//再次輸出其原型對(duì)象,這時(shí)候就發(fā)生了點(diǎn)奇怪的事情了
console.log(Fruit.prototype);
對(duì)比兩張圖片愚隧,有沒有發(fā)現(xiàn)異常蒂阱,心細(xì)的人可能發(fā)現(xiàn)了
Fruit.prototype
少了一個(gè)constructor
屬性,那么Fruit.prototype.constructor
屬性到底跑哪去了奸攻,我們?cè)诳刂婆_(tái)輸出一下看看蒜危。
console.log(Fruit.prototype.constructor)
//function Object() { [native code] }
//可能有人會(huì)感到奇怪,在Fruit.prototype中明明并沒有該屬性睹耐,那么這該死的constructor屬性從哪里來的辐赞。
//我們回到Fruit.prototype屬性,點(diǎn)開__proto__屬性,那么一切就明朗了硝训。
在
Fruit.prototype
中响委,只有其的__proto__
擁有constructor
屬性,所以是不是可以認(rèn)為其Fruit.prototype.constructor===Fruit.prototype.__proto__.constructor
窖梁?事實(shí)上赘风,我們可以認(rèn)為二者指向同一個(gè)構(gòu)造函數(shù)。由于重寫了Fruit
的原型對(duì)象纵刘,JavaScript引擎不能在顯式原型中找到constructor
屬性邀窃,那么它將通過隱式原型鏈查找,找到了Fruit.prototype.__proto__的constructor
屬性假哎。如果重寫原型就會(huì)導(dǎo)致constructor
屬性的更改瞬捕,那么在實(shí)際開發(fā)的時(shí)候就會(huì)發(fā)生指向不明的錯(cuò)誤鞍历,如下所示:
function Fruit(){}
function Animal(){}
Animal.prototype = new Fruit();
var apple = new Fruit();
var cat = new Animal();
alert(apple.constructor===cat.constructor);//true
//apple和cat明明屬于兩個(gè)不同構(gòu)造器產(chǎn)生的實(shí)例,但是它們的constructor屬性指向同一構(gòu)造器產(chǎn)生的實(shí)例
所以在修改構(gòu)造函數(shù)的原型時(shí)候肪虎,應(yīng)該修正該原型對(duì)象的constructor
屬性劣砍,通常修正方法有兩種:
//第一種方法:當(dāng)其原型修改時(shí),手動(dòng)更改其原型的```constructor```屬性的指向扇救。
Animal.prototype.constructor = Animal;
//第二種方法:保持原型的構(gòu)造器屬性刑枝,在子類構(gòu)造器函數(shù)內(nèi)初始化實(shí)例的構(gòu)造器屬性,
function Animal(){
this.constructor = arguments.callee;
//或者可以:this.constructor = Animal;
}
在網(wǎng)上對(duì)constructor屬性的作用有著許多不同的看法迅腔,有的人認(rèn)為其是為了將實(shí)例的構(gòu)造器的原型對(duì)象更好的暴露出來装畅,但是我個(gè)人認(rèn)為constructor屬性在整個(gè)原型繼承中其實(shí)是沒有起到什么作用的,甚至在JS語言中也是如此钾挟,因?yàn)槠淇勺x寫洁灵,所以其未必指向?qū)ο蟮臉?gòu)造函數(shù),像上面的保持原型構(gòu)造屬性不變掺出,只是從編程的習(xí)慣出發(fā)徽千,讓對(duì)象的constructor屬性指向其構(gòu)造函數(shù)。
說完了構(gòu)造函數(shù)的prototype
屬性汤锨,由于我在上文就已經(jīng)介紹過了普通的函數(shù)與構(gòu)造函數(shù)并沒有什么本質(zhì)的區(qū)別双抽,所以現(xiàn)在我們開始將目光放在一些特殊的函數(shù)上面。
Function
是JavaScript一個(gè)特殊的構(gòu)造函數(shù)闲礼,在JS中牍汹,每一個(gè)函數(shù)都是其對(duì)象(Object
也是)。在控制臺(tái)輸出下Function.prototype
得到這樣一個(gè)函數(shù)function () { [native code] }
柬泽。再用
console.log(Function.prototype);
//function () { [native code] }
//用typeof判斷下其類型
console.log(typeof Function.prototype)//function
//既然其是function類型的慎菲,那么因?yàn)樗械暮瘮?shù)都有prototype對(duì)
//象,所以其肯定就有prototype屬性了锨并,那么我們現(xiàn)在可以輸出看看了露该,但是神奇的事情發(fā)生了。
console.log(Function.prototype.prototype)//undefined
//其居然輸出了undefined第煮,這發(fā)生了什么事情解幼??
翻閱了許多資料包警,終于讓我找到了其原因所在撵摆。而這與JavaScript的底層有關(guān)了。在上篇文章害晦,我們就說到了Object.prototype
處于原型鏈的頂端特铝,而JavaScript在Object.prototype
的基礎(chǔ)上又產(chǎn)生了一個(gè)Function.prototype
對(duì)象,這個(gè)對(duì)象又被稱為[Function:Empty](空函數(shù),其是一個(gè)不同于一般函數(shù)的函數(shù)對(duì)象)
苟呐。隨后又以該對(duì)象為基礎(chǔ)打造了兩個(gè)構(gòu)造函數(shù)痒芝,一個(gè)即為Function
,另一個(gè)為Object
。意不意外牵素,驚不驚喜!但是看到下面澄者,你又會(huì)剛到更加意外的笆呆。所以,在下面的代碼如此顯示粱挡,你就不會(huì)感到意外了赠幕。
console.log(Object.__proto__ === Function.prototype);//true
//Object的__proto__屬性指向Function.prototype。這又說明Object這個(gè)構(gòu)造器是從Function的原型生產(chǎn)出來的询筏。
console.log(Object.constructor === Function);//true
//Object.constructor屬性指向了它的構(gòu)造函數(shù)Function
//看著上面的代碼榕堰,是不是能夠得出Object是一個(gè)Function的實(shí)例對(duì)象的結(jié)論。
沒錯(cuò)嫌套,Object
這個(gè)構(gòu)造函數(shù)是Function
的一個(gè)實(shí)例(因?yàn)?code>Object是繼承自Function.prototype
逆屡,甚至可以這樣說,所有的構(gòu)造函數(shù)都是 Function
的一個(gè)實(shí)例踱讨。
__proto__
談完了prototype
屬性魏蔗,現(xiàn)在我們開始來看看__proto__
屬性,在上篇文章中痹筛,我們就已經(jīng)提到了__proto__
指向的是當(dāng)前對(duì)象的原型對(duì)象莺治。由于在JS內(nèi)部,__proto__
屬性是為了保持子類與父類的一致性帚稠,所以在對(duì)象初始化的時(shí)候谣旁,在其內(nèi)部生成該屬性,并拒絕用戶去修改該屬性滋早。盡管目前我們可以手動(dòng)去修改該屬性榄审,但是為了保持這種一致性,盡量不要去修改該屬性馆衔。廢話不多說瘟判,我們來看看一些示例:
//一個(gè)普通的函數(shù)
function Fruit(){};
console.log(Fruit.__proto__);//function(){ [native code] }
//貌似有點(diǎn)眼熟,像是上面的空函數(shù)角溃,動(dòng)手試試
console.log(Fruit.__proto__===Function.prototype)//true
//恩拷获,有點(diǎn)大驚小怪了,對(duì)象的__proto__就是指向構(gòu)造該對(duì)象的構(gòu)造函數(shù)的原型對(duì)象减细。
//如果二者不等的話匆瓜,那就出事了。
//現(xiàn)在來看看一個(gè)構(gòu)造函數(shù)構(gòu)造出來的對(duì)象
var apple = new Fruit();
console.log(apple.__proto__);
//其指向了Fruit.prototype,但是如果Fruit.prototype該變量驮吱,那會(huì)怎么樣呢茧妒?
Fruit.prototype = {};
console.log(apple.__proto__);
//貌似跟上面并沒有多大的變化,但是別急左冬,我們接下來看桐筏。
var banana = new Fruit();
console.log(banana.__proto__);
//{};這就對(duì)了,對(duì)象的__proto__就是指向原型對(duì)象的拇砰,當(dāng)構(gòu)造函數(shù)的原型對(duì)象改變的時(shí)候梅忌,其也將改變。
//至于為什么apple和banana的__proto__屬性會(huì)變化除破,這就涉及到內(nèi)存分配的問題了牧氮,在這里就不再展開。
由于每個(gè)對(duì)象都將擁有一個(gè)__proto__
屬性瑰枫,那么apple.__proto__
必然擁有__proto__
屬性踱葛,那就讓我們一起探究下吧。
function Animal(){};
var dog = new Animal();
console.log(dog.__proto__.__proto__)
//Object {__defineGetter__: function, __defineSetter__: function, hasOwnProperty: function, __lookupGetter__: function, __lookupSetter__: function…}
//是不是很眼熟光坝,這跟上面的Object.prototypey一模一樣尸诽,輸出看看
console.log(dog.__proto__.__proto__==Object.prototype) //true
其實(shí)仔細(xì)分析下就應(yīng)該知道這樣的指向,dog.__proto__
指向Animal.prototype
,而Animal.prototype
其實(shí)是一個(gè)對(duì)象實(shí)例教馆,由Object
所構(gòu)造出來的逊谋,自然Animal.prototype.__proto__
指向Object.prototype
⊥疗蹋看完了對(duì)象的__proto__
屬性胶滋,現(xiàn)在來看下函數(shù)的相關(guān)屬性。
console.log(Animal.__proto__===Function.prototype)//true;
console.log(Animal.__proto__.__proto__===Object.prototype)//true;
console.log(Animal.__proto__.__proto__.__proto__)//null
可能有人會(huì)對(duì)Animal.__proto__.__proto__.__proto__===null
產(chǎn)生疑惑悲敷,有人也是因?yàn)檫@樣而認(rèn)為在整個(gè)原型鏈的頂端就是null,其實(shí)不然究恤,因?yàn)?code>null壓根就沒有任何屬性,自然對(duì)象和函數(shù)就不能從中繼承到什么東西了后德。
其實(shí)在JavaScript內(nèi)部部宿,當(dāng)實(shí)例化一個(gè)對(duì)象的時(shí)候,實(shí)例對(duì)象的__proto__
指向了構(gòu)造函數(shù)的prototype
屬性瓢湃,以此來繼承構(gòu)造函數(shù)prototype
上所有屬性和方法理张。
總結(jié):其實(shí)如果能夠縷清__proto__
和prototype
二者的關(guān)系,那么關(guān)于原型繼承就很簡單了绵患。每個(gè)對(duì)象都擁有了__proto__
屬性雾叭,所有對(duì)象的__proto__
屬性串聯(lián)起了一條原型鏈,連接了擁有繼承關(guān)系的對(duì)象落蝙,這條原型鏈的終點(diǎn)指向了Object.prototype
织狐。