Try   HackMD

JS 核心 使用建構式自定義原型

如何定義自己的原型

使用建構式的方式產生物件,並且會繼承同一個原型。
用建構函式,搭配 new 關鍵字的使用,以一個固定的藍圖建立許多結構相同的物件

先用一個函式(建構式)來做為他們的藍圖

  • 藍圖無法變成實體 (instance)
  • 若要將藍圖變成實體要透過 new 這個運算子
  • 這兩隻狗是共用一個藍圖產生的 : (指 Dog 建構函式)
    兩隻狗有各自的屬性,有一個共用的方法 bark (吠叫),在吼叫時會帶上自己的名字

new 運算子

  • 建立一個新的物件,並且連結回原本的建構物件 (指 Dog 函式)
  • 將物件的 _ proto _ 指向建構子的 prototype,形成原型串鏈
  • 會把新產生物件的 this 綁定在函式之上 (在建構函式內使用的 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 這個建構函式所產生出來的 var Bibi = new Dog('比比', '棕色', '小'); // 每次產生的物件也都會綁定在函式的 this 上 console.log(Bibi); // Dog{name: "比比", color: "棕色", size: "小"} (如下左圖) var Pupu = new Dog('噗噗', '白', '大'); // 透過此藍圖再來產生一隻狗 console.dir(Dog); // 來看一下 Dog 的函式內容 (如下右圖)

(第 10 行) Bibi 就是透過「Dog 建構函式」產生的,Dog 就是 Bibi 的原型 (狗的藍圖)
(第 12 行) 目前在此原型上並沒有看到狗的原型方法 (如下右圖)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

透過 prototype 的方式來新增原型方法

只要把 bark 這個 function 指定在 Dog.prototype 上面,所有 Dog 的 instance 都可以共享這個方法。

(承上程式碼) 為狗新增吼叫的能力

  • 建構式(constructor)函式本身就是一個物件,建構式函式物件裡有一個特有屬性 prototype
  • 透過 prototype 所新增的屬性就會作為原型的方法
  • Bibi 物件的 prototype 是 Dog.prototype;換句話說,Bibi 繼承自 Dog.prototype
// (Dog.prototype 即為 Dog 的原型) Dog.prototype.bark = function (){ // 在 Dog 函式上使用 prototype 來新增原型的方法 console.log(this.name + ' 吠叫'); // 使用 this 來綁定原本函式的名稱 } console.log(Bibi, Pupu); // (結果如下圖) Bibi.bark(); // 比比 吠叫 (有吼叫的能力) Pupu.bark(); // 噗噗 吠叫 (有吼叫的能力) console.log(Dog.prototype === Bibi.__proto__) // true

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

再複習一下 JavaScript 的基礎 :

你有一個叫做 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() 會在瀏覽器控制臺打印信息

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
學習回顧

  • 使用建構式的方式產生物件,並且會繼承同一個原型。
  • 建構式(constructor)函式本身就是一個物件,他有一個特有屬性 prototype
    • 透過 prototype 所新增的屬性就會作為原型的方法

new 運算子

  • 建立一個新的物件,並且連結回原本的建構物件 (指 DOG 函式)
  • 將物件的 _ proto _ 指向建構子的 prototype,形成原型串鏈
  • 在建構函式內使用的 this 就會綁定在新物件上
    • 將建構子的 this 指向 new 出來的新物件
    • 回傳這個物件
  • 透過 「new 運算子」搭配「Dog 建構函式」來產生新的物件「狗的實體」,新的物件和原本物件沒有關聯性

重點

  • 若要從原型新增方法,最好從建構函式裡的 prototype 原型作調整; 若從任意新增物件調整原型的話,維護上有很大的問題 (主要原因是會讓原型難以被追朔)
  • 建構函式(函式實體) - prototype - function 原型 - object 原型
  • 物件實體 - _ proto _ (等同 prototype)- object 原型

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
相關參考文件