使用建構式自定義原型
想像有一份狗狗形象藍圖 (即為建構函式),透過 new 這個運算子來產生實體狗。實體狗擁有自己的屬性(ex: 毛髮顏色、體型、特性),他們的共通點是會汪汪叫,把這共通點放在狗的建構函式原型上,則實體狗都可共享此方法一起汪汪叫。
如何定義自己的原型
- 使用建構式的方式產生物件,並且會繼承同一個原型。
- 用建構函式,搭配 new 關鍵字的使用,以一個固定的藍圖建立許多結構相同的物件。
先用一個函式(建構式)來做為他們的藍圖
- 藍圖無法變成實體 (instance)
- 若要將藍圖變成實體要透過 new 這個運算子
- 這兩隻狗是共用一個藍圖產生的 : (指 Dog 建構函式)
兩隻狗有各自的屬性,有一個共用的方法 bark (吠叫),在吼叫時會帶上自己的名字
new 運算子
- 建立一個新的物件,並且連結回原本的建構物件 (指 Dog 函式)
- 將物件的 _ proto _ 指向建構子的 prototype,形成原型串鏈
- 在建構函式內使用的 this 就會綁定在新物件上,將建構子的 this 指向 new 出來的新物件,並回傳這個物件
Dog 就是一個構造函數,可以用 new 這個關鍵字 new 出一個 instance 來。
透過 「new 運算子」搭配「Dog 建構函式」來產生新的物件「狗的實體」,新的物件和原本物件沒有關聯性。
// constructor 建構函式 (上面兩隻狗的藍圖) (只準備了屬性)
function Dog(name, color, size) {
this.name = name; // 使用 this 將這些屬性綁定在他們自己身上
this.color = color;
this.size = size;
}// 兩隻狗都是由 Dog 這個建構函式所產生出來的
// 每次產生的物件也都會綁定在函式的 this 上
var Bibi = new Dog('比比', '棕色', '小');
console.log(Bibi); // (如下圖)
var Pupu = new Dog('噗噗', '白', '大'); // 透過此藍圖再來產生一隻狗
console.dir(Dog); // 來看一下 Dog 的函式內容 (如下圖)
Bibi 就是透過「Dog 建構函式」產生的,Dog 就是 Bibi 的原型 (狗的藍圖)
目前在此 Dog 原型上並沒有看到狗的任何原型方法 (如下右圖)
透過 prototype 的方式來新增原型方法
只要把 bark 這個 function 指定在 Dog.prototype 上面,所有 Dog 的 instance 都可以共享這個方法。
(承上程式碼) 為狗新增吼叫的能力
- 建構式(constructor)函式本身就是一個物件,建構式函式物件裡有一個特有屬性 prototype
- 透過 prototype 所新增的屬性就會作為原型的方法
- Bibi 物件的 prototype 是 Dog.prototype;換句話說,Bibi 繼承自 Dog.prototype
// (Dog.prototype 即為 Dog 的原型)
// 在 Dog 函式上使用 prototype 來新增原型的方法
Dog.prototype.bark = function (){
console.log(this.name + ' 吠叫'); // 使用 this 來綁定原本函式的名稱
}
console.log(Bibi, Pupu); // (結果如下圖)
Bibi.bark(); // 比比 吠叫 (有吼叫的能力)
Pupu.bark(); // 噗噗 吠叫 (有吼叫的能力)
console.log(Dog.prototype === Bibi.__proto__) // true
再回顧一下剛剛實作的程式碼
你有一個叫做 Dog 的函數,就可以把 Dog 當作建構函式 (constructor),使用 new 來建立出一個 Dog 的實體 (instance),並且可以在 Dog.prototype 上面加上你想讓所有實體 (instance) 共享的屬性或是方法。
Prototype 原型
- 共用的屬性或方法,不用每次都幫實體建立一份,提出來放到 prototype 即可。
- 將 bark 這個共用的方法放到 Dog.prototype,暫且稱它為 Dog 的原型。
- JavaScript 的物件能夠「繼承」其 prototype 的屬性或方法。
總結
- 先建立建構函式 (普通的 function) → 透過這個 function 來建立物件
- 透過 new 運算子產生新的物件實體 (instance)
- 新產生的物件會把狗函式做為原型使用,也會把 this 套用在建構函式上
- 建構函式 (狗原型) 是共用的,有共用的屬性和方法
- 當新物件使用的方法很多時,會消耗很多記憶體
- 原型優勢 : 透過原型的方式,只要一個記憶體就可以產生大量的物件
重點
- prototype 是建構函式(constructor) 特有的原型屬性
- _ proto _ 物件上連結原型的屬性,並非正式的屬性
- 若要從原型新增方法,最好從建構函式裡的 prototype 原型作調整; 若從任意新增物件調整原型的話,維護上有很大的問題 (主要原因是會讓原型難以被追朔)
注意 : 請勿修改原生原型
以下範例來說: 由 Dog 所產生的 Bibi,卻可以改到 Dog 的原型,往後如果發現錯誤,卻無法從 “Dog” 這個建構函式找到。如果在大型專案或是有拆分多個檔案時,這個問題將更難以被發現。
function Dog(name, color, size) {
this.name = name;
this.color = color;
this.size = size;
}
Dog.prototype.bark = function () {
console.log(this.name + '吼叫');
};
// 用建構函式的產生的片段,並透過 __proto__ 覆蓋了原型的內容
var Bibi = new Dog('比比', '棕色', '小');
Bibi.__proto__.bark = function() {
console.log(this.name + '亂叫')
}
// 受影響的物件,無法使用 Dog 追朔到源頭
var Puppy = new Dog('帕比', '棕色', '小');
Puppy.bark(); // 帕比 亂叫
注意 : 不推薦直接去修改不屬於你的 Object
有些人會直接在 Array.prototype 上面加一些函式,讓自己可以更方便地做一些操作,原理也是這樣。可是一般來說,不推薦直接去修改不屬於你的 Object。
Array.prototype.last = function () {
return this[this.length - 1];
};
console.log([1,2,3].last()) // 3
補充
- console.dir() 可以顯示一個對象的所有屬性和方法(詳細打印,利於分析對象)
- console.log() 會在瀏覽器控制臺打印信息