最近作web項目,接觸了jquery等框架,雖然使用方便,可是仍是想學習下Javascript,今天分享下最近對js原型繼承的理解,不足之處歡迎指正。javascript
1、構造器的原型屬性與原型對象java
剛接觸js時一般依樣畫瓢,用函數new一個實例,也不知道其緣由,只據說js中函數即對象。原來js中沒有采用Java等語言中的類繼承體系,而是使用原型對象(prototype)實現繼承體系,具體說是利用「構造器」實現類的功能。jquery
首先解釋下原型繼承中的兩個重要概念:原型屬性、原型對象(實例)。web
就js對象系統而言,建立的每一個函數(構造器)都有一個prototype原型屬性,同時,經過構造器建立的每一個對象實例也包含一個_proto_屬性,prototype和_proto_屬性是一個指針,指向原型對象。普通函數與構造函數的惟一區別就是,其原型屬性prototype是否是一個有意義的值。框架
原型屬性prototype所指向的原型是一個對象實例(Object instance)。具體以下圖所示,若構造器Animal()有一個原型對象B,則由該構造器建立的實例都必然複製於B。即:Animal()的實例a1的_proto_屬性也會指向原型對象B。所以,實例a1可以繼承B的全部屬性、方法和其餘性質。函數
圖1 js對象實例化實現學習
2、空的對象測試
在javascript中,「空的對象」是整個原型繼承體系的根基,是全部對象的基礎。介紹「空的對象」以前,必須先介紹下「空對象(null)」。this
null不是「空的對象」,做爲javascript中的一個保留字,其含義是:spa
(1)屬於對象類型
(2)對象是空值
做爲一個對象類型,可使用for…in去列舉它,可是做爲一個空值,null沒有任何方法和屬性(包括constructor、_proto_等屬性),所以列舉不到任何內容。以下例所示:
var num=0;
for(var propertyName in null)
{
num++;
}
Alert(num);//顯示值爲0
最重要的一點是null沒有原型,它並非自Object()構造器(或其子類)實例化而來,對其進行instanceof 運算會返回false。
2.「空的對象」
「空的對象」是指一個標準的、經過Object()構造的對象實例。例如:
obj=new Object();或 obj={};
「空的對象」具備「對象」的一切特性,所以能夠存取toString()、valueof等預約義的屬性和方法。
3.「空的對象」與null的關係
以下圖2中紅線所示路徑,當經過」Object.prototype._proto_」獲取Object原型對象的-proto-屬性時,將會獲得」null」,因爲null對象沒有任何屬性,也就是說」Object {}」
原型對象就是原型鏈的終點了。
圖2 js類繼承體系
3、Javascript繼承的實現以及原型鏈維護
(1)繼承的實現
第一節說過javascript中類繼承是經過修改構造函數的原型屬性prototype實現的。以下代碼所示:
function Animal() {
this.name = 'Animal';
};
function Dog() {
};
Dog.prototype = new Animal();
var d = new Dog();
console.log(d.name);//'Animal'
經過建立一個Animal類型的實例並將其賦值給構造函數Dog()的prototype屬性,從而實現類型繼承,即Animal是Dog的父類。這樣Dog類型的實例d也能訪問Animal類型的name屬性。
(2)原型鏈
JS對象繼承體系中有兩種原型鏈:「內部原型鏈」和「構造器原型鏈」。如圖3所示,黑色箭頭指示路徑是經過構造函數的prototype屬性保持的「構造器原型鏈」。紅色箭頭指示路徑是經過對象實例的_proto_屬性保持的「內部原型鏈」。
圖3 原型鏈
(3)原型鏈維護
圖3說明構造器經過顯示的prototype構建了一個原型鏈,而對象實例也經過_ proto _屬性構建了一個原型鏈。因爲_ proto _是一個不可訪問的內部屬性(Chrome中能夠查看對象_ proto _屬性的值,但不能夠修改),所以沒法從子類(Dog)的實例dog1開始訪問整個原型鏈。所以,咱們須要從圖3中的「內部原型鏈」和「構造器原型鏈」中找到一個鏈接點,使得實例不能訪問obj._proto_的狀況下經過構造器訪問內部原型鏈(將兩種原型鏈串聯起來)。
若要從子類的實例開始訪問整個原型鏈,須要使用實例的constructor屬性維護原型鏈。
其實,JavaScript已經爲構造器維護了原型屬性,根據以下測試代碼,當咱們自定義一個構造器時,其原型對象是一個Object()類型的實例,可是其原型對象的constructor屬性默認老是指向構造器自身,而非指向其父類Object。如圖4中構造器實例中藍色框中的constructor屬性,該constructor屬性繼承自原型對象,所以能夠得出一個自定義的構造器產生的實例,其constructor屬性默認老是指向該構造器。
function Animal() {
};
var a = new Animal();
console.log(Animal.prototype);//Object(){}
console.log(Animal.prototype.constructor === Animal);//true//true
圖4
所以,在_proto_屬性不可訪問時,可經過a1.constructor.prototype獲取實例a1的原型對象。然而,當咱們自定義一個構造函數Dog(),而且手動指定其prototype屬性值爲Animal,即指定Dog的父類爲Animal。此時訪問d1.constructor值爲Animal,而不是Dog;由圖5能夠看出,Dog的原型對象和dog分別由Animal()和Dog()兩個不一樣的構造器產生,然而他們的constructor屬性指向了相同的構造器(Animal),這樣就與使用constructor屬性串聯兩種原型鏈的設想衝突了。
圖5
是構造器出問題仍是原型出了問題?圖5能夠看出,原型繼承要求的「複製行爲」已經正確實現,可以從子類實例中訪問原型對象屬性,問題是在給子類構造器Dog()賦予一個原型對象時應該「修正」該原型對象的構造屬性值(constructor)。ECMAScript 3標準提供的方法是:保持原型的構造器屬性,在子類構造器中初始化其實例對象的構造屬性。代碼以下:
function Dog () {
//初始化constructor屬性
this.constructor=Dog; //或 this.constructor=arguments.callee;
};
Dog.prototype = new Animal();//賦予原型對象,實現繼承
圖6
對constructor屬性「修正」後效果如圖6所示,在子類構造器Dog中初始化其實例對象的constructor屬性後,Dog的實例對象的constructor都指向Dog,而Dog的原型對象的constructor仍然指向父類型構造器Animal。這樣就能夠實現利用constructor屬性串聯起原型鏈,能夠從子類實例開始回溯整個原型鏈。