iT邦幫忙

2022 iThome 鐵人賽

DAY 4
0
Modern Web

JavaScript 之路,往前邁進吧!系列 第 4

JS之路 Day04 - Prototypal Inheritance(原型繼承)

  • 分享至 

  • xImage
  •  

Array Methods繁多,許多人常常使用時會在去MDN看文檔,但如果你仔細看會發現奇怪地方,比如說你想要去查map的語法,你實際會看到Array.prototype.map()

更奇怪的地方是,有些會有.prototype,有些沒有,當初我初學時百思不得其解。

其實這就跟今天主題的Prototypal inheritance有關,讓我們從一開始提到的mapArray Methods開始看起,寫一個簡單的例子:

const arr = [1, 2, 3, 4, 5];

const arr2 = arr.map((n) => n + 1);

console.log(arr2);

會發現說,我明明沒有寫這個方法,但是我可以用,why?
arr裡面明明就只有12345。這就是原型繼承的奧妙之處。

arr = [1, 2, 3, 4, 5]其實可以看成是arr = new Array([1, 2, 3, 4, 5])

其中Array就是內建的一個Constructor Function,它自身的prototype會指向一個帶有map(方法)跟其他一堆的方法的超大物件。

在這邊arr.map其實就會等於Array.prototype

意味著arr會連結到它的原型物件,所以它可以使用它的原型物件所有的方法,就是剛剛提到的超大物件,如下圖:

可以理解成,這個arrarray繼承了map方法,在arrarray本身其實是沒有去定義方法的,全部都在它的原型物件上了,我們可以確認這件事情是不是真的,來驗證吧。

證明這件事情 => 原型繼承

第一步是看arr.__proto__會不會跟Array.prototype相等

arr.__proto__代表的是arr這個array它的原型物件。
Array.prototype是Array這個constructor的原型。
結果是true那其實就代表他們是相同的。

const arr = [1, 2, 3, 4, 5];
console.log(arr.__proto__ === Array.prototype);// ture

第二步是看arr.map是不是跟Array.prototype.map相同。

這邊是為了要證明我上面所講的,arr.map其實就會等於Array.prototype這件事情。
結果是true那其實就代表他們是相等的。

const arr = [1, 2, 3, 4, 5];
console.log(arr.map === Array.prototype.map);

第三步是看arr.map會不會跟arr__proto__.map一樣。
繼承的概念,就是當這個物件找不到了,它會從它的上層原型物件去尋找,所以理論上當arr找不到它的map方法時,應該可以從arr__proto__這個arr的上層原型物件去拿到map方法。

結果還是true,代表上面想法沒有錯誤。

const arr = [1, 2, 3, 4, 5];
console.log(arr.map === arr.__proto__.map);

最後得出了一個結論,arr確實成功了繼承了原型。

那些沒有.prototype的方法

剛剛是講有的,現在來講沒有的。
這邊用Array.isArray()來舉例。

圖片來源:MDN

為什麼不是Array.prototype.isArray(),其實道理很間單,我的理解是因為不能確定我使用的對象是不是Array,所以我不能使用Array的原型物件來繼承。

像是Array.isArray()這個方法就是用來判斷這個東西是不是Array的,所以結果可能是也可能不是,所以沒辦法斷定說就是Array。

所以必須在寫的時候,不能使用.prototype的方式,要直接把Array給寫出來,我們來做個差異化比較:

// 有.prototype => Array.prototype.map()
const test = [1, 2, 3, 4];
// test.map就會等於Array.prototype
console.log(test.map(a => a * 2)); // [2, 4, 6, 8]

// 沒有寫.prototype => Array.isArray()
const test2 =[1, 2, 3, 4];
// 判斷的東西,是放在參數裡面的概念。
console.log(Array.isArray(test2)); // true


Object.prototype

原型繼承還有一個有趣的地方,就是剛剛講解的那個是Array的原型,但就算是Array,它的頂點原型也不是Array.prototype,而是Object.prototype

也不只是Array而已,全部的內建原型的頂點通通都是Object.prototype

所謂的頂點就是,上面已經沒有東西,高處不勝寒,想要再繼續往上的話只看的到null而已。

所以我會理解成,在JavaScript中所有的繼承都是由Object.prototype繼承而來,然後我去查詢相關爬文,也有一些人這麼說:

All objects inherit the properties and methods of Object

這句話出處:JavaScript Prototypes and Inheritance – and Why They Say Everything in JS is an Object

意思是其實所有的物件都是繼承了object的屬性跟方法,null不算,因為它沒有原型,只是充當原型鏈的最後一環。

增加更改原型繼承

除了昨天講到不要用Object.setPrototypeOf以及obj.__proto__去指定變換原型物件之外,還有也不要去增加更改原型繼承。

像是上面有提到的Array.prototype.map()方法,它是內建寫好的,但其實也可以自己寫一個方法塞進去,像是:

String.prototype.good = function() {
  console.log(this+"good");
};

"apple".good(); // apple good

這好嗎? 這不好。

原因有幾個:

  • 假如是在跟別人協作,你弄了一個自己寫的方法放入自己的原型物件,然後自己繼承用的很開心,但是別人看到會一頭霧水,不知從何而來。
  • 因為原型物件是global的,很容易造成衝突,像是說有兩個函式庫都添加了String.prototype.good這個方法,就會造成前面蓋後面。

自己需要用的話,那就直接寫一個方法給自己用就好,其實不太需要把這個方法,弄給全部人都能繼承,因為大部分javascript覺得你需要的方法都已經幫忙內建寫好了。

總結

希望大家看完這篇有比較懂為什麼大部分方法都有加上.prototype,以及對於原型繼承的部分有更實務上的理解,之前比較偏向講原理,而實際有運用到原型繼承的我想就是這些族繁不及備載的內建方法了吧,以上就是今天的介紹。

reference

[1] Inheritance and the prototype chain


上一篇
JS之路 Day03 - Prototype Chain(原型鏈)
下一篇
JS之路 Day05 - Constructor Function(構造函式)
系列文
JavaScript 之路,往前邁進吧!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言