2016年6月29日 星期三

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


在先前的筆記中,我們談到可以使用function constructor來建立物件,同時我們也提到 function constructor 這種概念是用來模仿Java語言而產生的(參考:[筆記] 談談JavaScript中的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;
    }
}


注意一下第六行的地方,這裡我們用的是 this.firstname 而不是直接用 firstname。當我使用 this 這個關鍵字的時候,它會指稱到的是 Person 這個物件,但是如果沒有使用this的話,會變成在 getFullName 這個 execution context 中找 firstname 和 lastname 這兩個變數,如果找不到的話就會往最外層的 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());


如此,對於firstname和lastname來說,在該物件就已經有這兩個屬性,因此它不會在往該物件的原型去尋找,而對 getFullName 來說,因為在 john 這個物件裡沒有這個方法,於是就會到 prototype 裡面去找,最後會回傳"John Doe"給我。

透過 Object.create 這種方法,是最單純使用 prototypal inheritance 的方式。如果你想要定一個物件的原型,就先建立一個 A 物件當做其他物件的基礎,然後再建立另一個空物件 B,指稱 A 物件當做它的原型,在透過為 B 物件賦予屬性或方法。
使用Object.create( )的方式運用繼承和原型的概念能夠非常簡便的建立物件。

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


雖然大部分的新瀏覽器都支援這樣的做法,但如果某些瀏覽器的版本真的舊到無法使用Object.create的話,可以怎麼辦呢?

這時候我們會寫一些程式來填補某些瀏覽器不支援的情況,我們把這些程式稱做polyfill

如果我們不確定瀏覽器是不是有支援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();
    };
}


讓我們來了解一下這段程式碼。首先,第二行的的 if(!Object.create) 的意思就是在檢驗瀏覽器中是否有內建 Object.create 的函式,如果沒有的話就會回傳 undefined ,在前面加一個 ! 這個邏輯運算子,則會把 undefined 轉換成布林值,所以這段程式碼轉成中文的話,意思就是「如果Object.create不存在的話,則執行...」,在這裡也就是會執行第3-10行當中的內容,會建立Object.create 這個 function。

在第4-6行則是說明如果所代的參數超過一個的話,會在console.log中回傳錯誤訊息(對於throw, new Error的用法可參考下面的延伸閱讀。)

在第7-9行則是會去執行 Object.create 這個函式原本會執行的內容,也就是先建立一個空的函式,然後把原本基礎物件的內容放入 F.prototype 中,最後再用函式建構式(function constructors)的方式,回傳 new F( ) ,如此,就能夠達到 Object.create 原本的效果。

延伸閱讀:
MDN - throw
MDN - error

程式範例


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





→回到此系列文章目錄


Share:

0 意見:

張貼留言