最近在學(xué)習(xí)JavaScript的面向?qū)ο螅籧onstructor,prototype和_proto_搞到頭大,但還好,磕磕絆碰碰,終于了解了一點(diǎn)原型繼承。接下來(lái)我們來(lái)看看這個(gè)原型繼承是怎么回事
一、關(guān)于原型的幾個(gè)概念
- prototype屬性
- _proto_屬性
- constructor屬性
他們直觀的關(guān)系是:
function/
|——prototype/
|—— constructor
|—— __proto__
prototype屬性
任何一個(gè)函數(shù)都存在著這樣的一個(gè)prototype屬性,這個(gè)屬性是一個(gè)對(duì)象仙蚜,稱為原型對(duì)象艳丛。這個(gè)原型對(duì)象里有自函數(shù)一創(chuàng)建就生成的兩個(gè)屬性一個(gè)是constructor和 _proto_,除此之外我們可以給這個(gè)對(duì)象添加屬性和方法,就像給一個(gè)普通對(duì)象動(dòng)態(tài)添加屬性和方法那樣
example:
//創(chuàng)建基類(或叫超類或叫父類)
function Person(){};
Person.prototype.name = 'kuohao';
Person.prototype.age = 21;
Person.prototype.method = function (){
return this.name +':'+ this.age;
}
//給Person構(gòu)造函數(shù)的原型對(duì)象添加一些屬性和方法
//(為什么說(shuō)它是構(gòu)造函數(shù)呢袭厂,因?yàn)樾枰脕?lái)構(gòu)造對(duì)象橄杨,實(shí)際上跟普通函數(shù)一樣)
現(xiàn)在原型對(duì)象就多了兩個(gè)屬性和一個(gè)方法
Person/
|——prototype/
|—— constructor
|—— __proto__
|——name:'kuhao'
|——age:21
|——method:function(){
return……
〈狭}
這個(gè)原型對(duì)象有啥用惯悠?鸭蛙?寝凌?
請(qǐng)看——
//實(shí)例化
var guy1 = new Person();
var guy2 = new Person();
console.log(guy1.name); //'kuohao'
console.log(guy2.name); //'kuohao'
guy1.method() //'kuohao':21
guy2.method() //'kuohao':21
我們沒(méi)有給他們定義屬性和方法伐债,他們繼承了Person構(gòu)造函數(shù)原型對(duì)象里面的屬性和方法祖今,注意我們也沒(méi)給Person構(gòu)造函數(shù)定義屬性和方法噢
咋一看邪驮,跟構(gòu)造函數(shù)和工廠模式差不多喻粹,他們都生成擁有相同屬性和方法的對(duì)象型酥。但是原型對(duì)象生成的對(duì)象有一個(gè)很重要的特點(diǎn)就是他們共用一個(gè)原型對(duì)象的屬性和方法查乒。
為什么這么說(shuō)呢?
使用布爾運(yùn)算來(lái)驗(yàn)證一下就知道了
guy1.method()===guy2.method; //true
如果是構(gòu)造函數(shù)生成的對(duì)象州弟,他們的方法都不相等潭陪,原因就是構(gòu)造函數(shù)生成的對(duì)象有不同的引用的地址,而構(gòu)造函數(shù)的原型繼承方法生成的對(duì)象指向了同一個(gè)引用地址慷嗜,他們的對(duì)象都使用這個(gè)引用地址的屬性和方法菌赖,所以他們的方法是相等的辕羽,對(duì)于生成的對(duì)象的new方法绰寞,在另外一篇文章會(huì)講到他炊。
_proto_屬性
這是相當(dāng)重要的一個(gè)屬性涩笤,但是它不是標(biāo)準(zhǔn)屬性俏脊,在標(biāo)準(zhǔn)中全谤,它是不對(duì)外開(kāi)放的,當(dāng)時(shí)chrome和Firefox的私有屬性中對(duì)外暴露了它爷贫,使得我們可以比較清楚了了解原型繼承的過(guò)程认然。
_proto_這個(gè)屬性可以讓我們?cè)L問(wèn)創(chuàng)建當(dāng)前對(duì)象的構(gòu)造函數(shù)的prototype原型對(duì)象。那么當(dāng)我們調(diào)用guy1.method()方法的時(shí)候漫萄,其實(shí)是這樣的:
- 當(dāng)js解析器解析guy1.method()的時(shí)候卷员,先看看guy1對(duì)象實(shí)例里有沒(méi)有method這個(gè)方法
js解析器問(wèn):"喂,哥們腾务,你的method方法呢?"
guy1說(shuō):"這個(gè)方法是我老爸的毕骡,不在我這里"
js解析器問(wèn):“怎么找到你老爸?”
guy1指著_proto_說(shuō):“這里有道門(mén),你進(jìn)去就可以找到他了”
2.然后js解析器就又跑去找他老豆未巫,找到他老豆后
問(wèn):“喂喂窿撬,你兒子叫我來(lái)找method方法,在哪叙凡?”
guy1的老豆Person就拿出一個(gè)箱子(prototype)劈伴,說(shuō):“method方法就在里面,你拿去吧握爷!”
如果Person父類還是沒(méi)找到method方法的話跛璧,實(shí)際上它的prototype對(duì)象也有一個(gè)_proto_屬性,可以訪問(wèn)guy1的爺爺類的prototype對(duì)象饼拍,找到method方法赡模。
所以,這個(gè)查找是一環(huán)接一環(huán)的师抄,就像遞歸一樣漓柑。
![原型鏈,圖片來(lái)自慕課網(wǎng)].PNG](http://upload-images.jianshu.io/upload_images/1577855-21c757167154f75a.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
constructor屬性
constructor屬性指向的是創(chuàng)建此對(duì)象的構(gòu)造函數(shù)的引用叨吮,這個(gè)屬性存在prototype原型對(duì)象中辆布,構(gòu)造函數(shù)的對(duì)象實(shí)例也可以通過(guò)constructor屬性來(lái)訪問(wèn)構(gòu)造它的那個(gè)函數(shù)。
這個(gè)屬性對(duì)于原型繼承的實(shí)現(xiàn)來(lái)說(shuō)茶鉴,不是很重要锋玲。
二、原型繼承的實(shí)現(xiàn)
//父類
function Person(){};
Person.prototype.method = function (){
return 'hello';
}
//子類
function Child(){};
Child.prototype = Object.create(Person.prototype);
Child.prototype.constructor = Child;
Child.prototype.say = function (){
return 'hello world';
}
var c1 = new Child();
c1.method(); //'hello'
c1.say(); //'hello world'
//由此可見(jiàn)子類繼承了父類的方法涵叮,也擁有自己的方法
Object.create()方法
Object.create()方法是ES5的一個(gè)用來(lái)創(chuàng)建一個(gè)指定原型和若干指定屬性的對(duì)象的方法惭蹂。
定義來(lái)自 Mozilla開(kāi)發(fā)者文檔
Child.prototype = Object.create(Person.prototye);
這句話的含義就是創(chuàng)建一個(gè)以Person原型對(duì)象為原型的空對(duì)象,賦值給Child的prototype原型對(duì)象.
Object.create(Person.prototye),相當(dāng)與創(chuàng)建了這樣一個(gè)對(duì)象:
{
constructor
//指向Person構(gòu)造函數(shù)割粮,這就是為什么上面要把這個(gè)屬性指回Child的原因
__proto__
//指向Person.prototype盾碗,上面說(shuō)到原型鏈繼承正是通過(guò)這個(gè)屬性層層查找實(shí)現(xiàn)的
}
將這個(gè)對(duì)象去覆蓋Child子類的prototype,這就成功與Person父類搭上了關(guān)系再此之前舀瓢,Person與Child沒(méi)任何關(guān)系廷雅。
但是Object.create()這個(gè)方法是新方法,低級(jí)的瀏覽器京髓,如IE9以下不支持航缀,需要做一下兼容處理
examle:
function createObejct(o){
if(!Object.create){
//新建一個(gè)構(gòu)造函數(shù),用來(lái)做臨時(shí)對(duì)象
function a(){};
//把原型對(duì)象參數(shù)賦給這個(gè)空對(duì)象的原型對(duì)象
a.prototype = o;
return new a();
//實(shí)例化一個(gè)出來(lái)一個(gè)原型對(duì)象指向原型對(duì)象參數(shù)的空對(duì)象堰怨,返回給上下文
}else{
return Object.create(o);
//直接使用
}
}
Object.create的一些替代方法
- 直接將Person.prototype賦值給Child.prototype
Child.prototype = Person.prototype
這樣Person和Child就共用一個(gè)原型對(duì)象芥玉,他們的屬性和方法都是一樣的,但是這個(gè)就有問(wèn)題了备图,如果Child類想擴(kuò)展自己的屬性和方法飞傀,那么Person類也會(huì)受到影響皇型。
這里要注意的是賦一個(gè)原型對(duì)象為Person.prototype的空對(duì)象(上面的方法)跟賦一個(gè)Person.prototype是不一樣的。
前者Child.prototype指向空對(duì)象的引用再由空對(duì)象中的_proto_\指向Person.prototype砸烦,后者Child.prototype直接指向Person.prototype引用弃鸦,后續(xù)的修改在都Person.prototype上,因?yàn)樗麄児灿靡粋€(gè)原型對(duì)象。
所以這個(gè)方法是非常不可取的幢痘,跟面向?qū)ο蟮亩鄳B(tài)特性相悖
2.new 一個(gè)Person的對(duì)象實(shí)例賦給Child的原型對(duì)象
Child.prototype = new Person()
這貌似是一個(gè)不錯(cuò)的方法唬格,但是它也有弊端,就是事實(shí)上我們常常不會(huì)使用一個(gè)空的構(gòu)造函數(shù)颜说,來(lái)實(shí)例化對(duì)象购岗,我們往往會(huì)將構(gòu)造函數(shù)繼承和原型繼承結(jié)合在一起,所以我們的構(gòu)造函數(shù)往往會(huì)有一些實(shí)例屬性和實(shí)例方法门粪,如果new Person()出來(lái)一個(gè)實(shí)例對(duì)象喊积,對(duì)象里面還夾帶著一些實(shí)例屬性和方法,這看起來(lái)非常怪玄妈,雖然也是一個(gè)指向Person.prototype的對(duì)象乾吻。所以這種方法也不推薦使用,應(yīng)該使用 Object.create() 較為妥當(dāng).
總結(jié):
我個(gè)人覺(jué)得實(shí)現(xiàn)原型繼承最重要就是兩個(gè)屬性的理解拟蜻,prototype原型對(duì)象和_proto_這個(gè)接口的概念的理解绎签,再結(jié)合原型鏈層層查找這種繼承方式的直觀感知,就能比較好地掌握原型式繼承酝锅。我也是這兩天才理解的原型鏈?zhǔn)嚼^承诡必,如果有所繆誤,請(qǐng)批評(píng)指正搔扁!