在上篇文章中,因爲篇幅的緣由只是針對構造函數的構造過程和原型鏈的存取進行深刻的講解,有點偏原理性的講解,並無對
___proto___
、prototype
和constructor
這些屬性之間的互相關係以及實際上的應用分析清楚。因此本文的目的就是爲了加深對原型繼承的理解,並可以將其應用在實際上。javascript
prototype
//建立一個構造函數。
function Fruit(){};
//輸出其原型對象:
console.log(Fruit.prototype);複製代碼
//若是手動設置其prototype屬性的話,那麼將改變其原型對象
Fruit.prototype = {
getName : function(){
return this.name;
},
name : 'fruit',
};
//再次輸出其原型對象,這時候就發生了點奇怪的事情了
console.log(Fruit.prototype);複製代碼
Fruit.prototype
少了一個
constructor
屬性,那麼
Fruit.prototype.constructor
屬性到底跑哪去了,咱們在控制檯輸出一下看看。
console.log(Fruit.prototype.constructor)
//function Object() { [native code] }
//可能有人會感到奇怪,在Fruit.prototype中明明並無該屬性,那麼這該死的constructor屬性從哪裏來的。
//咱們回到Fruit.prototype屬性,點開__proto__屬性,那麼一切就明朗了。複製代碼
Fruit.prototype
中,只有其的
__proto__
擁有
constructor
屬性,因此是否是能夠認爲其
Fruit.prototype.constructor===Fruit.prototype.__proto__.constructor
?事實上,咱們能夠認爲兩者指向同一個構造函數。因爲重寫了
Fruit
的原型對象,JavaScript引擎不能在顯式原型中找到
constructor
屬性,那麼它將經過隱式原型鏈查找,找到了
Fruit.prototype.__proto__的constructor
屬性。若是重寫原型就會致使
constructor
屬性的更改,那麼在實際開發的時候就會發生指向不明的錯誤,以下所示:
function Fruit(){}
function Animal(){}
Animal.prototype = new Fruit();
var apple = new Fruit();
var cat = new Animal();
alert(apple.constructor===cat.constructor);//true
//apple和cat明明屬於兩個不一樣構造器產生的實例,可是它們的constructor屬性指向同一構造器產生的實例複製代碼
因此在修改構造函數的原型時候,應該修正該原型對象的constructor
屬性,一般修正方法有兩種:java
//第一種方法:當其原型修改時,手動更改其原型的```constructor```屬性的指向。
Animal.prototype.constructor = Animal;
//第二種方法:保持原型的構造器屬性,在子類構造器函數內初始化實例的構造器屬性,
function Animal(){
this.constructor = arguments.callee;
//或者能夠:this.constructor = Animal;
}複製代碼
在網上對constructor屬性的做用有着許多不一樣的見解,有的人認爲其是爲了將實例的構造器的原型對象更好的暴露出來,可是我我的認爲constructor屬性在整個原型繼承中實際上是沒有起到什麼做用的,甚至在JS語言中也是如此,由於其可讀寫,因此其未必指向對象的構造函數,像上面的保持原型構造屬性不變,只是從編程的習慣出發,讓對象的constructor屬性指向其構造函數。編程
說完了構造函數的prototype
屬性,因爲我在上文就已經介紹過了普通的函數與構造函數並無什麼本質的區別,因此如今咱們開始將目光放在一些特殊的函數上面。app
Function
是JavaScript一個特殊的構造函數,在JS中,每個函數都是其對象(Object
也是)。在控制檯輸出下Function.prototype
獲得這樣一個函數function () { [native code] }
。再用函數
console.log(Function.prototype);
//function () { [native code] }
//用typeof判斷下其類型
console.log(typeof Function.prototype)//function
//既然其是function類型的,那麼由於全部的函數都有prototype對
//象,因此其確定就有prototype屬性了,那麼咱們如今能夠輸出看看了,可是神奇的事情發生了。
console.log(Function.prototype.prototype)//undefined
//其竟然輸出了undefined,這發生了什麼事情??複製代碼
翻閱了許多資料,終於讓我找到了其緣由所在。而這與JavaScript的底層有關了。在上篇文章,咱們就說到了Object.prototype
處於原型鏈的頂端,而JavaScript在Object.prototype
的基礎上又產生了一個Function.prototype
對象,這個對象又被稱爲[Function:Empty](空函數,其是一個不一樣於通常函數的函數對象)
。隨後又以該對象爲基礎打造了兩個構造函數,一個即爲Function
,另外一個爲Object
。意不意外,驚不驚喜!可是看到下面,你又會剛到更加意外的。因此,在下面的代碼如此顯示,你就不會感到意外了。ui
console.log(Object.__proto__ === Function.prototype);//true
//Object的__proto__屬性指向Function.prototype。這又說明Object這個構造器是從Function的原型生產出來的。
console.log(Object.constructor === Function);//true
//Object.constructor屬性指向了它的構造函數Function
//看着上面的代碼,是否是可以得出Object是一個Function的實例對象的結論。複製代碼
沒錯,Object
這個構造函數是Function
的一個實例(由於Object
是繼承自Function.prototype
,甚至能夠這樣說,全部的構造函數都是 Function
的一個實例。this
__proto__
談完了prototype
屬性,如今咱們開始來看看__proto__
屬性,在上篇文章中,咱們就已經提到了__proto__
指向的是當前對象的原型對象。因爲在JS內部,__proto__
屬性是爲了保持子類與父類的一致性,因此在對象初始化的時候,在其內部生成該屬性,並拒絕用戶去修改該屬性。儘管目前咱們能夠手動去修改該屬性,可是爲了保持這種一致性,儘可能不要去修改該屬性。廢話很少說,咱們來看看一些示例:spa
//一個普通的函數
function Fruit(){};
console.log(Fruit.__proto__);//function(){ [native code] }
//貌似有點眼熟,像是上面的空函數,動手試試
console.log(Fruit.__proto__===Function.prototype)//true
//恩,有點大驚小怪了,對象的__proto__就是指向構造該對象的構造函數的原型對象。
//若是兩者不等的話,那就出事了。
//如今來看看一個構造函數構造出來的對象
var apple = new Fruit();
console.log(apple.__proto__);
//其指向了Fruit.prototype,可是若是Fruit.prototype該變量,那會怎麼樣呢?
Fruit.prototype = {};
console.log(apple.__proto__);
//貌似跟上面並無多大的變化,可是別急,咱們接下來看。
var banana = new Fruit();
console.log(banana.__proto__);
//{};這就對了,對象的__proto__就是指向原型對象的,當構造函數的原型對象改變的時候,其也將改變。
//至於爲何apple和banana的__proto__屬性會變化,這就涉及到內存分配的問題了,在這裏就再也不展開。複製代碼
因爲每一個對象都將擁有一個__proto__
屬性,那麼apple.__proto__
必然擁有__proto__
屬性,那就讓咱們一塊兒探究下吧。prototype
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複製代碼
其實仔細分析下就應該知道這樣的指向,dog.__proto__
指向Animal.prototype
,而Animal.prototype
實際上是一個對象實例,由Object
所構造出來的,天然Animal.prototype.__proto__
指向Object.prototype
。看完了對象的__proto__
屬性,如今來看下函數的相關屬性。3d
console.log(Animal.__proto__===Function.prototype)//true;
console.log(Animal.__proto__.__proto__===Object.prototype)//true;
console.log(Animal.__proto__.__proto__.__proto__)//null複製代碼
可能有人會對Animal.__proto__.__proto__.__proto__===null
產生疑惑,有人也是由於這樣而認爲在整個原型鏈的頂端就是null,其實否則,由於null
壓根就沒有任何屬性,天然對象和函數就不能從中繼承到什麼東西了。
其實在JavaScript內部,當實例化一個對象的時候,實例對象的__proto__
指向了構造函數的prototype
屬性,以此來繼承構造函數prototype
上全部屬性和方法。
總結:其實若是可以縷清__proto__
和prototype
兩者的關係,那麼關於原型繼承就很簡單了。每一個對象都擁有了__proto__
屬性,全部對象的__proto__
屬性串聯起了一條原型鏈,鏈接了擁有繼承關係的對象,這條原型鏈的終點指向了Object.prototype
。