使用建構式自定義原型

Vicky
宅宅薇琪 [前端學習筆記]
7 min readFeb 23, 2021
放上程式碼中形容的狗狗

想像有一份狗狗形象藍圖 (即為建構函式),透過 new 這個運算子來產生實體狗。實體狗擁有自己的屬性(ex: 毛髮顏色、體型、特性),他們的共通點是會汪汪叫,把這共通點放在狗的建構函式原型上,則實體狗都可共享此方法一起汪汪叫。

如何定義自己的原型

  1. 使用建構式的方式產生物件,並且會繼承同一個原型。
  2. 用建構函式,搭配 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 原型

  1. 共用的屬性或方法,不用每次都幫實體建立一份,提出來放到 prototype 即可。
  2. 將 bark 這個共用的方法放到 Dog.prototype,暫且稱它為 Dog 的原型。
  3. JavaScript 的物件能夠「繼承」其 prototype 的屬性或方法。

總結

  1. 先建立建構函式 (普通的 function) → 透過這個 function 來建立物件
  2. 透過 new 運算子產生新的物件實體 (instance)
  3. 新產生的物件會把狗函式做為原型使用,也會把 this 套用在建構函式上
  4. 建構函式 (狗原型) 是共用的,有共用的屬性和方法
  5. 當新物件使用的方法很多時,會消耗很多記憶體
  6. 原型優勢 : 透過原型的方式,只要一個記憶體就可以產生大量的物件

重點

  1. prototype 是建構函式(constructor) 特有的原型屬性
  2. _ proto _ 物件上連結原型的屬性,並非正式的屬性
  3. 若要從原型新增方法,最好從建構函式裡的 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() 會在瀏覽器控制臺打印信息

--

--