JavaScript 這個語言有一個特性,所以資料其實都是以『物件』方式做建立,因此也可以說 JavaScript 所有資料都有原型,而到底什麼是原型?
單純用文字比喻的話,原型其實像是藍圖,我們可以在原型設定各種屬性、方法,用來幫助我們建立實體資料,而實體資料則會帶者原型上的屬性以及方法,以下面圖片為例:
上述圖片只是幫助思考『原型的概念』而已,實際程式碼來說的話,我們會使用函示建構式、ES6 的 Class 來當作衣服的原型,而實體資料則是:
const blackT = {
color:'black',
material:'棉',
size:'L'
}
const darkBlueT = {
color:'darkBlue ',
material:'聚酯纖維',
size:'XL'
}
這章節還不會講到如何建立原型,不過我們先將 blackT
物件丟到 console 並且再次呼叫,會顯示剛剛建立的 blackT
物件,不過物件的屬性底下還會有 [[Prototype]]
的東西,而這個 [[Prototype]]
其實就是今天介紹的原型。
值得一提的是原型本身還可以再繼承原型,因此這樣一段一段,因此又被稱做原型鍊,繼續使用衣服圖做範例:
P.S 過去文章會寫做 __proto__
而不是 [[Prototype]]
,這是因為 JavaScript 這語言本來就有對每筆資料設置 [[prototype]]
,ES5 之前並沒有標準方法來查詢這個隱藏屬性,因此瀏覽器使用 __proto__
這個方法來達成相關需求。
現在因為瀏覽器更新,所以 __proto__
改為正式的 [[Prototype]]
不過實際上還是可以使用 __proto__
等等相關方法,以上方 blackT
為例,使用 console.log(blackT.__proto__)
仍可以查到他的相關原型。
以下是原型的特性:
這邊直接使用程式碼舉例比較好懂
const array = [1,2,3]
console.log(array)
這邊的 array 就是個實體,點開 console 上的 array
裡面的 [[Prototype]]
就是原型,這點上面有提到。
而 array 中的 [[Prototype]]
有著許多我們常用的 陣列方法,比如 forEach()
、 filter()
,這些方法正是透過原型繼承來的方法,這也提到一個重點:『若實體要取用原型中的方法,便是使用 .
這個運算子來使用』,這個重點也和上面提到的 『一樣具有物件的特性』是相同的。
眼尖的朋友就會發現在 forEach
、 filter
這些方法底下,還有一層 [[Prototype]]
,這正是上面提到的,原型上還有原型,同時也是『原型向上查找的特性』,同時不管是陣列、還是函示在最上層的原型都會是物件。
接下來可以驗證一下「原型的方法與屬性是共用的」這一點
const array = [1,2,3]
array.__proto__.dobuleNum= function() {
return this.map(function(item) {
return item*2})
}
console.log(array.dobuleNum()) //[2, 4, 6]
範例中使用 __proto__
來新增 dobuleNum
這個方法,而 dobuleNum
就是將陣列中的值 *2 在回傳。透過 console.log(array.dobuleNum())
也會確實看到回傳 [2,4,6]
的值。
接者我們在新增一個陣列變數來試試看上面觀念是否正確。
const newArray = [55,66,77]
console.log(newArray .dobuleNum()) //[110, 132, 154]
最後這邊要提醒一下,實做中是不建議使用 __proto__
來製作原型方法,主要是這方法會污染所有原型,並讓原型來源難以查找、確認,比較實際的方法是使用原型的建構式,這寫是下一章節的主題。