0%

JS核心-(46)-繼承與原型練-原型在哪裡?

前言

介紹原型

原型

在介紹原型之前,我們要先來複習物件的內容

當我們要定義一隻狗的時候,我們會利用物件的資料結構,來對這隻狗進行描述,例如它的顏色、體型大小,以及可以吠叫的方

法。主要的原因就是因為 javascript 基本上都是由物件的方法去構成資料,而原型也是一樣的概念!

前一篇文章,java 類別繼承中我們有提到,如果我們要定義一個像是這樣的內容,我們會使用 class 來定義它,但是 javascript

中都是使用物件來定義,所以我們在定義原型的時候也是使用物件的方式來定義。

如果狗要透過這個原型來建立實體的話,也是透過繼承的方式繼承了原型的屬性跟方法,因此這就是兩個物件的概念。

原型的狗就會有毛色、體型大小,以及可以吠叫的方法。而在實體的狗呢這些屬性就會有可以自定義的空間。

那麼吠叫的部分就可以繼承原本原型的方法。

實際上,當我們運行 javascript 的時候,新增一個物件實體的時候就會有屬於該實體的屬性,如左上圖的毛色、體型、吠叫

那麼原型的部分(如右上圖)一樣也會有它自己的屬性,那這樣的結構上依然是屬於兩個物件。




另外,除了實體可以繼承一個原型之外,原型也可以繼承另外一個原型喔!

在另外一個原型的部分也可以擁有自己的屬性以及方法,那麼這樣的繼承關係可以一段一段的向上繼承,這樣的繼承狀態我們又

稱為原型鏈。在這個實體我們要取用其中的屬性的時候,會使用 . 點運算子的方法進行取用,例如 obj.Prop1obj.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

很明顯就成功的印出我們想要的內容。


原型的層級問題

剛剛說到,打開 ab__proto__ 的屬性能夠找到陣列的原型。

那麼繼續往下找又可以找到另一個 __proto__ 的屬性,再把它打開以後會看到的是物件的原型,因此這個陣列的原型其實是繼

承了物件的原型,而繼續往下看就可以看到物件的一些方法,但到最底就沒有 __proto__ 的屬性,代表說這個物件的原型就是

原型鏈的最頂層了 !


我們再來看一些 code

我們新增了一個新的物件,並且對該物件的物件原型上掛載 getName 的方法,而之後打開 ab 的原型鏈最頂端的物件原

型,也可以找到 getName 的方法的方法喔!

那麼接下來我們就試著在陣列中使用這個 getName 的方法。

首先我們先對 b 這個陣列加入了 name 的屬性,之後我們預期用 getName 應該可以抓到我們剛剛設定的 name 的內容

果不其然成功了,但陣列上並沒有 getName 的方法,所以透過向上查找的方式,在物件的原型上找到了 getName 的方法並且

使用它。