前言

我相信每個語法的發明都有它意義存在,以我看來Prototype的存在,是為了要讓JavaScript 也能實現物件導向,準確點來說,可以用來做到物件的繼承,透過Prototype的方式。

Prototype 的產生是為了 JavaScript 的繼承

而所謂物件的繼承,就是可以從其他地方拿到本身沒有的方法或是屬性,藉由去繼承有方法跟屬性的物件,來獲得使用那些方法跟屬性的權利。

JavaScript中,每個object都會去連結到Prototype object => 每個object都可以看得到裡面有Prototype(null是例外~)。

這個Prototype[[prototype]]也是原型物件。

舉例來說,創造一個空物件,會看見[[Prototype]]:

但是用Object.create使用null當原型的這個新物件是會看到no properties,很乾淨的那種。

Prototype object 包含了 properties(屬性) 跟 methods(方法)

簡單解釋一下什麼是propertiesmethods
首先要先知道物件會有key-value pairs,接著看下方程式碼:

1
2
3
4
5
6
7
8
const fruit = {
name: "apple", // name => properties
weight: "300g", // weight => properties

callSell: function () {
console.log("好吃的蘋果,快來給我買");
},
}; // callSell => methods

value 是一般值 => properties
value 是一個函式 => methods

意思是連結到這個Prototype的所有 object,都可以去使用這個Prototype objectproperties以及methods,這就是Prototype的奇妙之處,我沒有但我可以還是用。

再說一次,這種我沒有,但是我還是可以用的行為,其實是一種繼承的概念,因為是用原型來達成的,所以會把它稱為原型繼承。

如何設定Prototype

為什麼要設定,要如何設定,讓我用一個例子來舉例:

Prototype世界的小白家有餅乾,小紅家有花朵,他們住在不同的地方,要去拿到小白的餅乾,就沒辦法拿到小紅的花朵,反之也是一樣。

想要從小白那邊獲得餅乾,又能拿到花朵,這時候需要電話,比如説小白吃著餅乾,又想要手上拿著花朵,但這時候小白家沒有花朵,但是可以打電話給小紅,叫她把花給速速送過來。

這個電話號碼其實就是Object.setPrototypeOf

這個語法會需要兩個參數,如下用 A 跟 B 來表示:

1
Object.setPrototypeOf(A, B)

第一個參數 A 代表的是要設定原型的物件,第二個參數 B 代表的是參數 A 的新原型物件。

所以在這邊可以藉由小白獲得了小紅的電話號碼,獲得可以拿到小紅花朵這個概念,拿來比擬小白透過 Object.setPrototypeOf() 將「小紅指定為原型」。

用這個例子的範例來說,程式會長這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 小白擁有很多餅乾
const white = { cookies: true };
// 小紅擁有很多花朵
const red = { flowers: true };

// 小白獲得了可以打給小紅的電話號碼
// 指定 red 為 white 的 Prototype
Object.setPrototypeOf(white, red);

// 真是太好了,現在去找小白也能獲得花朵
console.log("flowers" in white); // true

//這是不太嚴謹的例子,因為現實中可能多試幾次後小白那邊就拿不到花朵,因為電話打太多次小紅會把小白封鎖。

在上面的程式裡面,小白就是物件,小紅就是原型物件,小白可以指定原型物件(打電話),來獲得原型物件的東西。

那假如我今天又想要在小白家拿到披薩呢? 現在知道說,小綠家有很多披薩,一樣把他的電話新增到小白手機裡,讓小白打電話給他。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const white = { cookies: true };
const red = { flowers: true };
Object.setPrototypeOf(white, red);

// 小綠擁有很多披薩 (小知識:pizza是不可數名詞,可以不用加s)
const green = { pizza: true };

Object.setPrototypeOf(white, green);

// 又多一個朋友,今晚我想來點餅乾+花朵+披薩
console.log("cookies" in white); // true
console.log("flowers" in white); // false
console.log("pizza" in white); // true

// 從小白家竟然拿不到小紅的花朵,難道小紅生氣了嗎?

小白家拿不到小紅的花朵,其實是因為Prototype世界的電話,一次只能存取一個電話號碼,小白手機裡面原本是存小紅電話號碼,但後來新增了小綠電話號碼,小紅的就消失了。

小白沒有在記別人電話號碼的,手機裡號碼不見了就不見了,現在只剩下小綠電話號碼,所以只能從小白家獲取小綠披薩。

重點 : 物件只能指定一種原型物件。

小白苦思著,竟然一次只能存取一個電話號碼,突然,小白靈光乍現,他想到了,只要叫小綠去用他的手機存取小紅電話不就好了嗎?

他打電話給小綠,小綠再打電話給小紅,之後再全部拿到小白家。

1
2
3
4
5
6
7
8
9
10
11
12
const white = { cookies: true };
const red = { flowers: true };
const green = { pizza: true };
Object.setPrototypeOf(white, green);

// 小綠獲得了小紅的電話號碼,可以打電話給小紅
Object.setPrototypeOf(green, red);

// 太好了,現在從小白家就能拿到花朵跟披薩,大家都很開心
console.log("cookies" in white); // true
console.log("flowers" in white); // true
console.log("pizza" in white); // true

透過這個友善的故事,希望能讓大家對於原型這個概念有個初步的理解,另外我還有在學習時看到一些不錯的比喻,寫得很棒歡迎去看 :

  1. 藉由洛克人打贏 boss 就會獲得 boss 新武器的機制來做原型的比喻。
    來自於重新認識 JavaScript: Day 24 物件與原型鏈
  2. 藉由猜謎問問題,遇到不會的問題就問其他人,來做原型的比喻。
    來自於忍者:JavaScript 開發技巧探秘, 2/e

設定Prototype其他方式

除了setPrototypeOf()之外其實也有一個方法可以去設定原型物件,就是__proto__,一樣可以讓小白拿到小紅的花:

1
2
3
4
5
6
7
const white = { cookies: true };
const red = { flowers: true };

white.__proto__ = red;
// 設定 小白的原型物件是小紅

console.log("flowers" in white); //true

雖然它可以也可以拿來設定,不過我們一般不會使用它,因為它已經被認為過時以及超級不推薦使用,現在都會建議使用setPrototypeOf(),我會發現是因為研究時 MDN 上看到,要是你在 MDN 上面有查過__proto__這個語法的話,你會發現點開映入眼簾的是很多的警告,很可怕,有興趣可以研究。

圖片來源:MDN

構造函式的原型

我快寫不完,最後簡單介紹一下構造函式的原型,構造函式創個一個新的物件時,這個新的物件會被設定原型,原型物件就是構造函式,可能有些人不知道什麼是構造函式,但今天再詳細解釋會有點太長,所以關於這個以後會專門做一期文章給大家講解。

簡單一個小範例介紹:

1
2
3
4
function Food(name) {
this.name = name;
}
const apple = new Food("apple");

Food 就是建構函式,然後藉由 new 去實例出 apple,這個新創造出來的物件,會被存到這個變數 apple 裡面。

然後這個 apple 它的原型物件就會是 Food,我們可以來驗證一下這件事情。

1
console.log(apple.__proto__ === Food.prototype); // true

結果沒錯,相當於就是這個新的物件會繼承構造函式的原型。


明天會繼續接著講原型鏈的/images/emoticon/emoticon06.gif

reference

[1] MDN - Object prototypes
[2] W3Schools - JavaScript Object Prototypes
[3] JS 原力覺醒 Day21 - 原型