JavaScript 不包含傳統(tǒng)的類繼承模型,而是使用 prototype 原型模型。
雖然這經(jīng)常被當(dāng)作是 JavaScript 的缺點被提及,其實基于原型的繼承模型比傳統(tǒng)的類繼承還要強大。 實現(xiàn)傳統(tǒng)的類繼承模型是很簡單,但是實現(xiàn) JavaScript 中的原型繼承則要困難的多。
由于 JavaScript 是唯一一個被廣泛使用的基于原型繼承的語言,所以理解兩種繼承模式的差異是需要一定時間的。
第一個不同之處在于 JavaScript 使用原型鏈的繼承方式。
注意: 簡單的使用
Bar.prototype = Foo.prototype將會導(dǎo)致兩個對象共享相同的原型。 因此,改變?nèi)我庖粋€對象的原型都會影響到另一個對象的原型,在大多數(shù)情況下這不是希望的結(jié)果。
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// 設(shè)置Bar的prototype屬性為Foo的實例對象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
// 修正Bar.prototype.constructor為Bar本身
Bar.prototype.constructor = Bar;
var test = new Bar() // 創(chuàng)建Bar的一個新實例
// 原型鏈
test [Bar的實例]
Bar.prototype [Foo的實例]
{ foo: 'Hello World' }
Foo.prototype
{method: ...};
Object.prototype
{toString: ... /* etc. */};
上面的例子中,test 對象從 Bar.prototype 和 Foo.prototype 繼承下來;因此,
它能訪問 Foo 的原型方法 method。同時,它也能夠訪問那個定義在原型上的 Foo 實例屬性 value。
需要注意的是 new Bar() 不會創(chuàng)造出一個新的 Foo 實例,而是
重復(fù)使用它原型上的那個實例;因此,所有的 Bar 實例都會共享相同的 value 屬性。
注意: 不要使用
Bar.prototype = Foo,因為這不會執(zhí)行Foo的原型,而是指向函數(shù)Foo。 因此原型鏈將會回溯到Function.prototype而不是Foo.prototype,因此method將不會在 Bar 的原型鏈上。
當(dāng)查找一個對象的屬性時,JavaScript 會向上遍歷原型鏈,直到找到給定名稱的屬性為止。
到查找到達原型鏈的頂部 - 也就是 Object.prototype - 但是仍然沒有找到指定的屬性,就會返回 undefined。
當(dāng)原型屬性用來創(chuàng)建原型鏈時,可以把任何類型的值賦給它(prototype)。
然而將原子類型賦給 prototype 的操作將會被忽略。
function Foo() {}
Foo.prototype = 1; // 無效
而將對象賦值給 prototype,正如上面的例子所示,將會動態(tài)的創(chuàng)建原型鏈。
如果一個屬性在原型鏈的上端,則對于查找時間將帶來不利影響。特別的,試圖獲取一個不存在的屬性將會遍歷整個原型鏈。
并且,當(dāng)使用for in循環(huán)遍歷對象的屬性時,原型鏈上的所有屬性都將被訪問。
一個錯誤特性被經(jīng)常使用,那就是擴展 Object.prototype 或者其他內(nèi)置類型的原型對象。
這種技術(shù)被稱之為 monkey patching 并且會破壞封裝。雖然它被廣泛的應(yīng)用到一些 JavaScript 類庫中比如 Prototype, 但是我仍然不認為為內(nèi)置類型添加一些非標(biāo)準(zhǔn)的函數(shù)是個好主意。
擴展內(nèi)置類型的唯一理由是為了和新的 JavaScript 保持一致,比如 Array.forEach。
在寫復(fù)雜的 JavaScript 應(yīng)用之前,充分理解原型鏈繼承的工作方式是每個 JavaScript 程序員必修的功課。 要提防原型鏈過長帶來的性能問題,并知道如何通過縮短原型鏈來提高性能。 更進一步,絕對不要擴展內(nèi)置類型的原型,除非是為了和新的 JavaScript 引擎兼容。