iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 29
0

Day 29 Object.create 與純粹的原型繼承

我們已經知道透過 new 運算子呼叫函數建構子可以建立物件,
但這種方式是為了模仿其他程式語言(Java、C#)透過 Class 與 new 關鍵字建立物件的方式,
其他程式語言(Java、C#)透過 Class 定義物件,

JavaScript 則是透過 new 運算子呼叫函數建構子的prototype屬性來定義物件的原型屬性,來達到類似的效果,

在新版本的瀏覽器中已經內建新的建立物件的方法了,
透過 Object.create 方法可以讓我們建立物件,
我們先建立一個物件,
程式碼如下:

var person = {
  firstname: 'Default',
  lastname: 'Default',
  greet: function() {
    return 'Hi ' + firstname;
  }
};

在透過Object.create 來建立一個新物件,
程式碼如下:

var person = {
  firstname: 'Default',
  lastname: 'Default',
  greet: function() {
    return 'Hi ' + firstname;
  }
};

var jimmy = Object.create(person);
console.log(jimmy);

在 Console 中的結果如下圖:

會發現 Object.create方法回傳了一個空物件給我們,
這時點一下空物件左邊的箭頭來察看空物件的原型屬性,
在 Console 中的結果如下圖:

會發現空物件的原型屬性裡有 person 物件的屬性與方法,
這是因為我們把 person 物件當作參數傳入Object.create方法,

這時候我在新建立的物件取用 greet 方法,
程式碼如下:

var person = {
  firstname: 'Default',
  lastname: 'Default',
  greet: function() {
    return 'Hi ' + firstname;
  }
};

var jimmy = Object.create(person);
console.log(jimmy.greet());

我能夠正確取用 greet 方法嗎?
在 Console 中的結果如下圖:

這是因為我在 person 物件的 greet 方法中在回傳屬性 firstname 時,沒有加上this,
我們修改一下程式碼,
程式碼如下:

var person = {
  firstname: 'Default',
  lastname: 'Default',
  greet: function() {
    return 'Hi ' + this.firstname;
  }
};

var jimmy = Object.create(person);
console.log(jimmy.greet());

在 Console 中的結果如下圖:

現在有正確的輸出結果了,這是因為 person 物件的 greet 方法如果在回傳 firstname 屬性時,
沒有透過 this 來取用,這會只在 greet 方法中找尋 firstname 變數,
如果找不到會往全域物件裡面找,
因為在全域物件中我們沒有定義 firstname 變數,所以回傳 undefined
person 物件被執行時沒有自己的執行環境,
但如果我在 greet 方法回傳 firstname 時透過 this 來取用 firstname 就可以取用到 person 物件中的 firstname 屬性了,因為物件方法中的 this 指向物件本身,

我也可以用透過Object.create方法建立的空物件來改變 person 物件的屬性值,
程式碼如下:

var person = {
  firstname: 'Default',
  lastname: 'Default',
  greet: function() {
    return 'Hi ' + this.firstname + ' ' + this.lastname;
  }
};

var jimmy = Object.create(person);
jimmy.firstname = 'jimmy';
jimmy.lastname = 'Huang';
console.log(jimmy.greet());

透過點(成員取用)運算子再次覆寫屬性就可以讓物件有不同的屬性值,

在 Console 中的結果如下圖:

透過這種方式能夠覆寫、隱藏傳入Object.create方法的物件的屬性或方法,

透過這種方式添加屬性或物件到 person 物件,
其他我同樣用 Object.create方法並把 person 物件當作參數傳入的其他新建立的物件也會有新添加的屬性或方法,

這就是純粹的原型繼承,這很直覺,讓你可以直接存取改變原型屬性內的屬性與方法,

在這個例子裡 ,當我們將 person 物件當作Object.create方法的參數傳入,
person 物件其實就是每個透過Object.create方法建立的物件的原型屬性,

有些舊版的瀏覽器(IE8),沒有支援Object.create方法,
這時就需要透過 polyfill 來添加功能到瀏覽器,
polyfill 可以理解成我們在瀏覽器中額外添加的功能(程式碼),
程式碼如下:

// 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是個方法,所以將匿名函數指派給Object.create
匿名表達式的參數接收一個物件,如果傳入的參數大於一個就報錯,
這是透過執行環境中的 arguments 屬性來得知參數量,
最重要的部份是在這個自建的匿名函數中新增一個空函數 F,
並將傳入的物件指派給空函數 F 的prototype
最後透過 new 運算子呼叫函數 F ,這會回一個空物件,
空物件的原型屬性是函數 F 的prototype
由於函數 F 的 prototype屬性被設成傳入的物件,
所以回傳的空物件的原型屬性就是傳入給Object.create方法當作參數的物件,

這就是 polyfill 想要做的事情,
讓舊版本瀏覽器也可以使用Object.create

所以把物件當作 Object.create方法的參數傳入,
最後回傳的新物件的原型屬性會是你傳入 Object.create方法當作參數的物件,

只要修改新物件的屬性或方法,就能覆寫、隱藏及添加屬性或方法給傳入 Object.create方法當作參數的物件,

傳入 Object.create方法當作參數的物件也就是新物件的原型屬性,

現在大部分的瀏覽器都已經支援Object.create方法了,
如果你使用一些新的 JavaScript 語法(ES 7 8)在舊版瀏覽器無法正確執行,
也可以找找看有無那個語法的 polyfill,
像是 MDN 就有提供不少現成的 polyfill,讓我們方便使用,


上一篇
Day 28 陣列與 for in
下一篇
Day 30 總結
系列文
教練我想學 JavaScript 30

尚未有邦友留言

立即登入留言