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 14
注意:如果沒有用
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"} 5
範例:建立多層繼承
目標: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 原型的方法) 26
⭐️ 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 所說的訪問到原型物件的所有方法了
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} 19
所以上面這一整段 Polyfill 的意思就是「給一個物件 proto
,它會變成新的空物件 temp
的原型」,其實就跟 var temp = Object.create(proto)
的效果是一樣的。
回顧
看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…
- 更純粹、強大、易懂的原型繼承 - Object.create
- 使用 Polyfill 補足舊瀏覽器缺少的功能