JavaScript Object Oriented Programming: Prototypal Inheritance
31 Jan 2016對大多數的語言來說,它們擁有「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 經由兩個步驟被執行:
- Step 1:Interpreter 在 rabbit 物件中尋找
eat()
method,但沒有找到,因此循著rabbit.__proto__
往父物件 animal 尋找,並且找到了。 - Step 2:此時的
eat()
method 中的 this 是指 rabbit 物件。
我們得到以下結論:
- 物件呼叫其父物件的 method,其中的 this 會被設定給這個物件。這就是繼承的意思。
- 對於物件來說,
__proto__
被稱為 prototype(原型)。因此,animal 是 rabbit 的 prototype。
Object.create
, Object.getPrototypeOf
__proto__
並非標準的屬性,只有 Chrome 和 Firefox 支援,其它瀏覽器則是隱藏此屬性於內部。所有目前流行的瀏覽器(除了 Opera,IE 則是版本 9 以上),對此屬性支援兩個標準 method - Object.create
和 Object.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);
解說如下:
- 建立新函式 F,新函式 F 由於沒有被設定任何值,因此 F 會建立一個空的物件。
- F.prototype 被設定值為 proto。
- 回傳由 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
- 使用屬性
__proto__
來實作繼承- 如果在物件中找不到此屬性,則會沿著
__proto__
到父物件尋找此屬性的值。 - this 的值會被設定給物件,而非 prototype。
- 屬性設定:
obj.prop = val
;屬性刪除:delete obj.prop
。
- 如果在物件中找不到此屬性,則會沿著
- 管理
__proto__
- Firefox / Chrome 可以直接存取
__proto__
,而大多數現行的瀏覽器只提供讀取(利用Object.getPrototypeOf(obj)
)而沒有修改的權限。 - 給予特定 prototype 的空物件可使用
Object.create(proto)
來達到繼承的目的,或使用自行實作的 function。
- Firefox / Chrome 可以直接存取
- 其它 method
for..in loop
可列出所有此 object 自己和 prototype 的屬性。obj.hasOwnProperty(prop)
在屬性屬於此物件本身(而非屬於 prototype)時會回傳 true。
參考資料
這篇文章的原始位置在這裡-JavaScript Object Oriented Programming - Prototypal Inheritance
由於部落格搬遷至此,因此在這裡放了一份,以便閱讀;部份文章片段也做了些許修改,以期提供更好的內容。