這篇文章主要介紹了JavaScript中的原型繼承基礎(chǔ)學(xué)習(xí)教程,基于原型prototype的繼承是JavaScript中實(shí)現(xiàn)面向?qū)ο笾械睦^承特性的基本手段,需要的朋友可以參考下
大多數(shù)編程語言中,都有類和對(duì)象,一個(gè)類可以繼承其他類。
在JavaScript中,繼承是基于原型的(prototype-based),這意味著JavaScript中沒有類,取而代之的是一個(gè)對(duì)象繼承另一個(gè)對(duì)象。:)
1. 繼承, the proto
在JavaScript中,當(dāng)一個(gè)對(duì)象rabbit繼承另一了對(duì)象animal時(shí),這意味著rabbit對(duì)象中將會(huì)有一個(gè)特殊的屬性:rabbit.__proto__ = animal;
當(dāng)訪問rabbit對(duì)象時(shí),如果解釋器在rabbit中不能找到屬性,那么它會(huì)順著__proto__鏈往上在animal對(duì)象中尋找
栗子中的__proto__屬性僅在Chrome和FireFox中可以訪問,請(qǐng)看一個(gè)栗子:
var animal = { eats: true }
var rabbit = { jumps: true }
rabbit.__proto__ = animal // inherit
alert(rabbit.eats) // true
eats屬性是從animal對(duì)象中訪問的。
如果在rabbit對(duì)象中已經(jīng)發(fā)現(xiàn)了屬性,那么就不會(huì)去檢查proto屬性啦。
再來一個(gè)栗子,當(dāng)子類中也有eats屬性時(shí),父類中的就不會(huì)訪問了。
var animal = { eats: true }
var fedUpRabbit = { eats: false}
fedUpRabbit.__proto__ = animal
alert(fedUpRabbit.eats) // false
你也可以在animal中添加一個(gè)函數(shù),那么在rabbit中也可以訪問了。
var animal = {
eat: function() {
alert( "I'm full" )
this.full = true
}
}
var rabbit = {
jump: function() { /* something */ }
}
rabbit.__proto__ = animal
(1)rabbit.eat():
rabbit.eat()函數(shù)以如下兩步執(zhí)行:
首先,解釋器查找rabbit.eat,rabbit中沒有eat函數(shù),那么它就順著rabbit.__proto__往上找,在animal中找到了。
函數(shù)以this = rabbit運(yùn)行。this值與__proto__屬性完全無關(guān)。
因此,this.full = true在rabbit中:
看看這里我們有什么新發(fā)現(xiàn),一個(gè)對(duì)象調(diào)用了父類函數(shù),但是this還是指向?qū)ο蟊旧?,這就是繼承。
被__proto__引用的對(duì)象稱作是原型(prototype),animal是rabbit的原型(譯者注:這就是rabbit的__proto__屬性引用了animal 的prototype屬性)
(2)讀時(shí)查找,不是寫時(shí)
當(dāng)讀取一個(gè)對(duì)象時(shí),比如this.prop,解釋器會(huì)在它的原型中查找屬性。
當(dāng)設(shè)置一個(gè)屬性值時(shí),比如this.prop = value,那么就沒有理由去查找了,這個(gè)屬性(prop)會(huì)被直接添加到這個(gè)對(duì)象中(這里是this)。delete obj.prop也類似,它只刪除對(duì)象本身的屬性,原型中的屬性保持原封不動(dòng)。
(3)關(guān)于proto
如果你在閱讀指南,這里我們叫的__proto__,在指南中表示為[[Prototype]]。雙方括號(hào)是很重要的,因?yàn)橛辛硪粋€(gè)屬性叫做prototype。
2. Object.create, Object.getPrototypeOf
__proto__是一個(gè)非標(biāo)準(zhǔn)的屬性,由Chrome/FireFox提供訪問,在其他的瀏覽器中保持不可見。
所有的現(xiàn)代瀏覽器除了Opera(IE > 9)支持兩個(gè)標(biāo)準(zhǔn)的函數(shù)來處理原型問題:
Object.ceate(prop[,props])
用給定了proto創(chuàng)建一個(gè)空對(duì)象:
var animal = { eats: true }
rabbit = Object.create(animal)
alert(rabbit.eats) // true
上面代碼創(chuàng)建了一個(gè)空rabbit對(duì)象,并且原型設(shè)置為animal
rabbit對(duì)象創(chuàng)建好以后,我們可以往里添加屬性了:
var animal = { eats: true }
rabbit = Object.create(animal)
rabbit.jumps = true
Object.creat函數(shù)的第二個(gè)參數(shù)props是可選的,它允許像新對(duì)象設(shè)置屬性。這里就省略了,因?yàn)槲覀冴P(guān)系的繼承。
(1)Object.getPrototypeOf(obj)
返回obj.__proto__的值。這個(gè)函數(shù)是標(biāo)準(zhǔn)的,可以在不能直接訪問__proto__屬性的瀏覽器中使用了。
var animal = {
eats: true
}
rabbit = Object.create(animal)
alert( Object.getPrototypeOf(rabbit) === animal ) // true
現(xiàn)代瀏覽器允許讀取__proto__屬性值,但是不能設(shè)置。
3. The prototype
有一些好的跨瀏覽器的方式設(shè)置__proto__屬性,這將會(huì)使用構(gòu)造器函數(shù)(constructor functions)。記住!任何函數(shù)創(chuàng)建一個(gè)對(duì)象都是通過new關(guān)鍵字的。
一個(gè)栗子:
function Rabbit(name) {
this.name = name
}
var rabbit = new Rabbit('John')
alert(rabbit.name) // John
new操作將原型的屬性設(shè)置到rabbit對(duì)象的的__proto__屬性中了。
讓我們來看看它的原理,例如,new Rabbit 對(duì)象,而Rabbit是繼承animal 的。
var animal = { eats: true }
function Rabbit(name) {
this.name = name
}
Rabbit.prototype = animal
var rabbit = new Rabbit('John')
alert( rabbit.eats ) // true, because rabbit.__proto__ == animal
Rabbit.prototype = animal 字面量意味著:對(duì)所有由new Rabbit創(chuàng)建的對(duì)象設(shè)__proto__ = animal
4. 跨瀏覽器 Object.create(proto)
Object.create(prop)函數(shù)功能的強(qiáng)大的,因?yàn)樗试S從給定的對(duì)象直接繼承。它可以由如下代碼模擬:
function inherit(proto) {
function F() {}
F.prototype = proto
return new F
}
inherit(animal) 與Object.create(animal)是完全等同的,返回一個(gè)空的對(duì)象,并且object.__proto__ = animal。
一個(gè)栗子:
var animal = { eats: true }
var rabbit = inherit(animal)
alert(rabbit.eats) // true
alert(rabbit.hasOwnProperty('eats')) // false, from prototype
來看一下它的原理是什么:
function inherit(proto) {
function F() {} // (1)
F.prototype = proto // (2)
return new F() // (3)
}
(1) 創(chuàng)建了一個(gè)新函數(shù),函數(shù)沒有向this設(shè)置任何屬性,以此`new F` 會(huì)創(chuàng)建一個(gè)空對(duì)象。
(2) `F.prototype`被設(shè)置為proto
(3) `new` F創(chuàng)建了一個(gè)空對(duì)象,對(duì)象的`__proto__ = F.prototype`
(4) Bingo! 我們得到了一個(gè)繼承`proto`的空對(duì)象
這個(gè)函數(shù)廣泛適用于各種庫和框架之中。
你的函數(shù)接受了一個(gè)帶有options 的對(duì)象
/* options contains menu settings: width, height etc */
function Menu(options) {
// ...
}
你想設(shè)置某些options
function Menu(options) {
options.width = options.width || 300 // set default value
// ...
}
。。。但是改變參數(shù)值可能會(huì)產(chǎn)生一些錯(cuò)誤的結(jié)果,因?yàn)閛ptions可能會(huì)在外部代碼中使用。一個(gè)解決辦法就是克隆options對(duì)象,復(fù)制所有的屬性到一個(gè)新的對(duì)象中,在新對(duì)象中修改,
怎樣用繼承來解決這個(gè)問題呢? P.S. options可以添加設(shè)設(shè)置,但是不能被刪除。
Solution
你可以繼承options,并且在它的子類的中修改或者添加新的屬性。
function inherit(proto) {
function F() {}
F.prototype = proto
return new F
}
function Menu(options) {
var opts = inherit(options)
opts.width = opts.width || 300
// ...
}
所有的操作只在子對(duì)象中有效,當(dāng)Menu方法結(jié)束時(shí),外部代碼仍然可以使用沒有修改的過的options對(duì)象。delete操作在這里非常重要,如果width是一個(gè)prototype中的屬性,delete opts.width不會(huì)產(chǎn)生任何作用
5. hasOwnProperty
所有的對(duì)象都有hasOwnProperty函數(shù),它可以用來檢測(cè)一個(gè)屬性是否對(duì)象自身還是屬于原型
一個(gè)栗子:
function Rabbit(name) {
this.name = name
}
Rabbit.prototype = { eats: true }
var rabbit = new Rabbit('John')
alert( rabbit.hasOwnProperty('eats') ) // false, in prototype
alert( rabbit.hasOwnProperty('name') ) // true, in object
6. Looping with/without inherited properties
for..in循環(huán)輸出一個(gè)對(duì)象的所有屬性,包括自身的和原型的。
function Rabbit(name) {
this.name = name
}
Rabbit.prototype = { eats: true }
var rabbit = new Rabbit('John')
for(var p in rabbit) {
alert (p + " = " + rabbit[p]) // outputs both "name" and "eats"
}
用hasOwnProperty可以過濾得到屬于對(duì)象自己的屬性:
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"
alert (p + " = " + rabbit[p]) // outputs only "name"
}
7. Summary
JavaScript是通過一個(gè)特殊的屬性proto來實(shí)現(xiàn)繼承的
當(dāng)訪問一個(gè)對(duì)象的屬性時(shí),如果解釋器不能在對(duì)象中找到,它就會(huì)去對(duì)象的原型中繼續(xù)尋找 對(duì)函數(shù)屬性來說,this指向這個(gè)對(duì)象,而不是它的原型。
賦值obj.prop = value, 刪除delete obj.prop
管理proto:
Chrome和FireFox可以直接訪問對(duì)象的__proto__屬性,大多數(shù)現(xiàn)代瀏覽器支持用Object.getPrototypeOf(obj)只讀訪問。
Object.create(proto) 可以用給定的proto生成空的子對(duì)象,或者通過如下代碼達(dá)到相同的功能:
function inherit(proto) {
function F() {}
F.prototype = proto
return new F()
}
其他方法:
for..in循環(huán)輸出一個(gè)對(duì)象的所有屬性(包括自身的和原型的)和對(duì)象的原型鏈。
如果一個(gè)屬性prop屬于對(duì)象obj那么obj.hasOwnProperty(prop)返回true,否則返回false。