[JS奇怪的世界]No.56 Object.create與純粹的原型繼承

前言

前面我們已經瞭解使用函數建構子建立物件的方式,我們也看過函數建構子的出現原因是為了模仿其他不能實作原型繼承的程式語言,所以這邊會有點尷尬,在其他的程式語言是使用 class 來定義物件該做什麼,然後再用 new 來建立物件,而這也就是函數建構子想模仿的事情。

而有些人在開發時都會比較專注於原型繼承,而非古典繼承。

另外 JavaScript 還有一個建立物件的方式,在較新的瀏覽器都會內建就是 Object.create,所以來看一下範例順便想想原型繼承吧。

Object.create

首先我們用物件實體語法建立一個新物件

1
2
3
4
5
6
7
var person = {
firstname: 'Default',
lastname: 'Default',
greet: function() {
return 'Hi' + this.firstname;
}
}

首先這邊要注意一件事情,物件不會建立新的執行環境,所當不寫上 this 時,他就會直接去找全域環境。

這邊要注意一件事情,接下來的寫法都是在做同一件事情 [ 建立物件 ],而這是使用了現代瀏覽器內建的方法來建立新物件

1
2
3
4
5
6
7
8
9
10
var person = {
firstname: 'Default',
lastname: 'Default',
greet: function() {
return 'Hi' + this.firstname;
}
}

var john = Object.create(person); // 傳入person
console.log(john);

這時候發生一件奇妙的事情

我一度以為是瀏覽器顯示問題,因為在課堂上的範例是這樣子

所以我試著使用 chrome 版本看看。

結果也是一樣,所以我試著用另一種方式去獲取物件裡面的東西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var person = {
firstname: 'Default',
lastname: 'Default',
greet: function() {
return 'Hi' + this.firstname;
}
}

var john = Object.create(person); // 傳入person
console.log(john);
console.log('----');
console.log(john.firstname);
console.log(john.lastname);
console.log(john.greet());

可以發現是可以獲取到物件內的東西,但是為什麼會是空物件呢?在 PJCHENder 文章中有提到

透過 Object.create() 可以建立一個空物件,同時可以將你帶入 Object.create() 的參數內容變成該物件的原型。

所以原因是出在使用 Object.create() 這個語法是在建立一個空的物件,然後將傳入的參數變成該物件的原型。

而使用這個方式我們可以彈性的複寫 or 調整屬性及方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var person = {
firstname: 'Default',
lastname: 'Default',
greet: function() {
return 'Hi' + this.firstname;
}
}

var john = Object.create(person); // 傳入person
console.log(john);
console.log(john.firstname);
john.firstname = 'John';
console.log('----');
console.log(john.firstname);

而這個好處我們可以保留原型並使用原型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var person = {
firstname: 'Default',
lastname: 'Default',
greet: function() {
return 'Hi' + this.firstname;
}
}

var john = Object.create(person); // 傳入person
console.log(john);
console.log(john.firstname);
john.firstname = 'John';
console.log('----');
console.log(john.firstname);
console.log(john.greet());

雖然 john 的物件中已經有 firstname,但原型依然保有 Default。

而這也就是本章節在講的純粹的原型繼承。

所以我們可以透過 Object.create() 來覆寫、隱藏屬性及方法,我們只要對 person 新增屬性及方法,然後透過使用 Object.create() 讓它也擁有這些屬性及方法,而這就是純粹的原型繼承。

專案過舊不支援 Object.create()

但是這邊要注意一件事情 Object.create() 僅有在較新的瀏覽器上才會支援,所以若專案需要在比較舊的瀏覽器或環境,那就不能用這種方式,而是改使用 polyfill

polyfill 是將 JavaScript 引擎缺少功能,增加功能到程式中,所以無論你在處理的程式如何,可以用這個方法去檢查引擎有沒有這個功能並增加功能。

課程這個說明實在很攏統,所以稍微參考一下其他文獻說明。

圖源 PJCHENder

參考了一下 PJCHENder 的說明後就比較清楚了,所以讓我們從範例去理解。

首先今天的狀況是瀏覽器過舊沒有支援 Object.create(),那就可以這樣做。

1
2
3
4
5
6
7
8
9
10
if (!Object.create()) {
Object.create = function (o) {
if (arguments.length > 1) {
throw new Error('Object.create implementation only accepts the first parameter.');
}
function F() {}
F.prototype = o;
return new F();
}
}

來解釋一下程式碼,首先我們使用 if (!Object.create()) 判斷瀏覽器是否有支援 Object.create(),若沒有就會回傳一個 undefined,在前面加入一個 ! 來反轉運算式結果,將 undefined 轉換成布林值,而 undefined 的布林值為 false,所以這一段的意思就是,當「Object.create()」不存在的時候就執行裡面的程式碼。

`undefined` 的布林值為 `false`

接下來 if (arguments.length > 1) 意思是當傳入的參數超過一個以上就會回傳錯誤訊息「Object.create implementation only accepts the first parameter.」 > Object.create() 只接受一個參數。

最後一行就是 function F() {} 會建立一個空的物件,然後將傳入的參數內容放入到 F.prototype 中,最後再用函數建構子的方式回傳 return new F();,這樣就可以達到原本的 Object.create() 效果。

圖源

JavaScript 全攻略:克服 JS 奇怪的部分

參考文獻

PJCHENder

0%