構(gòu)造函數(shù)與原型
與大部分面向?qū)ο笳Z言不同龄减,JavaScript中并沒有引入類(class)的概念,但JavaScript仍然大量地使用了對象班眯,為了保證對象之間的聯(lián)系希停,JavaScript引入了原型與原型鏈的概念。
在Java中署隘,聲明一個(gè)實(shí)例的寫法是這樣的:
ClassName obj = new ClassName()
復(fù)制代碼為了保證JavaScript“看起來像Java”宠能,JavaScript中也加入了new操作符:
var obj = new FunctionName()
復(fù)制代碼可以看到,與Java不同的是磁餐,JavaScript中的new操作符后面跟的并非類名而是函數(shù)名违崇,JavaScript并非通過類而是直接通過構(gòu)造函數(shù)來創(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')
復(fù)制代碼上述代碼就是聲明一個(gè)構(gòu)造函數(shù)并通過構(gòu)造函數(shù)創(chuàng)建實(shí)例的過程诊霹,這樣看起來似乎有點(diǎn)面向?qū)ο蟮臉幼恿诵哐樱珜?shí)際上這種方法還存在一個(gè)很大的問題。
在上面的代碼中脾还,有兩個(gè)實(shí)例被創(chuàng)建伴箩,它們有自己的名字、顏色鄙漏,但它們的bark方法是一樣的嗤谚,而通過構(gòu)造函數(shù)創(chuàng)建實(shí)例的時(shí)候,每創(chuàng)建一個(gè)實(shí)例怔蚌,都需要重新創(chuàng)建這個(gè)方法巩步,再把它添加到新的實(shí)例中。這無疑造成了很大的浪費(fèi)媚创,既然實(shí)例的方法都是一樣的渗钉,為什么不把這個(gè)方法單獨(dú)放到一個(gè)地方,并讓所有的實(shí)例都可以訪問到呢钞钙。
這里就需要用到原型(prototype):
每一個(gè)構(gòu)造函數(shù)都擁有一個(gè)prototype屬性鳄橘,這個(gè)屬性指向一個(gè)對象,也就是原型對象芒炼。當(dāng)使用這個(gè)構(gòu)造函數(shù)創(chuàng)建實(shí)例的時(shí)候瘫怜,prototype屬性指向的原型對象就成為實(shí)例的原型對象。
原型對象默認(rèn)擁有一個(gè)constructor屬性本刽,指向指向它的那個(gè)構(gòu)造函數(shù)(也就是說構(gòu)造函數(shù)和原型對象是互相指向的關(guān)系)鲸湃。
每個(gè)對象都擁有一個(gè)隱藏的屬性[[prototype]],指向它的原型對象子寓,這個(gè)屬性可以通過
Object.getPrototypeOf(obj) 或 obj.proto 來訪問暗挑。
實(shí)際上,構(gòu)造函數(shù)的prototype屬性與它創(chuàng)建的實(shí)例對象的[[prototype]]屬性指向的是同一個(gè)對象斜友,即 對象.proto === 函數(shù).prototype 炸裆。
如上文所述,原型對象就是用來存放實(shí)例中共有的那部分屬性鲜屏。
在JavaScript中烹看,所有的對象都是由它的原型對象繼承而來,反之洛史,所有的對象都可以作為原型對象存在惯殊。
訪問對象的屬性時(shí),JavaScript會(huì)首先在對象自身的屬性內(nèi)查找也殖,若沒有找到土思,則會(huì)跳轉(zhuǎn)到該對象的原型對象中查找。
那么可以將上述代碼稍微做些修改忆嗜,這里把bark方法放入Dog構(gòu)造函數(shù)的原型中:
function Dog(name, color) {
this.name = name
this.color = color
}
Dog.prototype.bark = () => {
console.log('wangwang~')
}
復(fù)制代碼接著再次通過這個(gè)構(gòu)造函數(shù)創(chuàng)建實(shí)例并調(diào)用它的bark方法:
const dog1 = new Dog('dog1', 'black')
dog1.bark() //'wangwang~'
復(fù)制代碼可以看到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???'
復(fù)制代碼這里dog2重寫bark方法并沒有對dog1造成影響霎褐,因?yàn)樗貙慴ark方法的操作實(shí)際上是為自己添加了一個(gè)新的方法使原型中的bark方法被覆蓋了址愿,而并非直接修改了原型中的方法。若想要修改原型中的方法冻璃,需要通過構(gòu)造函數(shù)的prototype屬性:
Dog.prototype.bark = () => {
console.log('haha~')
}
dog1.bark() //'haha~'
dog2.bark() //'haha~'
復(fù)制代碼這樣看起來就沒什么問題了响谓,將實(shí)例中共有的屬性放到原型對象中,讓所有實(shí)例共享這部分屬性省艳。如果想要統(tǒng)一修改所有實(shí)例繼承的屬性娘纷,只需要直接修改原型對象中的屬性即可。而且每個(gè)實(shí)例仍然可以重寫原型中已經(jīng)存在的屬性來覆蓋這個(gè)屬性跋炕,并且不會(huì)影響到其他的實(shí)例赖晶。
原型鏈與繼承
上文提到,JavaScript中所有的對象都是由它的原型對象繼承而來。而原型對象自身也是一個(gè)對象遏插,它也有自己的原型對象捂贿,這樣層層上溯,就形成了一個(gè)類似鏈表的結(jié)構(gòu)胳嘲,這就是原型鏈(prototype chain)厂僧。
所有原型鏈的終點(diǎn)都是Object函數(shù)的prototype屬性,因?yàn)樵贘avaScript中的對象都默認(rèn)由Object()構(gòu)造了牛。Objec.prototype指向的原型對象同樣擁有原型颜屠,不過它的原型是null,而null則沒有原型鹰祸。
通過原型鏈就可以在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()
復(fù)制代碼這里聲明了一個(gè)新的構(gòu)造函數(shù)Husky,通過call方法繼承Dog中的屬性(call方法的作用可以簡單理解為將Dog中的屬性添加到Husky中敬锐,因?yàn)檫€涉及到其他的知識(shí)點(diǎn)所以不多贅述)背传,并添加了一個(gè)weight屬性。然后用Dog函數(shù)創(chuàng)建了一個(gè)實(shí)例作為Husky的原型對象賦值給Husky.prototype以繼承方法台夺。這樣径玖,通過Husky函數(shù)創(chuàng)建的實(shí)例就擁有了Dog中的屬性和方法。
結(jié)語
如果想要深入了解關(guān)于JavaScript中的對象和原型鏈的話颤介,無腦推薦紅寶書(《JavaScript高級程序設(shè)計(jì)(第3版)》)吧梳星,第六章關(guān)于原型鏈有相當(dāng)詳細(xì)的講解。
作者:clancysong
鏈接:https://juejin.im/post/5a94c0de5188257a8929d837
來源:掘金
著作權(quán)歸作者所有滚朵。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)冤灾,非商業(yè)轉(zhuǎn)載請注明出處。