JavaScript Object Oriented Programming: Prototypal Inheritance

JavaScript

對大多數的語言來說,它們擁有「Class」和「Object」,而 Class 繼承其它的 Class。對於 JavaScript 來說,繼承是使用 prototype 來實作的,意即沒有 Class,而是由物件繼承其它的物件來達成繼承。

Inheritance, the proto

var animal = { eats: true },
  rabbit = { jumps: true };

rabbit.__proto__ = animal; // inherit
console.log(rabbit.eats); // true

當存取 rabbit 的 property「eats」時,由於無法在 rabbit 中找到,因此往父物件尋找,終於在 animal 找到,並且值是 true。如果存取 rabbit 的 property 可在自身找到,就不需要往父物件尋找。 例如,假設存取 rabbit 的 property「jumps」,由於可在 rabbit 找到,並且值是 true 因此就不往父物件找了。「 __proto__」是一個連接子物件和父物件的 link,讓 interpreter 在子物件找不到屬性時,可以依循這條 link 往父物件繼續尋找。由以上的範例可知,我們可以在 animal 放置一個 method,然後子物件 rabbit 也可以使用這個 method。

var animal = {
    eat: function() {
      console.log("I'm full");
      this.full = true;
    },
  },
  rabbit = {
    jump: function() {
      //do something
    },
  };

rabbit.__proto__ = animal;
rabbit.eat(); // I'm full

rabbit.eat() 這個 method 經由兩個步驟被執行:

我們得到以下結論:

Object.create, Object.getPrototypeOf

__proto__ 並非標準的屬性,只有 Chrome 和 Firefox 支援,其它瀏覽器則是隱藏此屬性於內部。所有目前流行的瀏覽器(除了 Opera,IE 則是版本 9 以上),對此屬性支援兩個標準 method - Object.createObject.getPrototypeOf

Object.create(proto[, props])

使用 animal __proto__ 建立一個空物件 rabbit,而且可在 rabbit 中建立自己的屬性 jumps。

var animal = { eats: true },
  rabbit = Object.create(animal);

rabbit.jumps = true;

console.log(rabbit.eats); // true
console.log(rabbit.jumps); // true

Object.getPrototypeOf(obj)

回傳 obj.__proto__ 的值。這個 method 是在標準規格裡面的,所以不支援 __proto__ 的瀏覽器仍可使用此 method。

var animal = { eats: true },
  rabbit = Object.create(animal);

console.log(Object.getPrototypeOf(rabbit) === animal); // true

因此,幾乎所有主要瀏覽器都允許「讀取」__proto__的值,但不允許修改它。

The prototype

有一個較好且跨瀏覽器的方法來設定 __proto__,而這個方法需要建構式的幫忙來設定 __proto__ 的值。

var animal = { eats: true };

function Rabbit(name) {
  this.name = name;
}

// set __proto__ = animal for all objects created by new Rabbit
Rabbit.prototype = animal;

var rabbit = new Rabbit('John');

console.log(rabbit.eats); // true

Crossbrowser Object.create(proto)

Object.create(proto)允許直接繼承給予的物件,可以使用我們自己撰寫的 method - inherit 來模仿它而達到一樣的功能。因此使用 inherit(animal) 的效果等同於使用 Object.create(animal) - 建立一個新的空物件,且 object.__proto__ = animal

function inherit(proto) {
  function F() {} // (1)
  F.prototype = proto; // (2)
  return new F(); // (3)
}

var animal = { eats: true },
  rabbit = inherit(animal);

console.log(rabbit.eats);

解說如下:

  1. 建立新函式 F,新函式 F 由於沒有被設定任何值,因此 F 會建立一個空的物件。
  2. F.prototype 被設定值為 proto。
  3. 回傳由 F 建立的空物件。空物件的 prototype 為 proto,意即 object.__proto__ = proto

hasOwnProperty

hasOwnProperty method 允許檢查其屬性是否屬於某個物件或其 prototype。

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = { eats: true };

var rabbit = new Rabbit('John');

console.log(rabbit.hasOwnProperty('eats')); // false, in prototype
console.log(rabbit.hasOwnProperty('name')); // true, in object

Looping with / without inherited properties

將所有的屬性利用 for loop 顯示出來。

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = { eats: true };

var rabbit = new Rabbit('John');

for (var p in rabbit) {
  console.log(p + ' = ' + rabbit[p]); // outputs both "name" and "eats"
}

利用 method hasOwnProperty 來篩選只有物件本身擁有的屬性,而非在 prototype 中。

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = { eats: true };

var rabbit = new Rabbit('John');

for (var p in rabbit) {
  if (!rabbit.hasOwnProperty(p)) continue; // filter out "eats"
  console.log(p + ' = ' + rabbit[p]); // outputs only "name"
}

Summary

參考資料


這篇文章的原始位置在這裡-JavaScript Object Oriented Programming - Prototypal Inheritance

由於部落格搬遷至此,因此在這裡放了一份,以便閱讀;部份文章片段也做了些許修改,以期提供更好的內容。

javascript prototype javascript