嘗試了解 JavaScript 的原型鏈

前言

這一篇其實我一直猶豫該不該寫,因為我深怕一個地方紀錄錯了會導致其他看的人也跟著錯,但是想一想我當初撰寫 Hexo 部落格的初衷是做自己的筆記後就又好點了,這一篇來記錄一下 JavaScript 的原型鏈這個主題

JavaScript Class

首先在了解原型鏈之前,必須先明白到一件事情,也就是 JavaScript 並沒有 Class,而你看到的 class 也只是 ES6 出的一個語法糖而已

(※ 語法糖: 意旨方便給你一個甜頭,方便撰寫而已。)

原型鏈的概念是什麼呢?打個比方來講,用前端技術做比喻,我們在學習前端時大多路徑是這樣子 HTML & CSS > JavaScript > JavaScript library or Framework

當我們以初階的前端工程師來講(僅會 HTML & CSS 、 JavaScript),我們前端網頁技術的基礎就是來至於這三種,而後續所學的 jade/pug、Sass/SCSS、jQuery、Vue.js、Angular、React 等,都是基於這三個技能為基礎做延伸的,所以當我們在使用 Vue.js 的時候,也是可以使用 JavaScript 原本的寫法,而過程中當你使用了非 Vue.js 所提供的 API 時,就會往上去尋找 JavaScript 是否有支援這個語法(Vue.js > JavaScript)。

(我自己臨時想的形容可能很差,求體諒)

如果這種說法不清楚的話,可以試著聽聽看我朋友宇軒所提供的原型鏈說法

1
2
3
4
5
6
7
8
9
10
11
12
如果要解釋給人聽的話
prototype 原型 我會想用遊戲的方式來比喻

像是用 RO 來說的話
初心者就好像是所有職業的原型
不管你之後的一轉、二轉、三轉、進階二轉

全部都是繼承初心者的

也就是說初心者的技能(方法)大家都會,

因此你如果是盧恩騎士,你仍然可以使用緊急治療 (從初心者那裏繼承來的方法,盧恩騎士本身不會,所以當盧恩騎士使用時,是從盧恩騎士>騎士領主>騎士>劍士>初心者) 這樣子慢慢往回找有沒有緊急治療這個技能 (方法)

而上述這幾種說法中都包含了原型鏈與繼承。

原型繼承

首先假設前面這邊會撰寫 ES5 的原型(prototype),後面再來講 class 寫法。

prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//首先我們先建立一個變數
let z = function () {
this.a = 1;
this.b = 2;
}

// 然後透建構子將 z 變成原型
const i = new z(); // 此時就有一個 z 的原型,而上面 a = 1,b = 2

// 接下來替 z 原型增加屬性
z.prototype.c = 3;
z.prototype.d = 'Hello' + this.a;

// 接下來看看各別會輸出什麼
console.log(i.a); // 1
console.log(i.b); // 2
console.log(i.c); // 3 ,原本的沒有這個原型,所以他會透過原型鏈去找
console.log(i.d); // Hello 1 ,原本的沒有這個原型,所以他會透過原型鏈去找
console.log(i.e); // 找完了所有原型鏈沒有任何東西,所以就回傳 undefined

prototype

讓我們用圖解來理解一下

首先我們宣告了一個變數並將它轉換為一個原型

prototype

接下來再針對原型加入東西

prototype

也因為這樣子,所以我們第一個 console.log 找 a 的圖徑是這樣子

prototype

以此類推到 c 就不太一樣了,因為這個原型並沒有 c 這個東西,所以它繼續往下找

prototype

所以當然到 i.e 的時候,會出現 undefined 因為沒有這個東西

ES6 class

這邊基本觀念與上面是一樣的,只是寫法不一樣而已,但是會更直覺,畢竟你想看看如果每一個資料都要掛在原型下,不就會有很多 xxx.prototype.xx,這樣子不但不直覺,也會導致程式碼混亂

首先我先將上面 prototype 的做法改成 ES6 Class

1
2
3
4
5
6
7
8
9
10
11
12
class z {
constructor(name) {
this.a = 1;
this.b = 2;
}
}
const i = new z();
console.log(i.a); // 1
console.log(i.b); // 2
console.log(i.c); // undefined
console.log(i.d); // undefined
console.log(i.e); // undefined

class

此時會發現 cd 會是 undefined,前面我們是這樣加入原型 z.prototype.c,但是語法糖 class,會更直覺一點,直接集中寫進去 constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class z {
constructor(name) {
this.a = 1;
this.b = 2;
this.c = 3;
this.d = 'Hello' + this.a;
}
}
const i = new z();
console.log(i.a); // 1
console.log(i.b); // 2
console.log(i.c); // 3
console.log(i.d); // Hello1
console.log(i.e); // undefined

class

透過語法糖來撰寫的好處就是可以集中管理,否則以往到處 z.prototype.cz.prototype.cz.prototype.,其實在管理程式碼上很痛苦,不單很難找又要看完整程式碼才能知道它附屬於哪一個原型。

結論

對於原型可能還是沒有解釋得很清楚,之後應該會在更努力專研一下,畢竟這東西有點特別,看了很多人寫的文章都有滿多種切入點的。

0%