前戲
- 寫的比較短了嚼锄,三分鐘看完應(yīng)該是沒(méi)問(wèn)題(嗯肩民。。)蚪战。
- 當(dāng)然最好再花半小時(shí)思考理解一下牵现。
正文
構(gòu)造函數(shù)與原型
與大部分面向?qū)ο笳Z(yǔ)言不同铐懊,JavaScript中并沒(méi)有引入類(class)的概念,但JavaScript仍然大量地使用了對(duì)象瞎疼,為了保證對(duì)象之間的聯(lián)系科乎,JavaScript引入了原型與原型鏈的概念。
在Java中贼急,聲明一個(gè)實(shí)例的寫法是這樣的:
ClassName obj = new ClassName()
為了保證JavaScript“看起來(lái)像Java”茅茂,JavaScript中也加入了new操作符:
var obj = new FunctionName()
可以看到,與Java不同的是太抓,JavaScript中的new操作符后面跟的并非類名而是函數(shù)名空闲,JavaScript并非通過(guò)類而是直接通過(guò)構(gòu)造函數(shù)來(lái)創(chuàng)建實(shí)例。
function Dog(name, color) {
this.name = name
this.color = color
this.bark = () => {
console.log('wangwang~')
}
}
const dog1 = new Dog('dog1', 'black')
const dog2 = new Dog('dog2', 'white')
上述代碼就是聲明一個(gè)構(gòu)造函數(shù)并通過(guò)構(gòu)造函數(shù)創(chuàng)建實(shí)例的過(guò)程走敌,這樣看起來(lái)似乎有點(diǎn)面向?qū)ο蟮臉幼恿瞬昵悖珜?shí)際上這種方法還存在一個(gè)很大的問(wèn)題。
在上面的代碼中悔常,有兩個(gè)實(shí)例被創(chuàng)建影斑,它們有自己的名字、顏色机打,但它們的bark方法是一樣的矫户,而通過(guò)構(gòu)造函數(shù)創(chuàng)建實(shí)例的時(shí)候,每創(chuàng)建一個(gè)實(shí)例残邀,都需要重新創(chuàng)建這個(gè)方法皆辽,再把它添加到新的實(shí)例中。這無(wú)疑造成了很大的浪費(fèi)芥挣,既然實(shí)例的方法都是一樣的驱闷,為什么不把這個(gè)方法單獨(dú)放到一個(gè)地方,并讓所有的實(shí)例都可以訪問(wèn)到呢空免。
這里就需要用到原型(prototype):
- 每一個(gè)構(gòu)造函數(shù)都擁有一個(gè)prototype屬性空另,這個(gè)屬性指向一個(gè)對(duì)象,也就是原型對(duì)象蹋砚。當(dāng)使用這個(gè)構(gòu)造函數(shù)創(chuàng)建實(shí)例的時(shí)候扼菠,prototype屬性指向的原型對(duì)象就成為實(shí)例的原型對(duì)象。
- 原型對(duì)象默認(rèn)擁有一個(gè)constructor屬性坝咐,指向指向它的那個(gè)構(gòu)造函數(shù)(也就是說(shuō)構(gòu)造函數(shù)和原型對(duì)象是互相指向的關(guān)系)循榆。
- 每個(gè)對(duì)象都擁有一個(gè)隱藏的屬性[[prototype]],指向它的原型對(duì)象墨坚,這個(gè)屬性可以通過(guò)
Object.getPrototypeOf(obj)
或obj.__proto__
來(lái)訪問(wèn)秧饮。 - 實(shí)際上,構(gòu)造函數(shù)的prototype屬性與它創(chuàng)建的實(shí)例對(duì)象的[[prototype]]屬性指向的是同一個(gè)對(duì)象,即
對(duì)象.__proto__ === 函數(shù).prototype
盗尸。 - 如上文所述柑船,原型對(duì)象就是用來(lái)存放實(shí)例中共有的那部分屬性。
- 在JavaScript中泼各,所有的對(duì)象都是由它的原型對(duì)象繼承而來(lái)椎组,反之,所有的對(duì)象都可以作為原型對(duì)象存在历恐。
- 訪問(wèn)對(duì)象的屬性時(shí)寸癌,JavaScript會(huì)首先在對(duì)象自身的屬性內(nèi)查找,若沒(méi)有找到弱贼,則會(huì)跳轉(zhuǎn)到該對(duì)象的原型對(duì)象中查找蒸苇。
那么可以將上述代碼稍微做些修改,這里把bark方法放入Dog構(gòu)造函數(shù)的原型中:
function Dog(name, color) {
this.name = name
this.color = color
}
Dog.prototype.bark = () => {
console.log('wangwang~')
}
接著再次通過(guò)這個(gè)構(gòu)造函數(shù)創(chuàng)建實(shí)例并調(diào)用它的bark方法:
const dog1 = new Dog('dog1', 'black')
dog1.bark() //'wangwang~'
可以看到bark方法能夠正常被調(diào)用吮旅。這時(shí)再創(chuàng)建另一個(gè)實(shí)例并重寫它的bark方法溪烤,然后再次分別調(diào)用兩個(gè)實(shí)例的bark方法并觀察結(jié)果:
const dog2 = new Dog('dog2', 'white')
dog2.bark() = () => {
console.log('miaomiaomiao???')
}
dog1.bark() //'wangwang~'
dog2.bark() //'miaomiaomiao???'
這里dog2重寫bark方法并沒(méi)有對(duì)dog1造成影響,因?yàn)樗貙慴ark方法的操作實(shí)際上是為自己添加了一個(gè)新的方法使原型中的bark方法被覆蓋了庇勃,而并非直接修改了原型中的方法檬嘀。若想要修改原型中的方法,需要通過(guò)構(gòu)造函數(shù)的prototype屬性:
Dog.prototype.bark = () => {
console.log('haha~')
}
dog1.bark() //'haha~'
dog2.bark() //'haha~'
這樣看起來(lái)就沒(méi)什么問(wèn)題了责嚷,將實(shí)例中共有的屬性放到原型對(duì)象中鸳兽,讓所有實(shí)例共享這部分屬性。如果想要統(tǒng)一修改所有實(shí)例繼承的屬性罕拂,只需要直接修改原型對(duì)象中的屬性即可揍异。而且每個(gè)實(shí)例仍然可以重寫原型中已經(jīng)存在的屬性來(lái)覆蓋這個(gè)屬性,并且不會(huì)影響到其他的實(shí)例爆班。
原型鏈與繼承
上文提到衷掷,JavaScript中所有的對(duì)象都是由它的原型對(duì)象繼承而來(lái)。而原型對(duì)象自身也是一個(gè)對(duì)象柿菩,它也有自己的原型對(duì)象戚嗅,這樣層層上溯,就形成了一個(gè)類似鏈表的結(jié)構(gòu)枢舶,這就是原型鏈(prototype chain)懦胞。
所有原型鏈的終點(diǎn)都是Object函數(shù)的prototype屬性,因?yàn)樵贘avaScript中的對(duì)象都默認(rèn)由Object()構(gòu)造祟辟。Objec.prototype指向的原型對(duì)象同樣擁有原型医瘫,不過(guò)它的原型是null侣肄,而null則沒(méi)有原型旧困。
通過(guò)原型鏈就可以在JavaScript中實(shí)現(xiàn)繼承,JavaScript中的繼承相當(dāng)靈活,有多種繼承的實(shí)現(xiàn)方法吼具,這里只介紹一種最常用的繼承方法也就是組合繼承僚纷。
function Dog(name, color) {
this.name = name
this.color = color
}
Dog.prototype.bark = () => {
console.log('wangwang~')
}
function Husky(name, color, weight) {
Dog.call(this, name, color)
this.weight = weight
}
Husky.prototype = new Dog()
這里聲明了一個(gè)新的構(gòu)造函數(shù)Husky,通過(guò)call方法繼承Dog中的屬性(call方法的作用可以簡(jiǎn)單理解為將Dog中的屬性添加到Husky中拗盒,因?yàn)檫€涉及到其他的知識(shí)點(diǎn)所以不多贅述)怖竭,并添加了一個(gè)weight屬性。然后用Dog函數(shù)創(chuàng)建了一個(gè)實(shí)例作為Husky的原型對(duì)象賦值給Husky.prototype以繼承方法陡蝇。這樣痊臭,通過(guò)Husky函數(shù)創(chuàng)建的實(shí)例就擁有了Dog中的屬性和方法。
結(jié)語(yǔ)
如果想要深入了解關(guān)于JavaScript中的對(duì)象和原型鏈的話登夫,無(wú)腦推薦紅寶書(《JavaScript高級(jí)程序設(shè)計(jì)(第3版)》)吧广匙,第六章關(guān)于原型鏈有相當(dāng)詳細(xì)的講解。