--- title: 'JS 核心 21 - 繼承與原型介紹' tags: JS 核心 ,JS , JavaScript description: 2021/02/18 --- JS 核心 -- 繼承與原型介紹 === ## 原型鍊的概念 - 為什麼有原型 ### 程式語言 JavaScript 名稱由來 1. 程式語言 JavaScript 與 Java 名字很接近,但寫法差距很大。 2. 在推出 JavaScript 時基於商業考量,希望能夠吸引 Java 開發者投入 JavaScript 的開發,所以將程式語言名稱取為 JavaScript。 3. avaScript 的特性也是源自於 Java,就是使用 new 方法來新增物件。Java 開發者看到可使用 new 方法來新增物件會覺得兩者看起來很相近,但兩者的根本運作有很大的不同。 4. 透過 new 方法所新增物件會有繼承的特性,兩者在繼承實作方式上有很大的不同。 5. 在 Java 裡面是屬於 <span class="red">**類別繼承**</span>。 6. 在 JavaScript 裡面是屬於 <span class="red">**原型繼承**</span>。 ### Java <span class="red">**類別繼承**</span> (物件導向) > class 屬於 Java 物件導向的概念 1. 當需要定義狗,且數量不只一隻,要重複定義時,就會定義一個類別 class。 (如下右圖) 2. 類別 class 名稱就是 "狗",在 "狗" 之下會定義狗應有的一些屬性及方法。 3. 可以透過類別 class 定義更多的狗出來。 ![](https://i.imgur.com/KTnQGU5.png) ### JavaScript 使用 <span class="red">**原型繼承**</span> >「原型」繼承可以讓本來沒有某個屬性的物件去存取其他物件的屬性。 > 繼承表示一個物件取用另一個物件的屬性或方法。 > JavaScript 中的所有物件都有原型屬性,這個屬性會參考到另一個物件,我們稱為原型 proto(被參考的物件)。 ( 如上左圖示 ) * class : 想像中的狗映像,沒辦法應用,只是想法 * instance 實體: 透過 new 方法,把想法實體化,可以定義特徵 (ex 色彩、體型、吼叫),實體的狗由狗的思想延伸出來的,這就稱為繼承。 * <span class="red">**繼承 : 一個實體可以取用另一個物件的屬性及方法**</span> * 所有的狗都有色彩、大小,繼承了此**屬性** * 所有的狗都會叫,繼承了此**方法** > 透過原型的方式做出類似 class 繼承的方法 * JavaScript 特性 : 根本都是物件。任何內容都是以物件的方式做建立。 * JavaScript 並沒有 class 概念。所以必須以原型的方式做出類似 class 的方法。 --- ## 原型在哪裡? 使用**物件**的方式來定義一隻狗。 若狗要建立實體,透過此原型物件來繼承原有的屬性和方法。(兩個物件的概念) ![](https://i.imgur.com/HaPRrm6.png) 在運行 JavaScript 時,當建立物件實體就有它自己的屬性和方法。 ### 原型的特性 * 實體物件有自己的屬性 ; 原型物件也有自己的屬性 (結構上都是使用物件)。 * 實體可以繼承一個原型,原型可以繼承另一個原型,另外一個原型也有自己的屬性和方法。 * 原型鍊 : 繼承可以一段一段向上繼承。 * 在原本的實體內取不到屬性,透過原型鍊方式不斷向上查找,直到原型鍊頂端為止。 * 若從原型新增兩個實體,兩個實體會共用同一個原型的方法。 ![](https://i.imgur.com/PgzPmjc.png) ### :pencil2: 範例 : 顯示陣列裡的最後一個值 1. 「實體物件」可以透過「點運算子」來取用**自己**的屬性及方法 * 也可透過「點運算子」不斷向上查找來取用**原型**的屬性及方法 2. 使用 forEach() 方法 : 可以把陣列裡所有的值一一取出並執行 * forEach() 方法不是 a 實體原本的屬性; 是「陣列原型」裡面的屬性 3. a、b 兩陣列實體都同時繼承「陣列原型」下 * 原型是共用的,_ _proto_ _ 連結的是陣列的原型,可透過此方式在「陣列原型」上新增內容 ``` var a = [1, 2, 3]; // a 為一實體陣列 (也屬於物件) (陣列的本質就是物件) console.log(a, a[1], a.length); // 結果如下左圖 a.forEach(function(i) { // 取用原型方法 forEach 把 a 陣列裡的值一一取出並執行 console.log(i); // 1 2 3 }); var b = [4, 5, 6]; a.__proto__.getLast = function () { // 透過 a.__proto__ 的方式在原型鏈上新增方法 (如下右圖) return this[this.length -1]; // this 代表此陣列。將陣列的最後一個數字取出 // 類似 return b[b.length -1]; } console.log(a, b); // [1, 2, 3] [4, 5, 6] console.log(a.getLast(), b.getLast()); // 3 6 (有共用的原型方法) ``` ![](https://i.imgur.com/PAcjzUG.png) * <span class="red">實際開發上**不建議直接對 _ _proto_ _ 新增功能**</span> * 使用此方法很容易發生汙染,也會不知道該方法是屬於哪個功能底下 * 實際開發上建議使用 prototype ### 陣列原型又繼承物件的原型 (有屬於自己的方法及內容) > 原型是有多個層級的,可以不斷向上查找 > <span class="red">**物件原型沒有 _ _proto_ _**</span> ,因為他是最頂層的物件原型內容 ```typescript= var a = [1, 2, 3]; var b = [4, 5, 6]; a.__proto__.getLast = function () { // 透過 a.__proto__ 的方式在原型鏈上新增方法 return this[this.length -1]; // this 代表此陣列。將陣列的最後一個數字取出 } console.log(a, b); // [1, 2, 3] [4, 5, 6] console.log(a.getLast(), b.getLast()); // 3 6 var family = { // 新增另一個物件實體 name: '小明家' } family.__proto__.getName = function() { // 在 family 物件的原型下新增 getName 方法 return this.name; } console.log(family, family.getName()); // {name: "小明家"} 小明家 (物件展開如下圖) ``` (第 3 行) 在陣列原型下新增 getLast 方法 (第 12 行) 在物件原型下新增 getName 方法 (如下左圖) (第 6 行) 原型就是不斷向上查找的概念,打開陣列實體,再打開陣列原型,再打開物件原型,就可看到有 getName 方法 (如下右圖) ![](https://i.imgur.com/jO606w0.png) :pencil2: **試著在陣列裡面使用 getName 方法** 陣列的本質就是物件,可以在陣列上新增屬性 name ```typescript=16 b.name = '陣列的屬性'; // 陣列的本質就是物件,在陣列上新增一個屬性 name console.log(b.getName()); // 陣列的屬性 (源自於物件原型裡的 getName 方法) console.log(b.toString()); // 4 5 6 (使用物件原型下的方法 toString) ``` 1. **原型**有不斷向上查找的特性,所以 b 可使用物件原型的方法 getName 2. a、b 兩「陣列實體」都同時「繼承陣列原型」下 * 此「陣列實體」除了可以用自己原本的屬性外,也可用陣列原型內的所有方法 * 原型有向上查找的特性,「陣列原型」又繼承於「物件原型」之下 * b 屬於「陣列原型」下的「陣列實體」,依然可以使用「物件原型」下的方法 ## :memo: 學習回顧 :::info * Java 使用類別繼承。 * class 屬於 Java 物件導向的概念。 * JavaScript 使用原型繼承。 * 「原型」繼承可以讓本來沒有某個屬性的物件去存取其他物件的屬性。 * 繼承表示一個物件取用另一個物件的屬性或方法。 * 在繼承的行為裡,透過被繼承的「後代類別」,所產生出來的物件,一開始就應該要直接具有「前代類別」的屬性跟方法。 * JavaScript 中的所有物件都有原型屬性,這個屬性會參考到另一個物件,我們稱為原型 proto(被參考的物件)。 * JavaScript 透過原型的方式做出類似 class 繼承的方法 * JavaScript 特性 : 根本都是物件。任何內容都是以物件的方式做建立。 * JavaScript 並沒有 class 概念。所以必須以原型的方式做出類似 class 的方法。 * 原型是什麼? 一種讓別的物件繼承其中的屬性的物件。 ### 原型的特性 1. 一樣具有物件的特性。 * 實體物件有自己的屬性 ; 原型物件也有自己的屬性 (結構上都是使用物件)。 2. 原型鍊 : 繼承可以一段一段向上繼承。 * 實體可以繼承一個原型,原型可以繼承另一個原型,另外一個原型也有自己的屬性和方法。 3. 向上查找的特性 * 在原本的實體內取不到屬性,透過原型鍊方式不斷向上查找,直到原型鍊頂端為止。 4. 原型可共用方法及屬性 * 若從原型新增兩個實體,兩個實體會共用同一個原型的方法。 * 原型是共用的,_ proto _ 連結的是陣列的原型 --- * 「實體物件」可以透過「點運算子」來取用自己的屬性及方法 * 也可透過「點運算子」不斷向上查找來取用原型的屬性及方法 * 物件原型沒有 _ proto _ ,因為他是最頂層的物件原型內容 ::: ## :+1: 相關參考文件 :::info [原型(Prototype)](https://ithelp.ithome.com.tw/articles/10205313) [KURO -物件與原型鏈](https://ithelp.ithome.com.tw/articles/10194154) [物件導向與原型繼承](https://huangpei.medium.com/%E5%85%8B%E6%9C%8Djs%E5%A5%87%E6%80%AA%E7%9A%84%E9%83%A8%E5%88%86-%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%88%87%E5%8E%9F%E5%9E%8B%E7%B9%BC%E6%89%BF-4edf1d5c9024) [該來理解 JavaScript 的原型鍊了](https://blog.techbridge.cc/2017/04/22/javascript-prototype/) ::: <style> .red { color: red; } .green { color: green; } </style>