點燈坊

失くすものさえない今が強くなるチャンスよ

深入探討 Class 與 Prototype Chain

Sam Xiao's Avatar 2021-01-03

ECMAScript 2015 雖然支援了 class 語法,但本質上仍是使用 Prototype 實作,本文深入探討其背後黑魔法。

Version

ECMAScript 2015

Constructor Function

let Book = function(title, price) {
  this.title = title
  this.price = price
}

Book.prototype.showDescription = function() {
  return `${this.title} / ${this.price}`
}

new Book('FP in JavaScript', 100)

ES5 雖然支援 new,卻沒有支援 class,而是搭配 constructor function。

第 1 行

let Book = function(title, price) {
  this.title = title
  this.price = price
}

Book() 傳入 titleprice,只負責建立 object 的 property。

其中 this 為稍後 new 所建立的 Object,因此可以直接使用 this.title 新增 property。

Constructor function 習慣使用 PascalCase,有別於一般 function 使用 camelCase

第 6 行

Book.prototype.showDescription = function() {
  return `${this.title} / ${this.price}`
}

Constructor function 本質還是 function,而 function 自帶 prototype property 為 {},因此直接對 {} 動態加上 showDescription() method。

prototype 為 function 專屬 property,就真的只是 property 而已,並不代表 function 的 Prototype 是 {},function 的 prototype 是由 __proto__ 指向 Function.prototype

此外,Book.prototype.constructor 會指向 Book(),所以將來 book.constructor 也會指向 Book(),稍後 book.__proto__ 會使用 book.constructor 找到其 Prototype Object。

第 10 行

let book = new Book('FP in JavaScript', 100)

當 function 搭配 new 時,就搖身一變成為 constructor function,this 為所建立的 Object。

prototype000

可發現 showDescription 不屬於 book Object,而是在其 Prototype Object,因此在 __proto__ 下。

我在學習 Prototype 時,最大卡關是觀念上明明應該對 Object 的 Prototype Objecy 新增 method,為什麼到最後是對 constructor function 的 prototype property 新增 method 呢 ?

當執行 showDescription() 時,會發現 book Object 沒有 showDescription(),此時會由 __proto__ 往其 Prototype Object 尋找 showDescription().。

由於 Book.prototype.constructor() 指向 Book(),所以 book.constructor() 亦指向 Book()__proto__ 可藉由 book.constructor property 找到 constructor function,再又其 prototype property 找到 Prototype Object,如此就可藉由 Prototype Chain 找到 showDescription()

可見 Book.prototype.constructor = Book__proto__ 能找到 Prototype Object 的線索。

prototype005

由上圖可發現 Book()prototype property 與 book Object 的 __proto__ property 都指向相同的 Book.prototype,也因此我們可藉由動態新增 Book.prototype 的 method 成為 book__proto__ 的 method,其關鍵就是 book Object 的 constructor property 指向 constructor function,而 constructor function 的 prototype property 指向 Book.prototype

Class

class Book {
  constructor(title, price) {
    this.title = title
    this.price = price
  }
  
  showDescription() {
    return `${this.title} / ${this.price}`
  }
}

new Book('FP in JavaScript', 100)

ES6 支援 class 後,語法又更簡單了,連 prototype 字眼完全沒看見。

prototype003

但可發現 showDescription 不屬於 book Object,而是在其 Prototype Object,因此在 __proto__ 下。

這也再次證明 class 只是 syntatic sugar,ECMAScript 依然是 Prototype-based,而非 Class-based。

Conclusion

  • __proto__ 只所以能找到其 Prototype Object,關鍵在 constructor property 指向 constructor function,再由 constructor function 的 prototype property 找到 Prototype Object,這就是 Prototype Chain 的黑魔法
  • 隨著時代的進步, ECMAScript 寫法不斷精簡,只是黑魔法越來越大,但其 Prototype-based 本質不變
  • ES6 的 class 寫法雖然完全看不到 Prototype,但本質仍是將 method 放在 Prototype Object,是道地的 syntatic sugar

Reference

MDN, Object.prototype.constructor
MDN, Object prototypes
MDN, Object.create()