古典與原型繼承
這堂課將介紹 JavaScript 中,和其他程式語言很不一樣的觀念,物件導向 (Object-Oriented) 與原型繼承 (Prototypal Inheritance)
Classical Inheritance v.s Prototypal Inheritance
首先,我們先來了解一下什麼是繼承 (Inheritance),其實就是一個物件取得另一個物件的屬性 properties
和方法 method
。
而繼承又分為兩種,一種是 Classical Inheritance,其他程式語言如 C#、Java 所使用的物件繼承方式 ; 另一種則是 JavaScript 所使用的繼承方式,Prototypal Inheritance。
Prototype Chain 原型鍊
延續前面的說法, JavaScript 是一個透過 Prototypal Inheritance 來讓物件取得另一個物件的屬性 properties
或方法 method
,先來讓我們看一下底下這張圖:
首先在記憶體中有一個叫做 obj
的物件,如果它底下有一個名為 prop1
的屬性,我們可以透過點 . (dot operator) 來取得這個物件屬性 obj.prop1
。
在前面 call()、bind() and apply()
的課程中,我們知道物件在被建立時,會有一些預設的屬性和方法,所有物件 (包括函式) 都會有一個 prototype
屬性,這裡我們先稱它為 proto
,接著,當我們要找一個名為 prop2
的屬性時,在 obj
身上找不到,便會向 proto
去找,如果找到了,一樣可以透過點 . (dot operator) 來指向它 obj.prop2
,而不須使用 obj.proto.prop2
來使用它。
然而,如果找不到就會繼續向 proto
的 prototype
找下去,前面提到,每個物件都有 prototype
,而 obj
的 proto
也不例外,所以當我們輸入 obj.prop3
時,在 obj
與 proto
都找不到便會向 proto
的 prototype
找下去,並回傳 obj.proto.proto.prop3
給我們。
在物件上尋找屬性找不到便向 prototype
查詢的形式便會形成一個原型鍊 Prototype Chain
,那麼一直找下去會到什麼時候呢 ? 會一直指向原型直到返回 null
為止。
接著我們看到第二張圖,物件的 prototype
其實是可以分享給其他物件的,如圖中的第二個物件 obj2
,它將 prototype
同樣指向 obj
的 proto
,所以當我們輸入 obj2.prop2
時,其實會和 obj.porp2
同樣指向記憶體中相同的位置。
我們其實不用把這個觀念想得太複雜,當我們呼叫物件的屬性時,如果找不到便會向它的 prototype
尋找,找到了同樣可以把它當作物件的屬性來呼叫。
範例
讓我們透過範例進一步了解這個概念,這個範例其實不會在一般的開發中這麼使用,因為現代瀏覽器提供了其他方式來使用它,如果按照範例使用,還會造成開發應用程式的效能問題。
1 | var person = { |
再次強調不要在實際開發上這麼使用,這只是為了方便了解原型鍊 Prototypal Chain
的概念而已。
首先,為了讓新建立的物件 john
使用到 person
的方法 getFullName
,我們要將 john
的 prototype
指向 person
,在 JavaScript 我們可以透過兩個下底線 _
包住 proto __proto__
來指定,這麼一來輸入 john.getFullName()
時因為找不到,就會沿著 Prototypal Chain
找到 person
的 getFullName
方法了。
1 | // don't do this EVER! for demo purpose only !!! |
那麼如果在這之後單純呼叫物件 john
的屬性呢 ?
1 | console.log(john.firstname); // John |
會發現結果還是物件 john
自己的屬性而不是 person
的 Default
,因為原型鍊的概念是,我在物件本身找不到時,才會沿著 prototype
往上尋找。
接著我們來看到另一個範例,首先,建立一個只有 firstname
屬性的物件 jane
,同樣地,我們將它的 portotype
指向 person
,所以當它呼叫 getFullName
方法時,因為物件本身沒有,自然就會往 protype
物件 person
尋找 ; 但是該物件並沒有 lastname
屬性,所以延續前面提到的,當物件沒有對應的屬性和方法時,就會往 prototype
尋找,結果就會是 person.lastname
的 Default
了。
1 | var jane = { |