2021年5月1日 星期六

原型鏈與繼承 | Javascript | 克服 JS 的奇怪部分

Photo by Aida L on Unsplash

前言

過去在學習 JavaScript 的時候,常常會聽到原型鏈這個名詞,那時候看了許多的資料,卻還是被它錯綜複雜的關係給迷惑。比如: __proto__prototype 的區別是什麼? 它們之間又有什麼關係。直到最近在 Udemy 上了 克服 JS 的奇怪部分 這門課之後,才對原型鏈有了較深刻的體會。下面就把我學習的心得寫出來和大家交流交流~~

原型鏈到底是什麼?

原型鏈其實是 JavaScript 實現繼承的一個機制。這是什麼意思呢? Anthony Alicea 用了一個簡單的例子來說明。

在上面的圖中,obj 代表一個物件,而這個物件有一個 prop1 的屬性。我們可以使用 obj.prop1 來存取它。
不過,除了 prop1 之外, JavaScript 引擎還偷偷替 obj 增加了一個屬性: __proto__。這個 __proto__ 其實指向了第二個物件。我們可以看到,這個物件有一個 prop2 的屬性。此時,當我們使用了obj.prop2,會發生什麼事呢?
首先,JavaScript 引擎會先搜尋 obj 物件本身有沒有 prop2。因為 obj 沒有 prop2,所以JavaScript 引擎會繼續搜尋它的 __proto__ 所指向的第二個物件。這時候,因為第二個物件有一個 prop2,因此它就會回傳這個 prop2。
同理,第二個物件也會有它的 __proto__,而這個 __proto__ 又會指向第三個物件。因此,當我們使用 obj.prop3 時,就會取得第三個物件的 prop3。看到這裡,我們可以漸漸體會到,JavaScript 是使用 __proto__ 這個屬性來將所有具有繼承關係的物件串接在一起。這看起來就像一條鍊子一樣,所以我們才會將其稱做原型鏈

上點程式碼吧!

我們用一段程式碼來具體說明一下上面的例子。

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());

這段程式碼中有兩個物件: personjohn。這兩個物件都有 firstnamelastname 兩個屬性,而 person 還有一個 getFullName() 的函式。剛剛有提到,每一個物件都會有一個 __proto__ 的屬性,而這裡我們將 john 的 __proto__ 指定為 person (這裡只是用來示範,實際上由於效能的考量,並不會這樣寫)。
如此一來,當執行 john.getFullName() 時,JavaScript 引擎首先會去找 john 本身有沒有 getFullName()。因為找不到,所以會繼續去搜尋 person ,最後就會找到了 person 的 getFullName()。因此,上面程式碼執行的結果會顯示:

> John Doe

由這個例子可以看出,john 執行的 getFullName() 是 person 的版本,這就好像 john 繼承了 person 一樣。因此,一開頭我們才會說原型鏈是 JavaScript 實現繼承的一個機制。

小結

在本篇文章中,我們對 JavaScript 的原型鏈有了初步的認識。不過有關 JavaScript 物件導向的議題還包括 function constructor、new operator 等等。這部分就留待下次討論吧~~

參考資料


沒有留言:

張貼留言