這是一篇關於JavaScript的物件導向和繼承的文章,寫於1年前,作者循序漸進,對想學習JavaScript中物件導向的同學來說是很有幫助的,因此試著翻譯一下,不妥之處,請指正。原文連結Objects and Inheritance in Javascript
雖然有些Javascript使用者可能永遠也不需要知道原型或物件導向語言的性質,但是那些來自傳統物件導向的語言的開發者使用的時候會發現JavaScript的繼承模型非常的奇怪。而不同的JS框架提供了各自的方法來編寫類別物件導向(class-like)的程式碼,這使得JS的物件導向更加的難以理解。這樣帶來的結果是:
1、沒有一個標準的方法來實現物件導向。
2、JS物件導向的底層概念並沒有被人們的熟悉
原型繼承
原型繼承是一個非常簡單的概念,它的本質是:
1、物件a繼承自物件b,就說b是a的原型(prototype)。
2、a繼承了b的所有的屬性,即如果b.x的值為1,那麼a.x的值為1
3、a自身的屬性會重寫b中同名的屬性
讓我們在具體的代碼中看看效果,假設有一個John Smith和一個繼承自他的Jane。
var john = {firstName: '>
var john = {firstName: 'John', lastName: 'Smith'};
var jane = {firstName: 'Jane'};
jane.__proto__ = john;
現在,我們稱john為jane的原型(prototype), jane繼承了john的所有的屬性
複製代碼
代碼如下:
jane.lastName
"Smith"//此屬性繼承自john
jane本身的屬性具有較高的優先權,如下
複製程式碼
程式碼如下:
jane.firstName;//此屬性覆寫了john中的firstName屬性
"Jane"
如果在這之後為john新增一個屬性,jane也會動態的繼承該屬性,如下所示複製程式碼
程式碼如下:
john.hair = 'brown'; //為john新增一個新屬性
複製程式碼
複製程式碼
此屬性覆蓋了john當中的同名的屬性(lastName)
複製代碼
代碼如下:
複製程式碼
程式碼如下: delete jane.lastName 該屬性的值就會恢復為john的值[code] jane.lastName "Smith" 現在,jane也可以繼承自其它的物件。在這個鏈中可以有任意多個繼承,我們將它稱為原型鏈(prototype chain),實際上,john也有一個prototype屬性複製程式碼 程式碼如下: john.__proto__; Object { }
在Firebug的控制台中,john.__proto__的值設定為了Object{},但是Object{}代表Object.prototype這個物件-所有物件的父類別。
這是對原型繼承的一個簡要描述。看起來還不錯,對吧?
但是,實際上,我們是不能用__proto__的。 。 。
告訴大家一個不好的消息......
IE不支援__proto__屬性,實際上,__proto__並不是ECMAScript規範中的屬性,而且,Mozilla也打算在火狐瀏覽器以後的版本會去掉該屬性。
但是,這並不表示__proto__屬性不存在。雖然在某些瀏覽器中無法直接存取__proto__屬性,但是它還是以某種形式存在,我們還得和他打交道,只不過沒有那麼直接了。
類別與繼承 因此,我們可以說,JavaScript沒有類別
請記住:JavaScript中沒有類別
那既然這樣,方法和繼承有事怎麼實現的呢?
通過原型(prototype)。在傳統的物件導向的語言中,方法是依賴類別的,而在JavaScript當中,方法是依賴物件的原型的,並且,原型是和物件的構造器(constructor)綁定在一起的。
在JavaScript當中,函數起到了構造器(constructor)的作用。透過使用new運算符,你可以把一個函數當作構造函數(constructor)來用。下面的代碼展示了我們創建了一個Cat函數:
function Cat(name){ // this.name = name // this指向新建的物件
}
以上程式碼會自動建立一個Cat.prototype物件
Cat.prototype }
我們可用new運算子來建立Cat的一個實例
程式碼如下:
var garfield = new Cat('Garfield') // 建立一個實例- Cat函數充當了建構子
現在,Cat.prototype物件成為了所有的通過new Cat()創建的對象的原型,例如: 複製代碼
代碼如下:
garfield.__proto__ === Cat.prototype
true //看到了嗎? `Cat.prototype` 現在是garfield物件的原型
現在,我們為Cat.prototype新增一個方法,添加之後,該方法可以被garfield訪問到複製代碼
代碼如下:
Cat .prototype.greet = function(){
console.log('Meow, I am ' this.name)
}
garfield.greet()
"Meow, I am Garfield"
程式碼如下:
程式碼如下:
var felix = new Cat('Felix')
felix.greet()
"Meow, I am Felix"
複製程式碼
程式碼如下:
garfield.greet = function(){
console.log("What's new?");
};
};
複製程式碼
程式碼如下: felix.greet(); "Meow, I am Felix"
因此,JavaScript中,方法可以直接和一個物件關聯,可以和物件的原型關聯,也可以和物件的任一父輩物件關聯,即,可以和原型鏈(prototype chain)的任意一環關聯。繼承也就是這樣實現的。
要建立一個二級原型鏈(prototype chain),我們首先需要建立另一個建構函式(constructor),叫Animal如何?
function Animal(){
function Animal(){
>
接下來,我們需要將Cat.prototype的原型指向一個Animal的對象,這樣一來,Cat的實例就會繼承所有的Animal的方法。因此,我們將Cat.prototype的值設定為Animal的實例,如下所示: 複製程式碼
程式碼如下:
Cat.prototype = new Animal();
除此之外,我們還要告訴新的Cat.prototype,它實際上是Cat的一個實例: 複製程式碼
程式碼如下:
Cat.prototype.constructor = Cat // 讓`Cat. prototype` 知道它是Cat的一個實例
雖然這樣做的目的主要是為了類別之間的層次關係,但通常還是有必要這樣做的。
現在,既然繼承自Animal.prototype和Cat.prototype的物件是屬於動物類,那麼所有Cat的實例也間接的繼承自Animal.prototype。如果我們為Animal.prototype增加一個新方法,那麼,所有的Cat的實例也能夠存取到該方法。 複製程式碼
程式碼如下:
Animal.prototype.breed = function{ >console.log('Making a new animal!');
return new this.constructor();
};
var kitty = garfield.breed();
Making a new animal!
透過上面的程式碼我們就實現了繼承,簡單吧。
結語 雖然JavaScript中基於原型的繼承很怪並且需要花一些時間才能習慣,但是他的核心思想是很簡單的。只要你真正理解了這些本質上的概念,你就會有信心在這些良莠不齊的程式碼中駕馭JavaScript的OO。 (完)^_^