2016年6月29日

[筆記] 談談JavaScript中最單純的原型繼承(prototypal inheritance)─ Object.create

img
在先前的筆記中,我們談到可以使用 function constructor 來建立物件,同時我們也提到 function constructor 這種概念是用來模仿 Java 語言而產生的(參考:[筆記] 談談 JavaScrip t中的 function constructor 和關鍵字 new),在其它的程式語言中,會用 class 這個關鍵字來設定該物件要長什麼樣子,然後透過關鍵字 new 來建立物件。
然而,和其他程式語言不同的地方在於,JavaScript 實際上使用的是 prototypal inheritance 而不是 classic inheritance,所以為了讓 JavaScript 回歸單純的 prototypal inheritance,現在的瀏覽器大部分都支援Object.create() 這種單純的方式來建立物件。
讓我們透過下面的例子,更清楚 pure prototypal inheritance 和 Object.create() 的操作。
首先,我們先使用下面這段程式建立一個原型的物件:
var Person = {
  firstName: 'Default',
  lastName: 'Default',
  getFullName: function () {
    return this.firstName + " " + this.lastName;
  }
}
注意一下 return 的地方,這裡我們用的是 this.firstName 而不是直接用 firstName。當我使用 this 這個關鍵字的時候,它會指稱到的是 Person 這個物件,但是如果沒有使用 this 的話,會變成在 getFullName 這個 execution context 中找 firstNamelastName 這兩個變數,如果找不到的話就會往最外層的全域環境(global environment)去找,最後如果還是找不到,就會出現 "Uncaught ReferenceError: firstName is not defined" 。

Object.create() 的使用

接下來我們說明如何使用 Object.create()
var john = Object.create(Person);
console.log(john);
輸出的結果如下,它是一個空物件,但是它繼承了 Person 這個物件當中的屬性和方法:
透過 Object.create() 可以建立一個空物件,同時可以將你帶入 Object.create() 的參數內容變成該物件的原型。
由於原型鍊(prototype chain),可以在這個物件當中建立相同屬性的內容,在執行的時候它會先找物件中最上層的屬性,於是就不會得到 default 的結果,像是這樣:
var Person = {
  firstName: 'Default',
  lastName: 'Default',
  getFullName: function () {
    return this.firstName + " " + this.lastName;
  }
}

var john = Object.create(Person);

john.firstName = 'John';
john.lastName = 'Doe';
console.log(john.getFullName());
如此,對於 firstNamelastName 來說,在該物件就已經有這兩個屬性,因此它不會在往該物件的原型去尋找,而對 getFullName 來說,因為在 john 這個物件裡沒有這個方法,於是就會到 prototype 裡面去找,最後會回傳 "John Doe"。
透過 Object.create() 這種方法,是最單純使用 prototypal inheritance 的方式。如果你想要定一個物件的原型,就先建立一個 A 物件當做其他物件的基礎,然後再建立另一個空物件 B,指稱 A 物件當做它的原型,在透過為 B 物件賦予屬性或方法。
使用 Object.create() 的方式運用繼承和原型的概念能夠非常簡便的建立物件。

如果瀏覽器不支援 Object.create()

雖然大部分的新瀏覽器都支援這樣的做法,但如果某些瀏覽器的版本真的舊到無法使用 Object.create() 的話,可以怎麼辦呢?
這時候我們會寫一些程式來填補某些瀏覽器不支援的情況,我們把這些程式稱做 polyfill
如果我們不確定瀏覽器是不是有支援 Object.create() 的話,我們可以寫如下的 polyfill
// polyfill for Object.create()

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 轉換成布林值,所以這段程式碼轉成中文的話,意思就是「如果 Object.create 不存在的話,則執行... 」。
Object.create 不存在的時候,接著會建立 Object.create 這個 function。 if(arguments.length > 1) 是說明如果所代入的參數超過一個的話,會在 console.log 中回傳錯誤訊息。
最後會去執行 Object.create 這個函式原本會執行的內容,也就是先建立一個空的函式 F(){ },然後把原本基礎物件的內容放入 F.prototype 中,最後再用函式建構式(function constructors)的方式,回傳 new F( ) ,如此,就能夠達到 Object.create() 原本的效果。

程式範例

//  polyfill
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();
  };
}

var Person = {
  firstName: 'Default',
  lastName: 'Default',
  getFullName: function () {
    return this.firstName + " " + this.lastName;
  }
}

var john = Object.create(Person);

john.firstName = 'John';
john.lastName = 'Doe';
console.log(john.getFullName());

資料來源

0 意見:

張貼留言