2016年6月20日 星期一

[筆記] 了解JavaScript中原型(prototype)、原型鍊(prototype chain)和繼承(inheritance)的概念


這篇筆記主要說明了JavaScript中非常重要的概念,也就是繼承(inheritance)、原型(prototype)和原型鍊(prototype chain)。

談繼承(inheritance)


讓我們先來了解一下繼承的意思,繼承的意思其實不用想得太複雜,簡單來說就是指一個物件可以提取到其他物件中的屬性(property)或方法(method)

繼承可以分成兩種,一種是classical inheritance,這種方式用在 C# 或 JAVA 當中;另一種則是我們這裡談到 JavaScript 所使用的,是屬於 prototypal inheritance 

談原型鍊(prototype chain)


由於JavaScript使用的是prototypal inheritance,所以必然會包含原型(prototype)的概念,讓我們看一下這張圖:

一個物件裡面除了所給予的屬性值外,另外也包含原型prototype。

obj.prop1:假設我們現在有一個物件,就稱作obj,而這個物件包含一個屬性(property),我們稱作prop1,現在我們可以使用 obj.prop1 來讀取這個屬性的值,就可以直接讀取到prop1的值了。

obj.prop2:從前篇筆記我們可以知道,JavaScript當中也會有一些預設在內的屬性和方法,而在所有JavaScript中,所有的物件和函式都包含prototype這個屬性,假設我們把它叫做proto,這時候如果我們使用obj.prop2的時候,JavaScript引擎會先在Obj的屬性裡去尋找有沒有稱作prop2的屬性,如果它找不到,這時候它就會再進一步往該物件的proto裡面去尋找。所以,雖然我們輸入 obj.prop2 的時候會得到回傳值,但實際上這不是obj裡面直接的屬性名稱,而是在obj裡面proto裡面找到的屬性名稱( obj.proto.prop2,但我們不需要這樣打)。

obj.prop3:同樣地,每一個物件裡面都包含一個prototype,包括物件proto也不例外,所以,如果我們是輸入 obj.prop3時,JavaScript會先在obj這個物件裡去尋找有沒有prop3這個屬性名稱,找不到時會再往obj的proto去尋找,如果還是找不到時,就在往proto這個物件裡面的proto找下去,最後找到後回傳屬性值給我們(obj.proto.proto.prop3)。

雖然乍看之下,prop3很像是在物件obj裡面的屬性,但實在上它是在obj→prop→prop 的物件裡面,而這樣從物件本身往proto尋找下去的鍊我們就稱作原型鍊(prototype chain)。這樣一直往下找會找到什麼時候呢?它會直到某個對象的原型為null為止(也就是不再有原型指向)

讓我們來看個例子幫助了解


讓我們實際來看個例子幫助我們了解prototype chain這個概念,但是要注意!要注意!要注意!這個例子只是為了用來說明prototype chain的概念,實際上撰寫程式時萬萬不可使用這樣的方式!

首先,我們先建立一個物件person和一個物件john

var person = {
    firstname:'Default',
    lastname:'Default',
    getFullName: function(){
        return this.firstname+ ' ' + this.lastname;
    }
}

var john = {
    firstname:'John',
    lastname:'Doe'
}

再次提醒,下面的示範只是為了說明原型鍊,在平常的情況下絕對不要這樣做,這樣做會拖慢整個瀏覽器的效能。

接著,我們知道所有的物件裡面都會包含原型(prototype)這個物件,在JavaScript中這個物件的名稱為 __proto__ 。如同上述原型鍊(prototype chain)的概念,如果在原本的物件中找不到指定的屬性名稱或方法時,就會進一步到 __proto__ 這裡面來找。這時候,我們為了示範,我們來對 __proto__ 做一些事:

//千萬不要照著下面這樣做,這麼做只是為了示範
john.__proto__ = person;

如此,john這個物件就繼承了person物件。在這種情況下,如果我們想要呼叫某個屬性或方法,但在原本john這個物件中找不到這個屬性名稱或方法時,JavaScript引擎就會到 __proto__ 裡面去找,所以當我接著執行時:

console.log(john.getFullName())        //    John Doe;


我們可以得到"John Doe"的結果。原本在john的這個物件中,是沒有getFullName這個方法的,但由於我讓 __proto__ 裡面繼承了person這個物件,所以當JavaScript引擎在john物件裡面找不到getFullName這個方法時,它便會到 __proto__ 裡面去找,最後它找到了,於是它回傳"John Doe"的結果。
如果我是執行

console.log(john.firstname);        //  John

我們會得到的是John而不是default,因為JavaScript引擎在尋找john.firstname這個屬性時,在john的物件裡就可以找到了,因此它不會在往 __proto__ 裡面找。這也就是我們剛剛在上面所說明的原型鍊(prototype chain)的概念, 一旦它在上層的部分找到該屬性或方法時,就不會在往下層的prototype去尋找。

在了解了prototype chain這樣的概念後,讓我們接著看下面這段程式碼:

var jane ={
    firstname: 'Jane'
}

jane.__proto__ = person;
console.log(jane.getFullName());


現在,你可以理解到會輸出什麼結果嗎?

答案是"Jane Default"。

因為在jane這個物件裡只有firstname這個屬性,所以當JavaScript引擎要尋找getFullName這個方法和lastname這個屬性時,它都會去找 __proto__ 裡面,而這裡面找到的就是當初一開始建立的person這個物件的內容。


程式範例

var person = {
    firstname:'Default',
    lastname:'Default',
    getFullName: function(){
        return this.firstname+ ' ' + this.lastname;
    }
}


var john = {
    firstname:'John',
    lastname:'Doe'
}

//千萬不要照著下面這樣做,這麼做只是為了示範
john.__proto__ = person;
console.log(john.getFullName());    //  John Doe
console.log(john.firstname);        //  John

var jane ={
    firstname: 'Jane'
}

jane.__proto__ = person;
console.log(jane.getFullName());


→回到此系列文章目錄

Share:

0 意見:

張貼留言