標籤:javascript prototype 繼承 對象
原型
我們建立的每一個函數都有一個prototype(原型)屬性,這個屬性是一個對象,它的用途是包含可以由特定類型的所有執行個體共用的屬性和方法。邏輯上可以這麼理解:prototype是通過調用建構函式而建立的那個對象的原型對象。
為什麼要引入原型的概念呢?使用原型的目的,也是他的好處是可以讓所有的對象執行個體共用它所包含的屬性和方法。換句話說,就是不必再建構函式中定義對象資訊,而是可以直接將這些資訊添加到原型中。
具體怎麼用,我們來看代碼執行個體:
<span style="font-size:18px;">//原型執行個體function Person(){} //聲明一個建構函式Person.prototype.name='Lian'; //在原型裡添加屬性Person.prototype.age=100;Person.prototype.run=function(){ //在原型裡添加方法return this.name + this.age + '奮鬥中……';};var person1=new Person();var person2=new Person();alert(person1.run==person2.run); //返回true,說明方法的引用地址是一致的,即兩個對象共用了一個方法</span>
為了更好的理解建構函式的聲明方式和原型模式的聲明方式的區別,我找了兩張圖分享給大家,協助大家理解:
我們可以看到,在原型模式聲明中,多了兩個屬性,這兩個屬性都是建立對象時自動產生的。_proto_屬性是執行個體指向原型對象的一個指標,它的作用就是指向建構函式的原型屬性constructor。通過這兩個屬性,就可以訪問到原型裡的屬相和方法了。
看到這裡你會覺得奇怪,上面代碼執行個體中的建構函式的函數體中什麼也沒有,才能訪問到原型對象裡的值,如果函數體中有屬性或者方法呢?這裡就要涉及一個原型模式執行流程的問題了:是先去尋找建構函式執行個體裡面的屬性和方法,如果有,立刻返回,若沒有,則去它的原型對象中找,若有,則返回。
使用原型模式建立對象也有其缺點,那就是它省略了建構函式傳參初始化這一過程,帶來的缺點就是初始化的值都是一樣的,但是這恰恰是它最大的優點,那就是共用。
繼承
繼承是物件導向中的一個核心概念,在比較正統的物件導向的語言中一般都會採用兩種方式實現繼承:一個是介面實現,一個是類繼承。而我們的JavaScript只支援繼承,而不支援介面實現,繼承是如何?的,這裡要引入原型鏈的概念了。什麼是原形鏈,我們看一段代碼就會知道。
<span style="font-size:18px;">//繼承執行個體function A(){this.name ='Lian';}function B(){this.age=100;}function C(){this.address='中國';}B.prototype.age =200;B.prototype =new A(); //B繼承了AC.prototype =new B(); //C又繼承了Bvar c=new C();alert(c.name+' '+ c.age); //C具有了A和B的屬性</span>
在JavaScript中,被繼承的函數稱為超類型(也就是物件導向中說的父類或者說是基類),繼承的函數稱為子類型(即子類或者衍生類別)。繼承有好處,但是也有其自己的問題,比如字面量重寫原型會中斷關係,使用參考型別的原型,並且子類型無法給超類型傳遞參數。
綜合考慮,我們使用原形鏈加上建構函式,這樣產生了組合繼承。
<span style="font-size:18px;">//組合繼承function Box(age){this.name='Lee';this.age=age;}Box.prototype.run=function(){return this.name +this.age;};function Desk(age ){Box.call(this,age); //對象冒充,給超類型傳參}Desk.prototype =new Box(); //原形鏈繼承var desk =new Desk(100);alert (desk.run()); //顯然Desk繼承了Box的run方法</span>
組合繼承是JavaScript最常用的繼承方式,但是,組合繼承也有一點問題,那就是超類型在使用過程中會被調用兩次,一次是建立子類型的時候,一次是在子類型建構函式的內部。這樣會帶來效能上的開銷,如何解決?留給讀者去思考……
JavaScript學習3:原型和繼承