前言
介紹原型
原型
在介紹原型之前,我們要先來複習物件的內容
當我們要定義一隻狗的時候,我們會利用物件的資料結構,來對這隻狗進行描述,例如它的顏色、體型大小,以及可以吠叫的方
法。主要的原因就是因為 javascript 基本上都是由物件的方法去構成資料,而原型也是一樣的概念!
前一篇文章,java 類別繼承中我們有提到,如果我們要定義一個像是這樣的內容,我們會使用 class 來定義它,但是 javascript
中都是使用物件來定義,所以我們在定義原型的時候也是使用物件的方式來定義。
如果狗要透過這個原型來建立實體的話,也是透過繼承的方式繼承了原型的屬性跟方法,因此這就是兩個物件的概念。
原型的狗就會有毛色、體型大小,以及可以吠叫的方法。而在實體的狗呢這些屬性就會有可以自定義的空間。
那麼吠叫的部分就可以繼承原本原型的方法。
實際上,當我們運行 javascript 的時候,新增一個物件實體的時候就會有屬於該實體的屬性,如左上圖的毛色、體型、吠叫
那麼原型的部分(如右上圖)一樣也會有它自己的屬性,那這樣的結構上依然是屬於兩個物件。
另外,除了實體可以繼承一個原型之外,原型也可以繼承另外一個原型喔!
在另外一個原型的部分也可以擁有自己的屬性以及方法,那麼這樣的繼承關係可以一段一段的向上繼承,這樣的繼承狀態我們又
稱為原型鏈。在這個實體我們要取用其中的屬性的時候,會使用 . 點運算子的方法進行取用,例如 obj.Prop1、obj.Prop2
,而當我們要取用的屬性或是方法沒有在這個實體上的時候,就會透過原型鏈向上查找,直到找到這個原型鏈的頂端為止。
除此之外,原型還有另一個特色,就是
如果用同一個原型新增了兩個實體,這兩個實體就會共用同一個原型繼承,共用相同的屬性名稱以及相同的方法。
原型的特性
- 一樣具有物件的特性
- 向上查找
- 原型可共用方法及屬性
觀念驗證
再來就透過程式碼的運作來進行剛剛上面的觀念驗證
var a = [1, 2, 3];
console.log(a);
執行之後我們把這個陣列打開來看
可以看到裡面有對應 0 1 2 的屬性,因為我們知道陣列的本質其實也是物件,所以這裡的 0 1 2 其實也是這個陣列的物件
屬性,當然 length 也是。所以物件的屬性的話我們有兩種取值的方式,一個是 [ ] 一個是 .
var a = [1, 2, 3];
console.log(a, a[1], a.length);
那麼這個陣列現在是屬於一個實體,我們可以透過 __proto__ 的屬性來看看它陣列的原型
可以看到陣列的原型中有許多的方法,我們剛剛也有提到可以透過 . 點運算子取用他的方法,這邊我們選用的是 forEach 的
方法,透過這個方法可以將陣列的每個值都遍歷過一遍。
var a = [1, 2, 3];
console.log(a, a[1], a.length);
a.forEach(function(i){
console.log(i);
})
可以看到 forEach 就不是屬於 a 這個實體的方法,而是屬於陣列原型的屬性方法。
還有我們剛剛也有講到原型是共用的,所以我們再來新增 b 這個新的陣列為 [4, 5 , 6]
__proto__ 的屬性是指向陣列的原型,所以我們照理說也可以用 __proto__ 的屬性新增方法到陣列的原型上,讓 b 陣列也可
以取用到相同的方法。
__proto__的屬性雖然可以達成一樣的效果,但一般我們還是不建議這樣使用,後面的章節會教大家使用prototype的屬
性將要新增的function掛載到原型上,讓其他實體也可以取用到。
這邊我們把陣列最後一個值取出來的方法透過 __proto__ 的屬性掛載到陣列的原型上。
var a = [1, 2, 3];
console.log(a, a[1], a.length);
a.forEach(function(i){
console.log(i);
})
var b = [4, 5, 6];
a.__proto__.getLast = function () {
return this [this.length -1];
}
console.log(a, b);
另外我們就真的使用這個 getLast 的方法看是否有成功
var a = [1, 2, 3];
console.log(a, a[1], a.length);
a.forEach(function(i){
console.log(i);
})
var b = [4, 5, 6];
a.__proto__.getLast = function () {
return this [this.length -1];
}
console.log(a, b);
console.log(a.getLast(), b.getLast()); // 3 , 6
很明顯就成功的印出我們想要的內容。
原型的層級問題
剛剛說到,打開 a 或 b 的 __proto__ 的屬性能夠找到陣列的原型。
那麼繼續往下找又可以找到另一個 __proto__ 的屬性,再把它打開以後會看到的是物件的原型,因此這個陣列的原型其實是繼
承了物件的原型,而繼續往下看就可以看到物件的一些方法,但到最底就沒有 __proto__ 的屬性,代表說這個物件的原型就是
原型鏈的最頂層了 !
我們再來看一些 code
我們新增了一個新的物件,並且對該物件的物件原型上掛載 getName 的方法,而之後打開 a 跟 b 的原型鏈最頂端的物件原
型,也可以找到 getName 的方法的方法喔!
那麼接下來我們就試著在陣列中使用這個 getName 的方法。
首先我們先對 b 這個陣列加入了 name 的屬性,之後我們預期用 getName 應該可以抓到我們剛剛設定的 name 的內容
果不其然成功了,但陣列上並沒有 getName 的方法,所以透過向上查找的方式,在物件的原型上找到了 getName 的方法並且
使用它。