昨天Prototype世界的例子中,小白這種打電話讓別人再去打電話找的方式,其實就是一種Prototype Chain的概念。

畫一張示意圖會比較清楚一些,用一個空的陣列來做舉例。

首先,陣列也是一種物件,空陣列代表裡面什麼都沒有,不過不代表不能從空陣列去獲取東西。

當我要從物件裡面找一個不存在的屬性,JavaScript 就會直接從它的原型物件去找,要是它的原型物件沒有,就會再往上找它的原型物件,直到最上面,都沒有那就是找不到。

而這個最上面的原型物件就稱作叫Object.prototype
真的就最上面了,再往上找就是null

而這一個由下往最上尋找的過程,一層一層相依尋找,就是原型鏈。

Object.create

這個可以拿來創造出一個擁有原型物件的新空物件,裡面的參數就帶要被繼承的原型物件,比如說:

1
2
3
4
5
const food = {
eat: true,
};

const apple = Object.create(food);

這個新被創造出來的物件其實原型物件就會自動綁定是 food 了,所以現在的 apple 也可以去看 eat 的這個屬性。

1
console.log(apple.eat); //true

除此之外,Object.create還有一個很厲害的地方,它可以藉由放第二個參數,直接為新的物件增加額外的屬性。

1
2
3
4
5
6
7
8
9
10
11
const food = {
eat: true,
};

const apple = Object.create(food, {
red: {
value: true,
},
});

console.log(apple.red); //true

Object.getPrototypeOf()

昨天只講到如何設定原型,其實還可以獲取原型,那就是使用Object.getPrototypeOf(),這個語法會回傳物件中的原型物件。

用上面的例子來舉例:

1
2
3
4
5
const food = {
eat: true,
};
const apple = Object.create(food);
console.log(Object.getPrototypeOf(apple) === food); //true

apple 的原型物件就是 food,所以答案是 true,證明沒想錯。

__proto__其實也能做到相同的事情,不過使用__proto__來進行設定或是獲取都是件不太好的事情(詳情請看昨天),所以請記得,現在主流的設定跟獲取方式:

  • Object.getPrototypeOf() => 獲取
  • Object.setPrototypeOf() => 設定

但是__proto__真的就一無可取之處嗎?那倒也不是,有一個用法的情境是不會被反對的。

__proto__當作屬性時


圖片來源: MDN

看起來 MDN 裡面比較偏向說,不要使用. __proto__的方式,但把__proto__當作屬性時算是一個還蠻 OK 的方式。

也有查到其他地方也有這種說法:

The only usage of proto, that’s not frowned upon, is as a property when creating a new object: { proto: … }.
說法出處:https://javascript.info/prototype-methods

讓我們來看看這方法是怎麼實作的吧。

創建一個新物件時,可以把__proto__當作是屬性,然後它的 value 直接設定成所想要的原型物件。

有一個重點是,__proto__的值必須是物件或是 null,比如說值是字串就無法,其實蠻合理的,因為字串不能變成一個原型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const food = {
eat: true,
};

const apple = {
color: "red",
__proto__: food,
};

const coconut = {
hardness: true,
__proto__: apple,
// 怕大家不知道所以講一下,coconut是椰子
};

在這邊有三個新創的物件,我利用__proto__當屬性設定的方式,讓這三個物件形成一個原型鏈,藉由把 apple 的__proto__設定成 food,再把 coconut 的__proto__屬性設定成 apple,這樣一來,理論上應該可以在 coconut 使用到 food 的屬性,我們來測試看看。

1
console.log(coconut.eat); //true

看來結果沒有錯,確實已經藉由__proto__當屬性的方式,把這三個物件串個原型鏈。

不要做的事情

現在已經提到了Object.setPrototypeOf以及obj.__proto__都可以直接的去更改原型物件,影響整體的Prototype Chain,但這是一件好的事情嗎?

關於這點去研究後,發現有些文章會說這個行為其實不是一件很好的事情,如下:

圖片來源:https://javascript.info/prototype-methods

大意就是,如果你今天覺得,速度很重要,就不要去修改已經存在的原型了,昨天有提到說,物件只能指定一種原型物件,要是去修改它,就會去覆蓋掉原本的原型物件,而這種行為,照那篇文章說,會去破壞掉object property access operations的內部優化,簡單說就會會讓javascript的執行速度變得很慢。

除非修改的重要程度比運行的速度還要重要,不然通常在創建物件時,就只會設置那麼一次,就不會再修改了,確實設計上是想改就改,但是除非有很確定自己在做什麼,不然我的理解是避免去做這件事情。

總結

可能其它程式語言並不是如此,但在javascript中就是透過Prototype Chain來把上下關係給連接起來,藉此來達到繼承的方式,推薦大家可以去看 huli 的 該來理解 JavaScript 的原型鍊了,裡面講的很清楚好懂,那麼今天就是這些,我們明天見!

reference

[1] Object.getPrototypeOf()
[2] Native prototypes