Sean's Blog

An image showing avatar

Hi, I'm Sean

這裡記錄我學習網站開發的筆記
歡迎交流 (ゝ∀・)b

LinkedInGitHub

Understand JavaScript #23 使用 Object.create 建立多層繼承

本文主要內容為探討「Object.create」的相關知識以及搭配使用的 Polyfill。

純粹的原型繼承 - Object.create

  • JavaScript 裡面另外一種建立物件的方法,沒有模仿別的程式語言
  • 現在的瀏覽器幾乎都有內建
  • 建立一個物件當作基本物件,然後在這個物件上建立新物件

使用範例:我們建立一個物件 person 作為基本物件,透過 Object.create 將它作為原型使用,在這個物件上面建立新的物件 damao

1var person = {
2  firstname: 'Default',
3  lastname: 'Default',
4  greet: function () {
5    return 'Hi ' + this.firstname;
6  },
7};
8
9console.log(person.greet()); // Hi Default
10
11var damao = Object.create(person); // 從傳入的物件上建立物件
12console.log(damao); // 是一個空物件,它的原型是 person 物件
13console.log(damao.greet()); // Hi Default

注意:如果沒有用 this,執行時會到全域執行環境中找 firstname,因為全域中只有 person,而 person 是物件所以不會建立執行環境,最後結果就會找不到 firstname

⭐️ Object.create 建立出來的是一個空物件!

使用 Object.create 後,可以再建立新物件的屬性和方法去覆蓋原型給的預設值。執行時,原型鏈找到新物件就會停止,不會繼續往下找。

1damao.firstname = 'Damao';
2damao.lastname = 'Huang';
3console.log(damao.greet()); // Hi Damao
4console.log(damao); // {firstname: "Damao", lastname: "Huang"}

範例:建立多層繼承

目標:Object > Animal > Mamegoma > Damao

1function Animal(species) {
2  this.kingdom = '動物界';
3  this.species = species || '海豹族';
4}
5Animal.prototype.drink = function () {
6  console.log(this.name + '喝水');
7};
8
9function Mamegoma(name, color, size) {
10  Animal.call(this, '海豹族'); // 繼承 Animal 的建構函式
11  this.name = name;
12  this.color = color || '白色';
13  this.size = size || '中';
14}
15Mamegoma.prototype = Object.create(Animal.prototype); // Mamegoma 的原型是繼承 Animal 的原型
16Mamegoma.prototype.constructor = Mamegoma; // 讓建構函式更完整
17Mamegoma.prototype.eat = function () {
18  console.log(this.name + '吃灰塵');
19};
20
21var Piu = new Mamegoma('Piu', '粉紅色', '大');
22console.log(Piu); // 註
23
24Piu.eat(); // Piu吃灰塵 (Mamegoma 原型的方法)
25Piu.drink(); // Piu喝水 (Animal 原型的方法)

⭐️ Piu.__proto__ is a reference to Mamegoma.prototype

註:補上 Mamegoma.prototype = Object.create(Animal.prototype) 才能讓 Mamegoma 繼承到 Animal 原型上面的屬性 kingdom 與 species。

沒加會變成 Mamegoma {name: 'Piu', color: '粉紅色', size: '大'};有加才會是 Mamegoma {kingdom: '動物界', species: '海豹族', name: 'Piu', color: '粉紅色', size: '大'}

dunderproto vs. prototype

  • dunderproto 指向建構它的建構函式的原型物件
  • Function 裡面的 prototype 屬性指向原型物件,同時原型物件也有 constructor 指回建構函式

傻傻分不清楚?以 Mamegoma 與 Piu 的關係為例:

1. 建構函式 Mamegoma()

  • 建構函式 Mamegoma() 的原型屬性 prototype 會指向 Mamegoma.prototype 這個原型物件
  • 在這個原型物件裡有共有的屬性和方法,所有建構函式聲明的實體 (Piu, Shirogoma) 都可以共享這些屬性和方法

建構函式的 __proto__ 屬性則是指向 Function.prototype

2. 原型物件 Mamegoma.prototype

  • 保存著實體共享的屬性和方法,有一個指針 constructor 指回建構函式

原型物件的 __proto__ 屬性是指向它的建構函式的原型物件,即 Animal.prototype

而 Animal.prototype 的 __proto__ 會再指向 Object.prototype,最後 Object.prototype 的 __proto__ 會指向 null

3. 實體 Piu

  • Piu 是 Mamegoma 這個物件的實體,Piu 這個物件有屬性 __proto__,指向建構函式的原型物件 (Mamegoma.prototype),這樣子就可以像上面 1 所說的訪問到原型物件的所有方法了
dunderproto vs. prototype

圖片來源:js 中proto和 prototype 的区别和关系? - doris 的回答 - 知乎

Polyfill

不過 Object.create 算是比較新的寫法,如果要支援比較舊的瀏覽器,可以加上 Polyfill 的程式碼。

  • Polyfill:把引擎(像是舊瀏覽器的 JavaScript 引擎)缺少的功能增加到程式裡面的程式碼
  • Polyfill 會先檢查當下使用的引擎有沒有某個功能,如果沒有就會幫忙加上去,讓舊的瀏覽器有新型瀏覽器的功能

Polyfill 做了什麼事情呢?

首先,一開始 Polyfill 的 if (typeof Object.create !== "function") 就是在檢查瀏覽器有沒有 Object.create 這個東西,如果存在就會直接跳過這整段 if 陳述句。

接下來 temp.__proto__ = proto 就是在設定物件 temp 的原型等於我們傳入的物件 proto。 這就相當於 var temp = Object.create(proto) 的意思,也就是用 proto 作為原型來建立新物件 temp,最後回傳 temp 完成物件建立。

1if (typeof Object.create !== 'function') {
2  Object.create = function (proto, propertiesObject) {
3    if (
4      !(
5        proto === null ||
6        typeof proto === 'object' ||
7        typeof proto === 'function'
8      )
9    ) {
10      throw TypeError('Argument must be an object, or null');
11    }
12    var temp = new Object();
13    temp.__proto__ = proto;
14    if (typeof propertiesObject === 'object')
15      Object.defineProperties(temp, propertiesObject);
16    return temp;
17  };
18}

所以上面這一整段 Polyfill 的意思就是「給一個物件 proto,它會變成新的空物件 temp 的原型」,其實就跟 var temp = Object.create(proto) 的效果是一樣的。

回顧

看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…

  • 更純粹、強大、易懂的原型繼承 - Object.create
  • 使用 Polyfill 補足舊瀏覽器缺少的功能

References