iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 21
1
Modern Web

JavaScript 原力覺醒 - 成為絕地武士之路系列 第 21

JS 原力覺醒 Day21 - 原型


上一篇提到 JS 是物件原型導向,而非物件導向的語言,如果想要像物件導向那樣達成物件之間屬性的共用,就需要借助原型的幫忙,所以了解「原型」的概念,對於我們後續想要活用 JS 的物件,或是在 JS 裡面撰寫物件導向風格的程式碼的話是非常重要的。

Outline

  • 物件導向:類別與物件
  • 物件導向:繼承的概念
  • 函式上的原型物件屬性
  • 透過函式建構子產生的物件
  • 總結:原型物件屬性

物件導向:類別與物件

在物件導向語言裡面,類別定義了描述某件事或某個功能的基本概念,就像一件商品或是建築物的設計圖ㄧ樣;而物件則是透過類別裡所描述的的概念實現出來的東西,對比於建築設計圖,就是建築物:

  • 類別 ←→ 建築設計圖
  • 物件 ←→ 建築物

https://ithelp.ithome.com.tw/upload/images/20191006/20106580XxkBIolrjB.jpg

當然上面的比喻只能說是非常非常粗淺的描述,完整的物件導向概念是非常博大精深的。這邊是想讓各位讀者了解它的原理,以及從什麼出發點被創造出來的,知道物件導向的根本概念後,後面我們解說 JS 原型的時候,就不會那麼不知所以然。

物件導向:繼承的概念

前面也提過原型存在的目的是為了達到繼承,那麼我們先來看看繼承是怎樣的概念,在物件導向裡的繼承是指類別可以以另一個類別為基礎,再往上進行擴充、或是修改,這樣一來就可以用很方便且較低成本的方式創造新的類別,因此,姑且說繼承的目的是為了讓「某些屬性可以共用」且可以減少重複。

https://ithelp.ithome.com.tw/upload/images/20191006/20106580UZUALPHjdm.jpg
用生活化的方式比喻的話繼承與被繼承物件之間的關係,有點像圖片內的「動物」這個總稱與「鳥」這樣更明確的稱呼,鳥也是動物的一種,所有動物都有特定共用的行為例如呼吸,但是有些行為可能只有鳥類做得出來例如飛行,因此可以知道,繼承可以讓物件同時具有共用的部分與較為特定的部分。

函式上的原型物件屬性

在物件導向裡面有類別的概念讓物件得以用很快速清楚的方式擴充,而 JS 裡面只有「物件」,所以只能用模擬的方式來達成類似的效果 — 那就要透過原型的幫忙。

前面在講繫結的時候我們提到,函式可以搭配 new 運算子成為「函式建構子」來產生物件,我們先來討論函式建構子的概念是什麼。在 JS 裡面,函式建構子其實與一般函式呼叫沒有差別,只是前面多了 new 這個關鍵字而已。

而在 JS 裡面,一個函式被創造出來的時候,JS 引擎會新增一個 prototype 屬性到這個函式上面,這個 prototype 是一個物件,我們姑且稱之為「原型物件」,在原型物件裡面我們可以找到一個指回該函式的constructor 屬性。

https://ithelp.ithome.com.tw/upload/images/20191006/20106580QnyFbprfLV.jpg

我們用下面的程式碼來當作例子:

 function User(firstName, lastName) {
	this.firstName = firstName,
	this.lastName = lastName,
	this.fullName = function() {
		return this.firstName + " " + this.lastName;
	}
}

var user1 = new User("Gin", "Tsai");

console.log(user1) 

我們用 User 函式當作函式建構子來產生物件,這個函式上面會有一個 prototype 屬性,且他是一個物件,裡面有另外兩個屬性:

  • 剛剛提到的 constructor 屬性,指向回該建構函式 ( User )
  • _proto__ 屬性 ,裡面又是另外一個物件,這一點後面會再詳談

透過函式建構子產生的物件

那麼,當物件透過這個函式建構子被產生之後,會不會有什麼特別的地方呢?相對於 JS 引擎在 function 上面加上 prototype 屬性,在這個新生成的物件上則是會被加上一個 __proto__ 屬性,這個屬性恰好是指向剛剛函式建構子的 prototype 物件。

https://ithelp.ithome.com.tw/upload/images/20191006/20106580aINQGXfUmW.png

User.prototype === user1.__proto__  //true

因此我們透過上面的例子可以得出這樣子一個結論:透過函式建構子生成的物件,其上面會有一個指向該物件所屬函式建構子 prototype 屬性的 __proto__ 屬性,也就是該新生成物件的「原型」。

https://ithelp.ithome.com.tw/upload/images/20191006/20106580sg56q0rUHF.jpg

現在讓我們用同樣的方式創造第二個使用者 user2 ,因為一樣都是透過函式建構子所產生的物件,因此在這個物件上照理說也會有一個 __proto__ 屬性並指向產生這個物件的函式建構子上的原型物件,所以我們可以知道只要是透過函式建構子被生成的物件,他們之間都有一個共享的原型物件( prototype,先知道這一點很重要。

https://ithelp.ithome.com.tw/upload/images/20191006/20106580UpsGcXBrmb.jpg

總結:原型物件屬性

現在我們知道了被生成物件與建構函式之間的關係:

所有透過函式建構子生成的物件,都透過 __proto__ 屬性與函式建構子上的 prototype 屬性做連結,或是說共享這個屬性。

但是光知道這些還沒有辦法知道實際的應用,下一章節我們會介紹這個部分,就讓我們往下看看 JS 是怎麼透過原型來達到繼承以及減少相同函式宣告的重複性的。

參考資源

Prototypes in JavaScript


上一篇
JS 原力覺醒 Day20 - 物件
下一篇
JS 原力覺醒 Day22 - 原型共享與原型繼承
系列文
JavaScript 原力覺醒 - 成為絕地武士之路30

尚未有邦友留言

立即登入留言